chore: save local history restorations from accidental git restore

This commit is contained in:
sanbuphy
2026-02-23 01:40:56 +08:00
parent 780be69b99
commit 2a0fdd3392
27 changed files with 5971 additions and 2743 deletions
+388 -97
View File
@@ -573,74 +573,224 @@ export default defineConfig({
text: '一、计算机是怎么回事',
collapsed: false,
items: [
{ text: '从晶体管到 CPU', link: '/zh-cn/appendix/1-computer-fundamentals/transistor-to-cpu' },
{ text: '操作系统(进程 / 内存 / 文件系统)', link: '/zh-cn/appendix/1-computer-fundamentals/operating-systems' },
{ text: '数据的编码、存储与传输', link: '/zh-cn/appendix/1-computer-fundamentals/data-encoding-storage' },
{ text: '网络:两台电脑如何对话', link: '/zh-cn/appendix/1-computer-fundamentals/computer-networks' },
{ text: '数据结构', link: '/zh-cn/appendix/1-computer-fundamentals/data-structures' },
{ text: '算法思维入门', link: '/zh-cn/appendix/1-computer-fundamentals/algorithm-thinking' },
{ text: '编程语言图谱', link: '/zh-cn/appendix/1-computer-fundamentals/programming-languages' },
{ text: '类型系统与编译原理入门', link: '/zh-cn/appendix/1-computer-fundamentals/type-systems-compilers' }
{
text: '从晶体管到 CPU',
link: '/zh-cn/appendix/1-computer-fundamentals/transistor-to-cpu'
},
{
text: '操作系统(进程 / 内存 / 文件系统)',
link: '/zh-cn/appendix/1-computer-fundamentals/operating-systems'
},
{
text: '数据的编码、存储与传输',
link: '/zh-cn/appendix/1-computer-fundamentals/data-encoding-storage'
},
{
text: '网络:从输入网址到返回结果的过程',
link: '/zh-cn/appendix/1-computer-fundamentals/computer-networks'
},
{
text: '数据结构',
link: '/zh-cn/appendix/1-computer-fundamentals/data-structures'
},
{
text: '算法思维入门',
link: '/zh-cn/appendix/1-computer-fundamentals/algorithm-thinking'
},
{
text: '编程语言图谱',
link: '/zh-cn/appendix/1-computer-fundamentals/programming-languages'
},
{
text: '类型系统与编译原理入门',
link: '/zh-cn/appendix/1-computer-fundamentals/type-systems-compilers'
}
]
},
{
text: '二、开发环境与工具',
collapsed: false,
items: [
{ text: '集成开发环境 (IDE) 基础', link: '/zh-cn/appendix/2-development-tools/ide-basics' },
{ text: '命令行与 Shell 脚本', link: '/zh-cn/appendix/2-development-tools/command-line-shell' },
{ text: 'Git:代码的时光机', link: '/zh-cn/appendix/2-development-tools/git-version-control' },
{ text: '环境变量与 PATH', link: '/zh-cn/appendix/2-development-tools/environment-path' },
{ text: '端口与 localhost', link: '/zh-cn/appendix/2-development-tools/ports-localhost' },
{ text: 'SSH 与密钥认证', link: '/zh-cn/appendix/2-development-tools/ssh-authentication' },
{ text: '包管理器(npm / pip / cargo', link: '/zh-cn/appendix/2-development-tools/package-managers' },
{ text: '调试的艺术', link: '/zh-cn/appendix/2-development-tools/debugging-art/' },
{ text: '正则表达式', link: '/zh-cn/appendix/2-development-tools/regex' }
{
text: '集成开发环境 (IDE) 基础',
link: '/zh-cn/appendix/2-development-tools/ide-basics'
},
{
text: '命令行与 Shell 脚本',
link: '/zh-cn/appendix/2-development-tools/command-line-shell'
},
{
text: 'Git:代码的时光机',
link: '/zh-cn/appendix/2-development-tools/git-version-control'
},
{
text: '环境变量与 PATH',
link: '/zh-cn/appendix/2-development-tools/environment-path'
},
{
text: '端口与 localhost',
link: '/zh-cn/appendix/2-development-tools/ports-localhost'
},
{
text: 'SSH 与密钥认证',
link: '/zh-cn/appendix/2-development-tools/ssh-authentication'
},
{
text: '包管理器(npm / pip / cargo',
link: '/zh-cn/appendix/2-development-tools/package-managers'
},
{
text: '调试的艺术',
link: '/zh-cn/appendix/2-development-tools/debugging-art/'
},
{
text: '正则表达式',
link: '/zh-cn/appendix/2-development-tools/regex'
}
]
},
{
text: '三、浏览器与前端',
collapsed: false,
items: [
{ text: 'JavaScript 语言深入', link: '/zh-cn/appendix/3-browser-and-frontend/javascript-deep-dive' },
{ text: 'TypeScript:给 JS 加上类型系统', link: '/zh-cn/appendix/3-browser-and-frontend/typescript' },
{ text: '前端框架对比(React / Vue / Svelte / Angular', link: '/zh-cn/appendix/3-browser-and-frontend/frontend-frameworks' },
{ text: '浏览器是一个操作系统', link: '/zh-cn/appendix/3-browser-and-frontend/browser-as-os' },
{ text: '浏览器渲染管道', link: '/zh-cn/appendix/3-browser-and-frontend/browser-as-os-rendering' },
{ text: 'HTML / CSS 布局体系', link: '/zh-cn/appendix/3-browser-and-frontend/html-css-layout' },
{ text: 'JavaScript 运行时', link: '/zh-cn/appendix/3-browser-and-frontend/javascript-runtime' },
{ text: '前端框架的本质', link: '/zh-cn/appendix/3-browser-and-frontend/frontend-framework-nature' },
{ text: '状态管理哲学', link: '/zh-cn/appendix/3-browser-and-frontend/state-management' },
{ text: '路由与导航', link: '/zh-cn/appendix/3-browser-and-frontend/routing-navigation' },
{ text: '图形与动画(Canvas / SVG / WebGL', link: '/zh-cn/appendix/3-browser-and-frontend/graphics-animation' },
{ text: '实时通信(WebSocket / SSE', link: '/zh-cn/appendix/3-browser-and-frontend/realtime-communication' },
{ text: '网页性能的度量与优化', link: '/zh-cn/appendix/3-browser-and-frontend/web-performance' },
{ text: '前端工程化全貌', link: '/zh-cn/appendix/3-browser-and-frontend/frontend-engineering' },
{ text: '无障碍与国际化', link: '/zh-cn/appendix/3-browser-and-frontend/a11n-i18n' }
{
text: 'JavaScript 语言深入',
link: '/zh-cn/appendix/3-browser-and-frontend/javascript-deep-dive'
},
{
text: 'TypeScript:给 JS 加上类型系统',
link: '/zh-cn/appendix/3-browser-and-frontend/typescript'
},
{
text: '前端框架对比(React / Vue / Svelte / Angular',
link: '/zh-cn/appendix/3-browser-and-frontend/frontend-frameworks'
},
{
text: '浏览器是一个操作系统',
link: '/zh-cn/appendix/3-browser-and-frontend/browser-as-os'
},
{
text: '浏览器渲染管道',
link: '/zh-cn/appendix/3-browser-and-frontend/browser-as-os-rendering'
},
{
text: 'HTML / CSS 布局体系',
link: '/zh-cn/appendix/3-browser-and-frontend/html-css-layout'
},
{
text: 'JavaScript 运行时',
link: '/zh-cn/appendix/3-browser-and-frontend/javascript-runtime'
},
{
text: '前端框架的本质',
link: '/zh-cn/appendix/3-browser-and-frontend/frontend-framework-nature'
},
{
text: '状态管理哲学',
link: '/zh-cn/appendix/3-browser-and-frontend/state-management'
},
{
text: '路由与导航',
link: '/zh-cn/appendix/3-browser-and-frontend/routing-navigation'
},
{
text: '图形与动画(Canvas / SVG / WebGL',
link: '/zh-cn/appendix/3-browser-and-frontend/graphics-animation'
},
{
text: '实时通信(WebSocket / SSE',
link: '/zh-cn/appendix/3-browser-and-frontend/realtime-communication'
},
{
text: '网页性能的度量与优化',
link: '/zh-cn/appendix/3-browser-and-frontend/web-performance'
},
{
text: '前端工程化全貌',
link: '/zh-cn/appendix/3-browser-and-frontend/frontend-engineering'
},
{
text: '无障碍与国际化',
link: '/zh-cn/appendix/3-browser-and-frontend/a11n-i18n'
}
]
},
{
text: '四、服务器与后端',
collapsed: false,
items: [
{ text: '后端语言对比(Node.js / Go / Java / Rust', link: '/zh-cn/appendix/4-server-and-backend/backend-languages' },
{ text: '客户端语言对比(Swift / Kotlin / Dart', link: '/zh-cn/appendix/4-server-and-backend/client-languages' },
{ text: '跨平台方案对比(React Native / Flutter / Electron / Tauri', link: '/zh-cn/appendix/4-server-and-backend/cross-platform' },
{ text: 'HTTP 协议', link: '/zh-cn/appendix/4-server-and-backend/http-protocol' },
{ text: '一个请求的完整旅程', link: '/zh-cn/appendix/4-server-and-backend/request-journey' },
{ text: 'Web 框架的本质', link: '/zh-cn/appendix/4-server-and-backend/web-frameworks' },
{ text: 'API 入门', link: '/zh-cn/appendix/4-server-and-backend/api-intro' },
{ text: 'API 设计哲学(REST / GraphQL / gRPC', link: '/zh-cn/appendix/4-server-and-backend/api-design' },
{ text: '序列化与数据格式', link: '/zh-cn/appendix/4-server-and-backend/serialization' },
{ text: '认证与授权体系', link: '/zh-cn/appendix/4-server-and-backend/auth-authorization' },
{ text: '并发、异步与多线程', link: '/zh-cn/appendix/4-server-and-backend/concurrency-async' },
{ text: '缓存的层次与策略', link: '/zh-cn/appendix/4-server-and-backend/caching' },
{ text: '消息队列与事件驱动', link: '/zh-cn/appendix/4-server-and-backend/message-queues' },
{ text: '异步任务队列与生产消费模型', link: '/zh-cn/appendix/4-server-and-backend/async-task-queues' },
{ text: '限流与背压控制', link: '/zh-cn/appendix/4-server-and-backend/rate-limiting-backpressure' },
{ text: '搜索引擎原理', link: '/zh-cn/appendix/4-server-and-backend/search-engines' },
{ text: '文件存储与对象存储', link: '/zh-cn/appendix/4-server-and-backend/file-storage' },
{ text: '后端分层架构', link: '/zh-cn/appendix/4-server-and-backend/backend-layered-architecture' }
{
text: '端语言对比(Node.js / Go / Java / Rust',
link: '/zh-cn/appendix/4-server-and-backend/backend-languages'
},
{
text: '客户端语言对比(Swift / Kotlin / Dart',
link: '/zh-cn/appendix/4-server-and-backend/client-languages'
},
{
text: '跨平台方案对比(React Native / Flutter / Electron / Tauri',
link: '/zh-cn/appendix/4-server-and-backend/cross-platform'
},
{
text: 'HTTP 协议',
link: '/zh-cn/appendix/4-server-and-backend/http-protocol'
},
{
text: '一个请求的完整旅程',
link: '/zh-cn/appendix/4-server-and-backend/request-journey'
},
{
text: 'Web 框架的本质',
link: '/zh-cn/appendix/4-server-and-backend/web-frameworks'
},
{
text: 'API 入门',
link: '/zh-cn/appendix/4-server-and-backend/api-intro'
},
{
text: 'API 设计哲学(REST / GraphQL / gRPC',
link: '/zh-cn/appendix/4-server-and-backend/api-design'
},
{
text: '序列化与数据格式',
link: '/zh-cn/appendix/4-server-and-backend/serialization'
},
{
text: '认证与授权体系',
link: '/zh-cn/appendix/4-server-and-backend/auth-authorization'
},
{
text: '并发、异步与多线程',
link: '/zh-cn/appendix/4-server-and-backend/concurrency-async'
},
{
text: '缓存的层次与策略',
link: '/zh-cn/appendix/4-server-and-backend/caching'
},
{
text: '消息队列与事件驱动',
link: '/zh-cn/appendix/4-server-and-backend/message-queues'
},
{
text: '异步任务队列与生产消费模型',
link: '/zh-cn/appendix/4-server-and-backend/async-task-queues'
},
{
text: '限流与背压控制',
link: '/zh-cn/appendix/4-server-and-backend/rate-limiting-backpressure'
},
{
text: '搜索引擎原理',
link: '/zh-cn/appendix/4-server-and-backend/search-engines'
},
{
text: '文件存储与对象存储',
link: '/zh-cn/appendix/4-server-and-backend/file-storage'
},
{
text: '后端分层架构',
link: '/zh-cn/appendix/4-server-and-backend/backend-layered-architecture'
}
]
},
{
@@ -648,77 +798,218 @@ export default defineConfig({
collapsed: false,
items: [
{ text: 'SQL', link: '/zh-cn/appendix/5-data/sql' },
{ text: '数据库原理(索引 / 事务 / 查询优化)', link: '/zh-cn/appendix/5-data/database-fundamentals' },
{ text: '数据模型全景(文档 / 图 / 时序 / 向量)', link: '/zh-cn/appendix/5-data/data-models' },
{ text: '数据埋点与用户行为采集', link: '/zh-cn/appendix/5-data/data-tracking' },
{ text: '数据分析基础(统计 / 指标 / 漏斗)', link: '/zh-cn/appendix/5-data/data-analysis' },
{ text: 'A/B 测试与实验驱动', link: '/zh-cn/appendix/5-data/ab-testing' },
{ text: '数据可视化与仪表盘', link: '/zh-cn/appendix/5-data/data-visualization' },
{ text: '数据治理与数据质量', link: '/zh-cn/appendix/5-data/data-governance' }
{
text: '数据库原理(索引 / 事务 / 查询优化)',
link: '/zh-cn/appendix/5-data/database-fundamentals'
},
{
text: '数据模型全景(文档 / 图 / 时序 / 向量)',
link: '/zh-cn/appendix/5-data/data-models'
},
{
text: '数据埋点与用户行为采集',
link: '/zh-cn/appendix/5-data/data-tracking'
},
{
text: '数据分析基础(统计 / 指标 / 漏斗)',
link: '/zh-cn/appendix/5-data/data-analysis'
},
{
text: 'A/B 测试与实验驱动',
link: '/zh-cn/appendix/5-data/ab-testing'
},
{
text: '数据可视化与仪表盘',
link: '/zh-cn/appendix/5-data/data-visualization'
},
{
text: '数据治理与数据质量',
link: '/zh-cn/appendix/5-data/data-governance'
}
]
},
{
text: '六、架构与系统设计',
collapsed: false,
items: [
{ text: '从单体到微服务的演进', link: '/zh-cn/appendix/6-architecture-and-system-design/monolith-to-microservices' },
{ text: '分布式系统的挑战', link: '/zh-cn/appendix/6-architecture-and-system-design/distributed-systems' },
{ text: '高可用与容灾', link: '/zh-cn/appendix/6-architecture-and-system-design/high-availability' },
{ text: '系统设计方法论', link: '/zh-cn/appendix/6-architecture-and-system-design/system-design-methodology' }
{
text: '从单体到微服务的演进',
link: '/zh-cn/appendix/6-architecture-and-system-design/monolith-to-microservices'
},
{
text: '分布式系统的挑战',
link: '/zh-cn/appendix/6-architecture-and-system-design/distributed-systems'
},
{
text: '高可用与容灾',
link: '/zh-cn/appendix/6-architecture-and-system-design/high-availability'
},
{
text: '系统设计方法论',
link: '/zh-cn/appendix/6-architecture-and-system-design/system-design-methodology'
}
]
},
{
text: '七、基础设施与运维',
collapsed: false,
items: [
{ text: 'Linux 基础', link: '/zh-cn/appendix/7-infrastructure-and-operations/linux-basics' },
{ text: 'Docker 容器化', link: '/zh-cn/appendix/7-infrastructure-and-operations/docker-containers' },
{ text: 'Kubernetes 编排', link: '/zh-cn/appendix/7-infrastructure-and-operations/kubernetes' },
{ text: 'CI / CD 自动化', link: '/zh-cn/appendix/7-infrastructure-and-operations/ci-cd' },
{ text: '域名、DNS 与 HTTPS', link: '/zh-cn/appendix/7-infrastructure-and-operations/dns-https' },
{ text: '负载均衡与网关', link: '/zh-cn/appendix/7-infrastructure-and-operations/load-balancing-gateway' },
{ text: '网关与反向代理', link: '/zh-cn/appendix/7-infrastructure-and-operations/gateway-proxy' },
{ text: '云平台实战', link: '/zh-cn/appendix/7-infrastructure-and-operations/cloud-platforms' },
{ text: 'IAM 权限管理', link: '/zh-cn/appendix/7-infrastructure-and-operations/cloud-iam' },
{ text: '对象存储与 CDN', link: '/zh-cn/appendix/7-infrastructure-and-operations/cloud-storage-cdn' },
{ text: '基础设施即代码', link: '/zh-cn/appendix/7-infrastructure-and-operations/infrastructure-as-code' },
{ text: '监控、日志与告警', link: '/zh-cn/appendix/7-infrastructure-and-operations/monitoring-logging' },
{ text: '故障排查与应急响应', link: '/zh-cn/appendix/7-infrastructure-and-operations/incident-response' }
{
text: 'Linux 基础',
link: '/zh-cn/appendix/7-infrastructure-and-operations/linux-basics'
},
{
text: 'Docker 容器化',
link: '/zh-cn/appendix/7-infrastructure-and-operations/docker-containers'
},
{
text: 'Kubernetes 编排',
link: '/zh-cn/appendix/7-infrastructure-and-operations/kubernetes'
},
{
text: 'CI / CD 自动化',
link: '/zh-cn/appendix/7-infrastructure-and-operations/ci-cd'
},
{
text: '域名、DNS 与 HTTPS',
link: '/zh-cn/appendix/7-infrastructure-and-operations/dns-https'
},
{
text: '负载均衡与网关',
link: '/zh-cn/appendix/7-infrastructure-and-operations/load-balancing-gateway'
},
{
text: '网关与反向代理',
link: '/zh-cn/appendix/7-infrastructure-and-operations/gateway-proxy'
},
{
text: '云平台实战',
link: '/zh-cn/appendix/7-infrastructure-and-operations/cloud-platforms'
},
{
text: 'IAM 权限管理',
link: '/zh-cn/appendix/7-infrastructure-and-operations/cloud-iam'
},
{
text: '对象存储与 CDN',
link: '/zh-cn/appendix/7-infrastructure-and-operations/cloud-storage-cdn'
},
{
text: '基础设施即代码',
link: '/zh-cn/appendix/7-infrastructure-and-operations/infrastructure-as-code'
},
{
text: '监控、日志与告警',
link: '/zh-cn/appendix/7-infrastructure-and-operations/monitoring-logging'
},
{
text: '故障排查与应急响应',
link: '/zh-cn/appendix/7-infrastructure-and-operations/incident-response'
}
]
},
{
text: '八、人工智能',
collapsed: false,
items: [
{ text: 'AI 简史与核心概念', link: '/zh-cn/appendix/8-artificial-intelligence/ai-history' },
{ text: '神经网络与深度学习', link: '/zh-cn/appendix/8-artificial-intelligence/neural-networks' },
{ text: 'Transformer 与注意力机制', link: '/zh-cn/appendix/8-artificial-intelligence/transformer-attention' },
{ text: '大语言模型的工作原理', link: '/zh-cn/appendix/8-artificial-intelligence/llm-principles' },
{ text: '提示词工程', link: '/zh-cn/appendix/8-artificial-intelligence/prompt-engineering' },
{ text: '上下文工程', link: '/zh-cn/appendix/8-artificial-intelligence/context-engineering' },
{ text: '多模态模型(视觉 / 音频 / 视频)', link: '/zh-cn/appendix/8-artificial-intelligence/multimodal-models' },
{ text: '图像生成原理', link: '/zh-cn/appendix/8-artificial-intelligence/image-generation' },
{ text: '语音合成与识别', link: '/zh-cn/appendix/8-artificial-intelligence/speech-synthesis-recognition' },
{ text: 'Embedding 与向量检索', link: '/zh-cn/appendix/8-artificial-intelligence/embedding-vector-retrieval' },
{ text: 'RAG 架构', link: '/zh-cn/appendix/8-artificial-intelligence/rag' },
{ text: 'AI Agent 与工具调用', link: '/zh-cn/appendix/8-artificial-intelligence/ai-agents' },
{ text: 'AI 协议(MCP & A2A', link: '/zh-cn/appendix/8-artificial-intelligence/ai-protocols' },
{ text: '模型微调与部署', link: '/zh-cn/appendix/8-artificial-intelligence/model-finetuning-deployment' },
{ text: 'AI 原生应用设计', link: '/zh-cn/appendix/8-artificial-intelligence/ai-native-app-design' },
{ text: 'AI 能力词典', link: '/zh-cn/appendix/8-artificial-intelligence/ai-capability-dictionary' }
{
text: 'AI 简史与核心概念',
link: '/zh-cn/appendix/8-artificial-intelligence/ai-history'
},
{
text: '神经网络与深度学习',
link: '/zh-cn/appendix/8-artificial-intelligence/neural-networks'
},
{
text: 'Transformer 与注意力机制',
link: '/zh-cn/appendix/8-artificial-intelligence/transformer-attention'
},
{
text: '大语言模型的工作原理',
link: '/zh-cn/appendix/8-artificial-intelligence/llm-principles'
},
{
text: '提示词工程',
link: '/zh-cn/appendix/8-artificial-intelligence/prompt-engineering'
},
{
text: '上下文工程',
link: '/zh-cn/appendix/8-artificial-intelligence/context-engineering'
},
{
text: '多模态模型(视觉 / 音频 / 视频)',
link: '/zh-cn/appendix/8-artificial-intelligence/multimodal-models'
},
{
text: '图像生成原理',
link: '/zh-cn/appendix/8-artificial-intelligence/image-generation'
},
{
text: '语音合成与识别',
link: '/zh-cn/appendix/8-artificial-intelligence/speech-synthesis-recognition'
},
{
text: 'Embedding 与向量检索',
link: '/zh-cn/appendix/8-artificial-intelligence/embedding-vector-retrieval'
},
{
text: 'RAG 架构',
link: '/zh-cn/appendix/8-artificial-intelligence/rag'
},
{
text: 'AI Agent 与工具调用',
link: '/zh-cn/appendix/8-artificial-intelligence/ai-agents'
},
{
text: 'AI 协议(MCP & A2A',
link: '/zh-cn/appendix/8-artificial-intelligence/ai-protocols'
},
{
text: '模型微调与部署',
link: '/zh-cn/appendix/8-artificial-intelligence/model-finetuning-deployment'
},
{
text: 'AI 原生应用设计',
link: '/zh-cn/appendix/8-artificial-intelligence/ai-native-app-design'
},
{
text: 'AI 能力词典',
link: '/zh-cn/appendix/8-artificial-intelligence/ai-capability-dictionary'
}
]
},
{
text: '九、工程素养',
collapsed: false,
items: [
{ text: '代码质量与重构', link: '/zh-cn/appendix/9-engineering-excellence/code-quality-refactoring' },
{ text: '测试策略', link: '/zh-cn/appendix/9-engineering-excellence/testing-strategies' },
{ text: '设计模式', link: '/zh-cn/appendix/9-engineering-excellence/design-patterns' },
{ text: '安全思维与攻防基础', link: '/zh-cn/appendix/9-engineering-excellence/security-thinking' },
{ text: '技术文档写作', link: '/zh-cn/appendix/9-engineering-excellence/technical-writing' },
{ text: '开源协作', link: '/zh-cn/appendix/9-engineering-excellence/open-source-collaboration' },
{ text: '技术选型方法论', link: '/zh-cn/appendix/9-engineering-excellence/technology-selection' }
{
text: '代码质量与重构',
link: '/zh-cn/appendix/9-engineering-excellence/code-quality-refactoring'
},
{
text: '测试策略',
link: '/zh-cn/appendix/9-engineering-excellence/testing-strategies'
},
{
text: '设计模式',
link: '/zh-cn/appendix/9-engineering-excellence/design-patterns'
},
{
text: '安全思维与攻防基础',
link: '/zh-cn/appendix/9-engineering-excellence/security-thinking'
},
{
text: '技术文档写作',
link: '/zh-cn/appendix/9-engineering-excellence/technical-writing'
},
{
text: '开源协作',
link: '/zh-cn/appendix/9-engineering-excellence/open-source-collaboration'
},
{
text: '技术选型方法论',
link: '/zh-cn/appendix/9-engineering-excellence/technology-selection'
}
]
}
]
@@ -0,0 +1,479 @@
<template>
<div class="demo">
<div class="header">
<span class="icon">🎨</span>
<span class="title">四种 API 风格对比</span>
</div>
<div class="tabs">
<button
v-for="style in styles"
:key="style.id"
:class="['tab', { active: active === style.id }]"
@click="active = style.id"
>
{{ style.icon }} {{ style.name }}
</button>
</div>
<div class="content">
<div class="style-header">
<h4>{{ currentStyle.name }}</h4>
<span class="badge">{{ currentStyle.badge }}</span>
</div>
<p class="desc">{{ currentStyle.desc }}</p>
<div class="example-section">
<div class="example-label">示例获取用户信息</div>
<pre class="code-block"><code>{{ currentStyle.example }}</code></pre>
</div>
<div class="features">
<div class="features-title">核心特点</div>
<div class="features-grid">
<div
v-for="(f, i) in currentStyle.features"
:key="i"
class="feature-item"
>
<span class="check"></span>
<span>{{ f }}</span>
</div>
</div>
</div>
<div class="meta">
<div class="meta-row">
<span class="meta-label">适用场景</span>
<span class="meta-value">{{ currentStyle.scenarios }}</span>
</div>
<div class="meta-row">
<span class="meta-label">官方地址</span>
<a :href="currentStyle.official" target="_blank" class="meta-link">{{
currentStyle.official
}}</a>
</div>
</div>
</div>
<div class="compare-section">
<div class="compare-title">📊 风格速览对比</div>
<div class="compare-table">
<div class="compare-row head">
<div class="cell">特性</div>
<div class="cell">RPC</div>
<div class="cell highlight">REST</div>
<div class="cell">GraphQL</div>
<div class="cell">gRPC</div>
</div>
<div class="compare-row">
<div class="cell">核心理念</div>
<div class="cell">面向过程</div>
<div class="cell highlight">面向资源</div>
<div class="cell">面向数据</div>
<div class="cell">面向方法</div>
</div>
<div class="compare-row">
<div class="cell">URL 风格</div>
<div class="cell">动词为主</div>
<div class="cell highlight">名词为主</div>
<div class="cell">单一端点</div>
<div class="cell">不依赖URL</div>
</div>
<div class="compare-row">
<div class="cell">学习曲线</div>
<div class="cell low"></div>
<div class="cell"></div>
<div class="cell"></div>
<div class="cell high"></div>
</div>
<div class="compare-row">
<div class="cell">性能</div>
<div class="cell">一般</div>
<div class="cell">一般</div>
<div class="cell">较好</div>
<div class="cell best">优秀</div>
</div>
<div class="compare-row">
<div class="cell">使用占比</div>
<div class="cell">~30%</div>
<div class="cell highlight">~50%</div>
<div class="cell">~15%</div>
<div class="cell">~5%</div>
</div>
</div>
</div>
</div>
</template>
<script setup>
import { ref, computed } from 'vue'
const active = ref('rest')
const styles = [
{
id: 'rpc',
icon: '📞',
name: 'RPC',
badge: '最传统',
desc: 'Remote Procedure Call,远程过程调用。像调用本地方法一样调用远程服务,面向过程,简单直接。超过 50% 的内部 API 采用这种风格。',
example: `GET /getUserInfo?id=123
POST /createUser
POST /deleteOrder
GET /queryUserList`,
features: [
'URL 命名往往是动词',
'HTTP 方法基本只用 GET/POST',
'设计简单,几乎无约束',
'需要详细文档说明'
],
scenarios: '内部 API、性能敏感场景、难以抽象为资源的业务',
official: '无官方规范(概念性风格)'
},
{
id: 'rest',
icon: '🌐',
name: 'REST',
badge: '最常用',
desc: 'Representational State Transfer,表述性状态转移。由 Roy Fielding 于 2000 年在其博士论文中提出。面向资源,用 URL 标识资源,用 HTTP 方法操作资源。',
example: `GET /users # 获取用户列表
GET /users/123 # 获取单个用户
POST /users # 创建用户
PUT /users/123 # 全量更新
PATCH /users/123 # 部分更新
DELETE /users/123 # 删除用户`,
features: [
'URL 是名词,不是动词',
'使用 HTTP 方法表达操作',
'无状态,请求包含所有信息',
'可缓存,支持分层系统'
],
scenarios: '公开 API、CRUD 操作、资源边界清晰的业务',
official: 'https://restfulapi.net/'
},
{
id: 'graphql',
icon: '📊',
name: 'GraphQL',
badge: '最灵活',
desc: '由 Facebook 于 2015 年开源。一种查询语言,客户端可以精确指定需要的数据字段,避免过度获取或获取不足。',
example: `query {
user(id: "123") {
name
email
orders {
id
total
}
}
}`,
features: [
'单一端点(/graphql',
'客户端决定返回字段',
'Schema 即文档',
'一次请求获取多资源'
],
scenarios: '客户端需求多变、数据关系复杂、移动端 App',
official: 'https://graphql.org/'
},
{
id: 'grpc',
icon: '⚡',
name: 'gRPC',
badge: '最高效',
desc: '由 Google 于 2016 年开源。高性能 RPC 框架,使用 Protocol Buffers 序列化,基于 HTTP/2,支持双向流通信。',
example: `service UserService {
rpc GetUser(GetUserRequest) returns (User);
rpc CreateUser(CreateUserRequest) returns (User);
}
message User {
string id = 1;
string name = 2;
}`,
features: [
'二进制传输,性能极高',
'强类型,代码自动生成',
'基于 HTTP/2,双向流',
'浏览器支持差'
],
scenarios: '微服务内部通信、高性能场景、强类型需求',
official: 'https://grpc.io/'
}
]
const currentStyle = computed(() => {
return styles.find((s) => s.id === active.value) || styles[1]
})
</script>
<style scoped>
.demo {
border: 1px solid var(--vp-c-divider);
border-radius: 12px;
background: var(--vp-c-bg-soft);
margin: 24px 0;
overflow: hidden;
}
.header {
padding: 14px 20px;
background: var(--vp-c-bg);
border-bottom: 1px solid var(--vp-c-divider);
display: flex;
align-items: center;
gap: 10px;
}
.icon {
font-size: 20px;
}
.title {
font-weight: 600;
font-size: 15px;
}
.tabs {
display: flex;
gap: 6px;
padding: 12px 16px;
background: var(--vp-c-bg);
border-bottom: 1px solid var(--vp-c-divider);
overflow-x: auto;
}
.tab {
padding: 8px 14px;
border: 1px solid var(--vp-c-divider);
border-radius: 6px;
background: var(--vp-c-bg-soft);
font-size: 13px;
font-weight: 500;
cursor: pointer;
white-space: nowrap;
transition: all 0.2s;
}
.tab:hover {
border-color: var(--vp-c-brand);
}
.tab.active {
background: var(--vp-c-brand);
border-color: var(--vp-c-brand);
color: white;
}
.content {
padding: 20px;
}
.style-header {
display: flex;
align-items: center;
gap: 10px;
margin-bottom: 10px;
}
.style-header h4 {
margin: 0;
font-size: 18px;
}
.badge {
padding: 3px 8px;
border-radius: 4px;
font-size: 11px;
font-weight: 600;
background: color-mix(in srgb, var(--vp-c-brand) 15%, transparent);
color: var(--vp-c-brand);
}
.desc {
font-size: 14px;
color: var(--vp-c-text-2);
line-height: 1.6;
margin: 0 0 16px 0;
}
.example-section {
margin-bottom: 16px;
}
.example-label {
font-size: 12px;
color: var(--vp-c-text-3);
margin-bottom: 8px;
}
.code-block {
background: #1e293b;
color: #e2e8f0;
padding: 14px;
border-radius: 8px;
font-family: 'Menlo', 'Monaco', monospace;
font-size: 12px;
line-height: 1.6;
overflow-x: auto;
margin: 0;
}
.features {
margin-bottom: 16px;
}
.features-title {
font-size: 13px;
font-weight: 600;
margin-bottom: 10px;
}
.features-grid {
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 8px;
}
@media (max-width: 640px) {
.features-grid {
grid-template-columns: 1fr;
}
}
.feature-item {
display: flex;
align-items: flex-start;
gap: 8px;
font-size: 13px;
color: var(--vp-c-text-2);
}
.check {
color: var(--vp-c-brand);
font-weight: bold;
}
.meta {
padding-top: 14px;
border-top: 1px solid var(--vp-c-divider);
}
.meta-row {
display: flex;
align-items: flex-start;
gap: 10px;
margin-bottom: 8px;
font-size: 13px;
}
.meta-label {
color: var(--vp-c-text-3);
min-width: 70px;
flex-shrink: 0;
}
.meta-value {
color: var(--vp-c-text-2);
}
.meta-link {
color: var(--vp-c-brand);
text-decoration: none;
word-break: break-all;
}
.meta-link:hover {
text-decoration: underline;
}
.compare-section {
background: var(--vp-c-bg);
border-top: 1px solid var(--vp-c-divider);
padding: 16px 20px;
}
.compare-title {
font-size: 14px;
font-weight: 600;
margin-bottom: 12px;
}
.compare-table {
border: 1px solid var(--vp-c-divider);
border-radius: 8px;
overflow: hidden;
}
.compare-row {
display: grid;
grid-template-columns: 1fr repeat(4, 1fr);
}
.compare-row:nth-child(odd) {
background: var(--vp-c-bg-soft);
}
.compare-row:nth-child(even) {
background: var(--vp-c-bg);
}
.compare-row.head {
background: var(--vp-c-bg-alt);
}
.cell {
padding: 10px 8px;
font-size: 12px;
color: var(--vp-c-text-2);
text-align: center;
border-right: 1px solid var(--vp-c-divider);
}
.cell:last-child {
border-right: none;
}
.head .cell {
font-weight: 600;
color: var(--vp-c-text-1);
}
.cell:first-child {
text-align: left;
font-weight: 500;
color: var(--vp-c-text-1);
padding-left: 12px;
}
.cell.highlight {
background: color-mix(in srgb, var(--vp-c-brand) 10%, transparent);
color: var(--vp-c-brand);
font-weight: 600;
}
.cell.low {
color: #22c55e;
}
.cell.high {
color: #f59e0b;
}
.cell.best {
color: #22c55e;
font-weight: 600;
}
@media (max-width: 640px) {
.compare-row {
grid-template-columns: 70px repeat(4, 1fr);
}
.cell {
padding: 8px 4px;
font-size: 11px;
}
}
</style>
@@ -0,0 +1,533 @@
<template>
<div class="demo">
<div class="header">
<span class="icon">📦</span>
<span class="title">data 字段设计规范</span>
</div>
<div class="tabs">
<button
v-for="tab in tabs"
:key="tab.id"
:class="['tab', { active: active === tab.id }]"
@click="active = tab.id"
>
{{ tab.icon }} {{ tab.name }}
</button>
</div>
<div class="content">
<div v-if="active === 'structure'" class="section">
<h4>单对象 vs 列表</h4>
<div class="compare-row">
<div class="compare-col">
<div class="compare-title">单对象</div>
<pre class="code-sm">{
"code": 0,
"data": {
"id": 123,
"name": "张三"
}
}</pre>
</div>
<div class="compare-col">
<div class="compare-title">列表</div>
<pre class="code-sm">{
"code": 0,
"data": {
"items": [...],
"pagination": {
"page": 1,
"total": 100
}
}
}</pre>
</div>
</div>
<div class="note">列表数据包裹在 items 数组中分页信息放在 pagination 对象</div>
</div>
<div v-if="active === 'naming'" class="section">
<h4>字段命名规范</h4>
<div class="rule-list">
<div
v-for="rule in namingRules"
:key="rule.name"
class="rule-item"
>
<div class="rule-header">
<span class="rule-icon">{{ rule.icon }}</span>
<span class="rule-name">{{ rule.name }}</span>
</div>
<div class="rule-examples">
<code class="good">{{ rule.good }}</code>
<span
v-if="rule.bad"
class="vs"
>vs</span>
<code
v-if="rule.bad"
class="bad"
>{{ rule.bad }}</code>
</div>
<div class="rule-desc">{{ rule.desc }}</div>
</div>
</div>
</div>
<div v-if="active === 'datetime'" class="section">
<h4>时间格式设计</h4>
<div class="time-example">
<pre class="code-block">{
"created_at": "2024-01-15T09:30:00.000Z",
"updated_at": "2024-01-15T10:00:00.000Z",
"expired_at": "2025-01-15T00:00:00.000Z"
}</pre>
</div>
<div class="time-rules">
<div class="time-rule">
<span class="rule-label">格式</span>
<span class="rule-value">ISO 8601</span>
</div>
<div class="time-rule">
<span class="rule-label">时区</span>
<span class="rule-value">UTCZ 后缀或明确偏移量</span>
</div>
<div class="time-rule">
<span class="rule-label">精度</span>
<span class="rule-value">毫秒 .000Z</span>
</div>
<div class="time-rule">
<span class="rule-label">命名</span>
<span class="rule-value">xxx_at 表示时间点xxx_duration 表示时长</span>
</div>
</div>
</div>
<div v-if="active === 'null'" class="section">
<h4>空值处理</h4>
<div class="compare-row">
<div class="compare-col good-col">
<div class="compare-title"> 推荐</div>
<pre class="code-sm">{
"name": "张三",
"nickname": null,
"avatar": null
}</pre>
<div class="compare-desc">字段存在但无值时返回 null</div>
</div>
<div class="compare-col bad-col">
<div class="compare-title"> 不推荐</div>
<pre class="code-sm">{
"name": "张三"
}</pre>
<div class="compare-desc">省略字段前端需判断是否存在</div>
</div>
</div>
<div class="null-tips">
<div class="tip-item">空数组返回 <code>[]</code></div>
<div class="tip-item">空对象返回 <code>{}</code></div>
<div class="tip-item">前端可统一处理无需判断字段是否存在</div>
</div>
</div>
<div v-if="active === 'relation'" class="section">
<h4>关联数据设计</h4>
<div class="relation-tabs">
<button
v-for="r in relations"
:key="r.id"
:class="['r-tab', { active: rId === r.id }]"
@click="rId = r.id"
>
{{ r.name }}
</button>
</div>
<div class="relation-content">
<div class="relation-desc">{{ currentRelation.desc }}</div>
<pre class="code-block"><code>{{ currentRelation.code }}</code></pre>
</div>
</div>
</div>
<div class="tips">
<span class="tips-icon">💡</span>
<span class="tips-text">参考 ISO 8601 时间标准字段命名保持 snake_case 风格</span>
</div>
</div>
</template>
<script setup>
import { ref, computed } from 'vue'
const active = ref('structure')
const rId = ref('embed')
const tabs = [
{ id: 'structure', icon: '📐', name: '结构设计' },
{ id: 'naming', icon: '📝', name: '命名规范' },
{ id: 'datetime', icon: '🕐', name: '时间格式' },
{ id: 'null', icon: '∅', name: '空值处理' },
{ id: 'relation', icon: '🔗', name: '关联数据' }
]
const namingRules = [
{ icon: '🔡', name: '使用 snake_case', good: 'created_at', bad: 'createdAt', desc: 'JSON 字段名统一用下划线' },
{ icon: '📖', name: '避免缩写', good: 'user_id', bad: 'uid', desc: '保持可读性' },
{ icon: '✅', name: '布尔值加前缀', good: 'is_active, has_permission', bad: 'active, permission', desc: '一眼识别布尔类型' },
{ icon: '📅', name: '时间带后缀', good: 'created_at, expired_at', bad: 'created, expired', desc: '明确是时间字段' },
{ icon: '🔢', name: '数量带后缀', good: 'total_count, page_size', bad: 'total, size', desc: '明确是数值类型' }
]
const relations = [
{
id: 'embed',
name: '内嵌',
desc: '适合数据量小、频繁访问的关联数据',
code: `{
"id": 123,
"name": "张三",
"department": {
"id": 1,
"name": "技术部"
}
}`
},
{
id: 'foreign',
name: '外键',
desc: '适合数据量大、按需加载的关联数据',
code: `{
"id": 123,
"name": "张三",
"department_id": 1
}`
},
{
id: 'expand',
name: 'expand 参数',
desc: 'Stripe 风格,客户端按需展开',
code: `// GET /users/123?expand=department
{
"id": 123,
"name": "张三",
"department": {
"id": 1,
"name": "技术部"
}
}`
}
]
const currentRelation = computed(() => {
return relations.find(r => r.id === rId.value) || relations[0]
})
</script>
<style scoped>
.demo {
border: 1px solid var(--vp-c-divider);
border-radius: 12px;
background: var(--vp-c-bg-soft);
margin: 24px 0;
overflow: hidden;
}
.header {
padding: 14px 20px;
background: var(--vp-c-bg);
border-bottom: 1px solid var(--vp-c-divider);
display: flex;
align-items: center;
gap: 10px;
}
.icon {
font-size: 20px;
}
.title {
font-weight: 600;
font-size: 15px;
}
.tabs {
display: flex;
gap: 4px;
padding: 10px 12px;
background: var(--vp-c-bg);
border-bottom: 1px solid var(--vp-c-divider);
overflow-x: auto;
}
.tab {
padding: 6px 12px;
border: 1px solid var(--vp-c-divider);
border-radius: 6px;
background: var(--vp-c-bg-soft);
font-size: 12px;
font-weight: 500;
cursor: pointer;
white-space: nowrap;
transition: all 0.2s;
}
.tab:hover {
border-color: var(--vp-c-brand);
}
.tab.active {
background: var(--vp-c-brand);
border-color: var(--vp-c-brand);
color: white;
}
.content {
padding: 16px;
}
.section h4 {
margin: 0 0 12px 0;
font-size: 14px;
}
.compare-row {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 12px;
}
@media (max-width: 640px) {
.compare-row {
grid-template-columns: 1fr;
}
}
.compare-col {
padding: 12px;
border-radius: 8px;
background: var(--vp-c-bg);
border: 1px solid var(--vp-c-divider);
}
.compare-col.good-col {
border-color: color-mix(in srgb, #22c55e 30%, transparent);
background: color-mix(in srgb, #22c55e 5%, var(--vp-c-bg));
}
.compare-col.bad-col {
border-color: color-mix(in srgb, #ef4444 30%, transparent);
background: color-mix(in srgb, #ef4444 5%, var(--vp-c-bg));
}
.compare-title {
font-size: 12px;
font-weight: 600;
margin-bottom: 8px;
}
.compare-desc {
font-size: 11px;
color: var(--vp-c-text-3);
margin-top: 8px;
}
.code-sm {
background: #1e293b;
color: #e2e8f0;
padding: 10px;
border-radius: 6px;
font-family: 'Menlo', monospace;
font-size: 11px;
line-height: 1.5;
overflow-x: auto;
margin: 0;
}
.code-block {
background: #1e293b;
color: #e2e8f0;
padding: 12px;
border-radius: 6px;
font-family: 'Menlo', monospace;
font-size: 11px;
line-height: 1.5;
overflow-x: auto;
margin: 0;
}
.note {
font-size: 12px;
color: var(--vp-c-text-2);
margin-top: 12px;
padding: 8px 12px;
background: var(--vp-c-bg);
border-radius: 6px;
}
.rule-list {
display: flex;
flex-direction: column;
gap: 10px;
}
.rule-item {
padding: 12px;
background: var(--vp-c-bg);
border: 1px solid var(--vp-c-divider);
border-radius: 8px;
}
.rule-header {
display: flex;
align-items: center;
gap: 8px;
margin-bottom: 8px;
}
.rule-icon {
font-size: 16px;
}
.rule-name {
font-size: 13px;
font-weight: 600;
}
.rule-examples {
display: flex;
align-items: center;
gap: 8px;
margin-bottom: 6px;
}
.rule-examples code {
padding: 3px 8px;
border-radius: 4px;
font-size: 12px;
}
.rule-examples .good {
background: color-mix(in srgb, #22c55e 15%, transparent);
color: #16a34a;
}
.rule-examples .bad {
background: color-mix(in srgb, #ef4444 15%, transparent);
color: #dc2626;
text-decoration: line-through;
}
.rule-examples .vs {
font-size: 10px;
color: var(--vp-c-text-3);
}
.rule-desc {
font-size: 11px;
color: var(--vp-c-text-3);
}
.time-example {
margin-bottom: 12px;
}
.time-rules {
display: flex;
flex-direction: column;
gap: 6px;
}
.time-rule {
display: flex;
align-items: center;
gap: 12px;
padding: 8px 12px;
background: var(--vp-c-bg);
border-radius: 6px;
}
.rule-label {
font-size: 12px;
font-weight: 600;
color: var(--vp-c-text-2);
min-width: 40px;
}
.rule-value {
font-size: 12px;
color: var(--vp-c-text-1);
}
.null-tips {
margin-top: 12px;
display: flex;
flex-direction: column;
gap: 6px;
}
.tip-item {
font-size: 12px;
color: var(--vp-c-text-2);
padding: 6px 10px;
background: var(--vp-c-bg);
border-radius: 4px;
}
.tip-item code {
background: var(--vp-c-bg-soft);
padding: 1px 5px;
border-radius: 3px;
font-size: 11px;
}
.relation-tabs {
display: flex;
gap: 4px;
margin-bottom: 12px;
}
.r-tab {
padding: 6px 12px;
border: 1px solid var(--vp-c-divider);
border-radius: 4px;
background: var(--vp-c-bg);
font-size: 12px;
cursor: pointer;
transition: all 0.2s;
}
.r-tab:hover {
border-color: var(--vp-c-brand);
}
.r-tab.active {
background: var(--vp-c-brand);
border-color: var(--vp-c-brand);
color: white;
}
.relation-desc {
font-size: 12px;
color: var(--vp-c-text-2);
margin-bottom: 10px;
}
.tips {
display: flex;
align-items: center;
gap: 8px;
padding: 10px 16px;
background: var(--vp-c-bg);
border-top: 1px solid var(--vp-c-divider);
}
.tips-icon {
font-size: 14px;
}
.tips-text {
font-size: 12px;
color: var(--vp-c-text-2);
}
</style>
@@ -0,0 +1,575 @@
<template>
<div class="demo">
<div class="header">
<span class="icon"></span>
<span class="title">错误响应设计进阶</span>
</div>
<div class="tabs">
<button
v-for="tab in tabs"
:key="tab.id"
:class="['tab', { active: active === tab.id }]"
@click="active = tab.id"
>
{{ tab.icon }} {{ tab.name }}
</button>
</div>
<div class="content">
<div v-if="active === 'validate'" class="section">
<h4>参数校验错误</h4>
<pre class="code-block">{
"code": 10001,
"message": "参数校验失败",
"data": {
"errors": [
{
"field": "email",
"message": "邮箱格式不正确",
"value": "invalid-email"
},
{
"field": "password",
"message": "密码长度至少 8 位",
"value": "123"
}
]
}
}</pre>
<div class="field-tips">
<div class="tip-row">
<code>field</code>
<span>出错字段名前端可定位表单</span>
</div>
<div class="tip-row">
<code>message</code>
<span>用户友好的错误描述</span>
</div>
<div class="tip-row">
<code>value</code>
<span>客户端提交的值可选</span>
</div>
</div>
</div>
<div v-if="active === 'business'" class="section">
<h4>业务错误</h4>
<pre class="code-block">{
"code": 20001,
"message": "余额不足",
"data": {
"current_balance": 50.00,
"required_amount": 99.00,
"shortfall": 49.00,
"suggestion": "请充值后重试"
}
}</pre>
<div class="business-tips">
<div class="b-tip"> 返回当前状态数据便于前端展示</div>
<div class="b-tip"> 提供 suggestion 给出解决建议</div>
<div class="b-tip"> 数据结构化前端可灵活展示</div>
</div>
</div>
<div v-if="active === 'layers'" class="section">
<h4>错误码分层设计</h4>
<div class="layer-list">
<div
v-for="layer in layers"
:key="layer.range"
class="layer-item"
>
<div class="layer-range">{{ layer.range }}</div>
<div class="layer-info">
<div class="layer-name">{{ layer.name }}</div>
<div class="layer-example">示例{{ layer.example }}</div>
</div>
<div class="layer-desc">{{ layer.desc }}</div>
</div>
</div>
<div class="layer-note">错误码从外到内系统 服务 业务 认证 参数</div>
</div>
<div v-if="active === 'http'" class="section">
<h4>HTTP 状态码 vs 业务状态码</h4>
<div class="http-compare">
<div class="http-col">
<div class="http-title">HTTP 状态码</div>
<div class="http-desc">传输层状态</div>
<div class="http-codes">
<div class="http-code">
<span class="code-num">2xx</span>
<span>请求成功</span>
</div>
<div class="http-code">
<span class="code-num">4xx</span>
<span>客户端错误</span>
</div>
<div class="http-code">
<span class="code-num">5xx</span>
<span>服务端错误</span>
</div>
</div>
</div>
<div class="http-arrow"></div>
<div class="http-col">
<div class="http-title">业务状态码</div>
<div class="http-desc">业务层状态</div>
<div class="http-codes">
<div class="http-code">
<span class="code-num">0</span>
<span>业务成功</span>
</div>
<div class="http-code">
<span class="code-num">1xxxx</span>
<span>参数错误</span>
</div>
<div class="http-code">
<span class="code-num">2xxxx</span>
<span>业务错误</span>
</div>
</div>
</div>
</div>
<div class="http-note">HTTP 200 + 业务错误码 是业界主流做法</div>
</div>
<div v-if="active === 'examples'" class="section">
<h4>常见错误码示例</h4>
<div class="ex-tabs">
<button
v-for="ex in examples"
:key="ex.id"
:class="['ex-tab', { active: exId === ex.id }]"
@click="exId = ex.id"
>
{{ ex.name }}
</button>
</div>
<div class="ex-content">
<div class="ex-list">
<div
v-for="item in currentExample.items"
:key="item.code"
class="ex-row"
>
<code class="ex-code">{{ item.code }}</code>
<span class="ex-msg">{{ item.message }}</span>
</div>
</div>
</div>
</div>
</div>
<div class="tips">
<span class="tips-icon">💡</span>
<span class="tips-text">错误信息要"机器可读 + 人类友好"便于前端统一处理</span>
</div>
</div>
</template>
<script setup>
import { ref, computed } from 'vue'
const active = ref('validate')
const exId = ref('param')
const tabs = [
{ id: 'validate', icon: '🔍', name: '参数校验' },
{ id: 'business', icon: '💼', name: '业务错误' },
{ id: 'layers', icon: '📊', name: '分层设计' },
{ id: 'http', icon: '🌐', name: 'HTTP对比' },
{ id: 'examples', icon: '📋', name: '常见示例' }
]
const layers = [
{ range: '50001-59999', name: '系统层', example: '50001 数据库异常', desc: '基础设施问题' },
{ range: '40001-49999', name: '服务层', example: '40001 第三方服务超时', desc: '外部依赖问题' },
{ range: '30001-39999', name: '认证层', example: '30001 未登录', desc: '身份权限问题' },
{ range: '20001-29999', name: '业务层', example: '20001 余额不足', desc: '业务规则校验' },
{ range: '10001-19999', name: '参数层', example: '10001 参数缺失', desc: '客户端输入问题' }
]
const examples = [
{
id: 'param',
name: '参数层',
items: [
{ code: 10001, message: '缺少必填参数' },
{ code: 10002, message: '参数格式错误' },
{ code: 10003, message: '参数长度超限' },
{ code: 10004, message: '参数值非法' }
]
},
{
id: 'auth',
name: '认证层',
items: [
{ code: 30001, message: '未登录' },
{ code: 30002, message: '登录已过期' },
{ code: 30003, message: '无权限访问' },
{ code: 30004, message: '账号已被禁用' }
]
},
{
id: 'biz',
name: '业务层',
items: [
{ code: 20001, message: '余额不足' },
{ code: 20002, message: '商品已下架' },
{ code: 20003, message: '订单已取消' },
{ code: 20004, message: '库存不足' }
]
},
{
id: 'sys',
name: '系统层',
items: [
{ code: 50001, message: '数据库异常' },
{ code: 50002, message: '缓存服务异常' },
{ code: 50003, message: '系统繁忙,请稍后重试' }
]
}
]
const currentExample = computed(() => {
return examples.find(e => e.id === exId.value) || examples[0]
})
</script>
<style scoped>
.demo {
border: 1px solid var(--vp-c-divider);
border-radius: 12px;
background: var(--vp-c-bg-soft);
margin: 24px 0;
overflow: hidden;
}
.header {
padding: 14px 20px;
background: var(--vp-c-bg);
border-bottom: 1px solid var(--vp-c-divider);
display: flex;
align-items: center;
gap: 10px;
}
.icon {
font-size: 20px;
}
.title {
font-weight: 600;
font-size: 15px;
}
.tabs {
display: flex;
gap: 4px;
padding: 10px 12px;
background: var(--vp-c-bg);
border-bottom: 1px solid var(--vp-c-divider);
overflow-x: auto;
}
.tab {
padding: 6px 12px;
border: 1px solid var(--vp-c-divider);
border-radius: 6px;
background: var(--vp-c-bg-soft);
font-size: 12px;
font-weight: 500;
cursor: pointer;
white-space: nowrap;
transition: all 0.2s;
}
.tab:hover {
border-color: var(--vp-c-brand);
}
.tab.active {
background: var(--vp-c-brand);
border-color: var(--vp-c-brand);
color: white;
}
.content {
padding: 16px;
}
.section h4 {
margin: 0 0 12px 0;
font-size: 14px;
}
.code-block {
background: #1e293b;
color: #e2e8f0;
padding: 12px;
border-radius: 6px;
font-family: 'Menlo', monospace;
font-size: 11px;
line-height: 1.5;
overflow-x: auto;
margin: 0;
}
.field-tips {
margin-top: 12px;
display: flex;
flex-direction: column;
gap: 6px;
}
.tip-row {
display: flex;
align-items: center;
gap: 10px;
padding: 8px 12px;
background: var(--vp-c-bg);
border-radius: 6px;
}
.tip-row code {
background: var(--vp-c-bg-soft);
padding: 2px 8px;
border-radius: 4px;
font-size: 12px;
color: var(--vp-c-brand);
min-width: 70px;
}
.tip-row span {
font-size: 12px;
color: var(--vp-c-text-2);
}
.business-tips {
margin-top: 12px;
display: flex;
flex-direction: column;
gap: 6px;
}
.b-tip {
font-size: 12px;
color: var(--vp-c-text-2);
padding: 6px 10px;
background: var(--vp-c-bg);
border-radius: 4px;
}
.layer-list {
display: flex;
flex-direction: column;
gap: 8px;
}
.layer-item {
display: grid;
grid-template-columns: 100px 1fr auto;
gap: 12px;
align-items: center;
padding: 10px 12px;
background: var(--vp-c-bg);
border: 1px solid var(--vp-c-divider);
border-radius: 6px;
}
@media (max-width: 640px) {
.layer-item {
grid-template-columns: 1fr;
gap: 6px;
}
}
.layer-range {
font-family: monospace;
font-size: 12px;
font-weight: 600;
color: var(--vp-c-brand);
background: var(--vp-c-bg-soft);
padding: 4px 8px;
border-radius: 4px;
text-align: center;
}
.layer-name {
font-size: 13px;
font-weight: 600;
}
.layer-example {
font-size: 11px;
color: var(--vp-c-text-3);
}
.layer-desc {
font-size: 11px;
color: var(--vp-c-text-2);
background: color-mix(in srgb, var(--vp-c-brand) 10%, transparent);
padding: 4px 8px;
border-radius: 4px;
}
.layer-note {
margin-top: 12px;
font-size: 12px;
color: var(--vp-c-text-2);
padding: 8px 12px;
background: var(--vp-c-bg);
border-radius: 6px;
}
.http-compare {
display: flex;
align-items: stretch;
gap: 12px;
}
@media (max-width: 640px) {
.http-compare {
flex-direction: column;
}
.http-arrow {
display: none;
}
}
.http-col {
flex: 1;
padding: 12px;
background: var(--vp-c-bg);
border: 1px solid var(--vp-c-divider);
border-radius: 8px;
}
.http-title {
font-size: 13px;
font-weight: 600;
margin-bottom: 4px;
}
.http-desc {
font-size: 11px;
color: var(--vp-c-text-3);
margin-bottom: 10px;
}
.http-arrow {
display: flex;
align-items: center;
font-size: 20px;
color: var(--vp-c-text-3);
}
.http-codes {
display: flex;
flex-direction: column;
gap: 6px;
}
.http-code {
display: flex;
align-items: center;
gap: 8px;
font-size: 12px;
}
.code-num {
font-family: monospace;
font-weight: 600;
color: var(--vp-c-brand);
min-width: 50px;
}
.http-note {
margin-top: 12px;
font-size: 12px;
color: var(--vp-c-text-2);
padding: 8px 12px;
background: color-mix(in srgb, #22c55e 10%, var(--vp-c-bg));
border-radius: 6px;
}
.ex-tabs {
display: flex;
gap: 4px;
margin-bottom: 12px;
flex-wrap: wrap;
}
.ex-tab {
padding: 5px 10px;
border: 1px solid var(--vp-c-divider);
border-radius: 4px;
background: var(--vp-c-bg);
font-size: 11px;
cursor: pointer;
transition: all 0.2s;
}
.ex-tab:hover {
border-color: var(--vp-c-brand);
}
.ex-tab.active {
background: var(--vp-c-brand);
border-color: var(--vp-c-brand);
color: white;
}
.ex-list {
display: flex;
flex-direction: column;
gap: 6px;
}
.ex-row {
display: flex;
align-items: center;
gap: 12px;
padding: 8px 12px;
background: var(--vp-c-bg);
border-radius: 6px;
}
.ex-code {
font-family: monospace;
font-size: 12px;
font-weight: 600;
color: var(--vp-c-brand);
background: var(--vp-c-bg-soft);
padding: 2px 8px;
border-radius: 4px;
min-width: 50px;
text-align: center;
}
.ex-msg {
font-size: 12px;
color: var(--vp-c-text-2);
}
.tips {
display: flex;
align-items: center;
gap: 8px;
padding: 10px 16px;
background: var(--vp-c-bg);
border-top: 1px solid var(--vp-c-divider);
}
.tips-icon {
font-size: 14px;
}
.tips-text {
font-size: 12px;
color: var(--vp-c-text-2);
}
</style>
@@ -0,0 +1,603 @@
<template>
<div class="demo">
<div class="header">
<span class="icon">📋</span>
<span class="title">API 响应结构设计</span>
</div>
<div class="tabs">
<button
v-for="tab in tabs"
:key="tab.id"
:class="['tab', { active: active === tab.id }]"
@click="active = tab.id"
>
{{ tab.icon }} {{ tab.name }}
</button>
</div>
<div class="content">
<div v-if="active === 'why'" class="section">
<h4>为什么要统一响应格式</h4>
<div class="problem-box">
<div class="problem-title"> 问题不同接口返回格式不一致</div>
<pre class="code-sm">
// 接口 A
{ "data": { "user": {...} } }
// 接口 B
{ "result": { "user": {...} } }
// 接口 C
{ "user": {...} }</pre>
<div class="problem-desc">
前端需要针对每个接口单独处理代码冗余容易出错
</div>
</div>
<div class="solution-box">
<div class="solution-title"> 解决统一响应格式</div>
<pre class="code-sm">
{
"code": 0,
"message": "success",
"data": { ... },
"request_id": "req-xxx"
}</pre>
</div>
</div>
<div v-if="active === 'fields'" class="section">
<h4>响应字段说明</h4>
<div class="field-list">
<div v-for="field in fields" :key="field.name" class="field-item">
<div class="field-header">
<code class="field-name">{{ field.name }}</code>
<span class="field-type">{{ field.type }}</span>
<span v-if="field.required" class="field-required">必填</span>
</div>
<div class="field-desc">{{ field.desc }}</div>
</div>
</div>
</div>
<div v-if="active === 'codes'" class="section">
<h4>业务状态码设计</h4>
<div class="code-ranges">
<div class="range-item">
<span class="range-num">0</span>
<span class="range-label">成功</span>
</div>
<div class="range-item">
<span class="range-num">1xxxx</span>
<span class="range-label">客户端错误</span>
</div>
<div class="range-item">
<span class="range-num">2xxxx</span>
<span class="range-label">业务错误</span>
</div>
<div class="range-item">
<span class="range-num">3xxxx</span>
<span class="range-label">认证/权限错误</span>
</div>
<div class="range-item">
<span class="range-num">5xxxx</span>
<span class="range-label">系统错误</span>
</div>
</div>
<div class="code-examples">
<div v-for="code in codeExamples" :key="code.code" class="code-row">
<code class="code-value">{{ code.code }}</code>
<span class="code-msg">{{ code.message }}</span>
</div>
</div>
</div>
<div v-if="active === 'examples'" class="section">
<h4>不同场景响应示例</h4>
<div class="example-tabs">
<button
v-for="ex in examples"
:key="ex.id"
:class="['ex-tab', { active: exId === ex.id }]"
@click="exId = ex.id"
>
{{ ex.name }}
</button>
</div>
<div class="example-content">
<pre class="code-block"><code>{{ currentExample.code }}</code></pre>
<div class="example-note">{{ currentExample.note }}</div>
</div>
</div>
<div v-if="active === 'pagination'" class="section">
<h4>分页参数设计</h4>
<div class="pg-row">
<div class="pg-col">
<div class="pg-title">请求参数</div>
<div class="pg-params">
<div class="pg-item">
<code>page</code>
<span>页码 1 开始</span>
</div>
<div class="pg-item">
<code>page_size</code>
<span>每页数量默认 20</span>
</div>
<div class="pg-item">
<code>sort</code>
<span>排序 created_desc</span>
</div>
</div>
</div>
<div class="pg-col">
<div class="pg-title">响应格式</div>
<pre class="code-sm">
"pagination": {
"page": 1,
"page_size": 20,
"total": 156,
"total_pages": 8,
"has_next": true
}</pre>
</div>
</div>
</div>
</div>
<div class="tips">
<span class="tips-icon">💡</span>
<span class="tips-text">request_id 用于问题追踪建议使用 UUID v4 或雪花算法生成</span>
</div>
</div>
</template>
<script setup>
import { ref, computed } from 'vue'
const active = ref('why')
const exId = ref('success')
const tabs = [
{ id: 'why', icon: '❓', name: '为什么统一' },
{ id: 'fields', icon: '📝', name: '字段说明' },
{ id: 'codes', icon: '🔢', name: '状态码' },
{ id: 'examples', icon: '📄', name: '示例' },
{ id: 'pagination', icon: '📑', name: '分页' }
]
const fields = [
{
name: 'code',
type: 'number',
required: true,
desc: '业务状态码,0 表示成功'
},
{ name: 'message', type: 'string', required: true, desc: '状态描述信息' },
{
name: 'data',
type: 'any',
required: false,
desc: '业务数据,失败时可为 null'
},
{
name: 'request_id',
type: 'string',
required: true,
desc: '请求唯一标识,用于追踪'
},
{
name: 'timestamp',
type: 'string',
required: false,
desc: '响应时间戳,ISO 8601 格式'
}
]
const codeExamples = [
{ code: 0, message: 'success - 成功' },
{ code: 10001, message: '参数错误:缺少必填字段' },
{ code: 10002, message: '资源不存在' },
{ code: 20001, message: '余额不足' },
{ code: 30001, message: '未登录' },
{ code: 50001, message: '系统繁忙,请稍后重试' }
]
const examples = [
{
id: 'success',
name: '成功-单对象',
code: `{
"code": 0,
"message": "success",
"data": {
"id": 123,
"name": "张三",
"email": "zhangsan@example.com"
},
"request_id": "req-abc123"
}`,
note: '成功响应:data 包含具体业务数据'
},
{
id: 'list',
name: '成功-列表',
code: `{
"code": 0,
"message": "success",
"data": {
"items": [
{ "id": 1, "name": "商品A" },
{ "id": 2, "name": "商品B" }
],
"pagination": {
"page": 1,
"page_size": 20,
"total": 156
}
},
"request_id": "req-def456"
}`,
note: '列表响应:items 数组 + pagination 分页信息'
},
{
id: 'error',
name: '业务错误',
code: `{
"code": 20001,
"message": "余额不足,当前余额 50.00 元",
"data": null,
"request_id": "req-ghi789"
}`,
note: '业务错误:code 非 0message 说明原因'
},
{
id: 'validate',
name: '参数校验',
code: `{
"code": 10001,
"message": "参数校验失败",
"data": {
"errors": [
{ "field": "email", "message": "邮箱格式不正确" },
{ "field": "password", "message": "密码长度至少 8 位" }
]
},
"request_id": "req-jkl012"
}`,
note: '参数错误:data.errors 列出所有错误字段'
}
]
const currentExample = computed(() => {
return examples.find((e) => e.id === exId.value) || examples[0]
})
</script>
<style scoped>
.demo {
border: 1px solid var(--vp-c-divider);
border-radius: 12px;
background: var(--vp-c-bg-soft);
margin: 24px 0;
overflow: hidden;
}
.header {
padding: 14px 20px;
background: var(--vp-c-bg);
border-bottom: 1px solid var(--vp-c-divider);
display: flex;
align-items: center;
gap: 10px;
}
.icon {
font-size: 20px;
}
.title {
font-weight: 600;
font-size: 15px;
}
.tabs {
display: flex;
gap: 4px;
padding: 10px 12px;
background: var(--vp-c-bg);
border-bottom: 1px solid var(--vp-c-divider);
overflow-x: auto;
}
.tab {
padding: 6px 12px;
border: 1px solid var(--vp-c-divider);
border-radius: 6px;
background: var(--vp-c-bg-soft);
font-size: 12px;
font-weight: 500;
cursor: pointer;
white-space: nowrap;
transition: all 0.2s;
}
.tab:hover {
border-color: var(--vp-c-brand);
}
.tab.active {
background: var(--vp-c-brand);
border-color: var(--vp-c-brand);
color: white;
}
.content {
padding: 16px;
}
.section h4 {
margin: 0 0 12px 0;
font-size: 14px;
}
.problem-box,
.solution-box {
margin-bottom: 12px;
padding: 12px;
border-radius: 8px;
}
.problem-box {
background: color-mix(in srgb, #ef4444 8%, var(--vp-c-bg));
border: 1px solid color-mix(in srgb, #ef4444 20%, transparent);
}
.solution-box {
background: color-mix(in srgb, #22c55e 8%, var(--vp-c-bg));
border: 1px solid color-mix(in srgb, #22c55e 20%, transparent);
}
.problem-title,
.solution-title {
font-size: 13px;
font-weight: 600;
margin-bottom: 8px;
}
.problem-desc {
font-size: 12px;
color: var(--vp-c-text-3);
margin-top: 8px;
}
.code-sm {
background: #1e293b;
color: #e2e8f0;
padding: 10px;
border-radius: 6px;
font-family: 'Menlo', monospace;
font-size: 11px;
line-height: 1.5;
overflow-x: auto;
margin: 0;
}
.field-list {
display: flex;
flex-direction: column;
gap: 8px;
}
.field-item {
padding: 10px 12px;
background: var(--vp-c-bg);
border: 1px solid var(--vp-c-divider);
border-radius: 6px;
}
.field-header {
display: flex;
align-items: center;
gap: 8px;
margin-bottom: 4px;
}
.field-name {
font-size: 13px;
font-weight: 600;
color: var(--vp-c-brand);
}
.field-type {
font-size: 11px;
color: var(--vp-c-text-3);
background: var(--vp-c-bg-soft);
padding: 2px 6px;
border-radius: 4px;
}
.field-required {
font-size: 10px;
color: #f59e0b;
background: color-mix(in srgb, #f59e0b 15%, transparent);
padding: 1px 5px;
border-radius: 3px;
}
.field-desc {
font-size: 12px;
color: var(--vp-c-text-2);
}
.code-ranges {
display: flex;
flex-wrap: wrap;
gap: 8px;
margin-bottom: 14px;
}
.range-item {
display: flex;
align-items: center;
gap: 6px;
padding: 6px 10px;
background: var(--vp-c-bg);
border: 1px solid var(--vp-c-divider);
border-radius: 6px;
}
.range-num {
font-family: monospace;
font-size: 12px;
font-weight: 600;
color: var(--vp-c-brand);
}
.range-label {
font-size: 11px;
color: var(--vp-c-text-2);
}
.code-examples {
display: flex;
flex-direction: column;
gap: 4px;
}
.code-row {
display: flex;
align-items: center;
gap: 10px;
padding: 8px 10px;
background: var(--vp-c-bg);
border-radius: 4px;
}
.code-value {
font-family: monospace;
font-size: 12px;
font-weight: 600;
color: var(--vp-c-brand);
min-width: 50px;
}
.code-msg {
font-size: 12px;
color: var(--vp-c-text-2);
}
.example-tabs {
display: flex;
gap: 4px;
margin-bottom: 10px;
flex-wrap: wrap;
}
.ex-tab {
padding: 5px 10px;
border: 1px solid var(--vp-c-divider);
border-radius: 4px;
background: var(--vp-c-bg);
font-size: 11px;
cursor: pointer;
transition: all 0.2s;
}
.ex-tab:hover {
border-color: var(--vp-c-brand);
}
.ex-tab.active {
background: var(--vp-c-brand);
border-color: var(--vp-c-brand);
color: white;
}
.code-block {
background: #1e293b;
color: #e2e8f0;
padding: 12px;
border-radius: 6px;
font-family: 'Menlo', monospace;
font-size: 11px;
line-height: 1.5;
overflow-x: auto;
margin: 0;
}
.example-note {
font-size: 11px;
color: var(--vp-c-text-3);
margin-top: 8px;
padding-left: 4px;
}
.pg-row {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 12px;
}
@media (max-width: 640px) {
.pg-row {
grid-template-columns: 1fr;
}
}
.pg-col {
padding: 12px;
background: var(--vp-c-bg);
border: 1px solid var(--vp-c-divider);
border-radius: 6px;
}
.pg-title {
font-size: 12px;
font-weight: 600;
margin-bottom: 10px;
}
.pg-params {
display: flex;
flex-direction: column;
gap: 6px;
}
.pg-item {
display: flex;
align-items: center;
gap: 8px;
font-size: 11px;
}
.pg-item code {
background: var(--vp-c-bg-soft);
padding: 2px 6px;
border-radius: 3px;
font-size: 11px;
color: var(--vp-c-brand);
}
.pg-item span {
color: var(--vp-c-text-2);
}
.tips {
display: flex;
align-items: center;
gap: 8px;
padding: 10px 16px;
background: var(--vp-c-bg);
border-top: 1px solid var(--vp-c-divider);
}
.tips-icon {
font-size: 14px;
}
.tips-text {
font-size: 12px;
color: var(--vp-c-text-2);
}
</style>
@@ -0,0 +1,417 @@
<template>
<div class="raf-root">
<div class="raf-layout">
<!-- Left: Client Side -->
<div class="raf-left">
<div class="raf-header">
<span class="raf-icon">💻</span>
<span class="raf-title">Client (Browser/App)</span>
</div>
<div class="raf-controls">
<div class="raf-scenarios">
<button
v-for="s in scenarios"
:key="s.id"
:class="['raf-chip', { active: currentScenario.id === s.id }]"
@click="selectScenario(s)"
:disabled="processing"
>
{{ s.label }}
</button>
</div>
</div>
<div class="raf-request-box">
<div class="raf-http-line">
<span :class="['raf-method', currentScenario.method]">{{ currentScenario.method }}</span>
<span class="raf-url">{{ currentScenario.url }}</span>
</div>
<div v-if="currentScenario.body" class="raf-code-block">
{{ JSON.stringify(currentScenario.body, null, 2) }}
</div>
<button
class="raf-send-btn"
@click="sendRequest"
:disabled="processing"
>
{{ processing ? 'Sending...' : 'Send Request' }}
</button>
</div>
<div class="raf-response-box" v-if="response">
<div class="raf-status-line">
<span class="raf-label">Response Status:</span>
<span :class="['raf-status-badge', getStatusColor(response.status)]">
{{ response.status }} {{ response.statusText }}
</span>
</div>
<div class="raf-code-block response-body">
{{ JSON.stringify(response.body, null, 2) }}
</div>
</div>
</div>
<!-- Right: Server Side -->
<div class="raf-right">
<div class="raf-header server-header">
<span class="raf-icon"></span>
<span class="raf-title">Server (API)</span>
</div>
<div class="raf-server-state">
<!-- Database View -->
<div class="raf-section">
<div class="raf-section-title">📦 Database (Users Resource)</div>
<div class="raf-db-view">
<transition-group name="list">
<div v-for="user in db" :key="user.id" class="raf-db-item">
<span class="raf-db-id">ID: {{ user.id }}</span>
<span class="raf-db-name">{{ user.name }}</span>
<span class="raf-db-role">({{ user.role }})</span>
</div>
</transition-group>
<div v-if="db.length === 0" class="raf-empty">No users found</div>
</div>
</div>
<!-- Logs -->
<div class="raf-section">
<div class="raf-section-title">📜 Server Logs</div>
<div class="raf-logs" ref="logsRef">
<div v-for="(log, i) in logs" :key="i" class="raf-log-line">
<span class="raf-log-time">[{{ log.time }}]</span>
<span :class="log.type">{{ log.msg }}</span>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</template>
<script setup>
import { ref, reactive, nextTick } from 'vue'
const processing = ref(false)
const response = ref(null)
const logs = ref([])
const logsRef = ref(null)
const db = ref([
{ id: 1, name: "Alice", role: "admin" },
{ id: 2, name: "Bob", role: "user" }
])
const scenarios = [
{ id: 'get-all', label: 'GET /users', method: 'GET', url: '/api/users', body: null },
{ id: 'get-one', label: 'GET /users/1', method: 'GET', url: '/api/users/1', body: null },
{ id: 'create', label: 'POST /users', method: 'POST', url: '/api/users', body: { name: "Charlie", role: "user" } },
{ id: 'not-found', label: 'GET /users/99', method: 'GET', url: '/api/users/99', body: null },
{ id: 'delete', label: 'DELETE /users/1', method: 'DELETE', url: '/api/users/1', body: null },
]
const currentScenario = ref(scenarios[0])
function selectScenario(s) {
currentScenario.value = s
response.value = null
}
function addLog(msg, type = 'info') {
const now = new Date()
const time = `${now.getHours()}:${now.getMinutes()}:${now.getSeconds()}`
logs.value.push({ time, msg, type })
nextTick(() => {
if (logsRef.value) logsRef.value.scrollTop = logsRef.value.scrollHeight
})
}
function getStatusColor(status) {
if (status >= 200 && status < 300) return 'status-success'
if (status >= 400 && status < 500) return 'status-error'
return 'status-neutral'
}
async function sendRequest() {
processing.value = true
response.value = null
addLog(`Received ${currentScenario.value.method} ${currentScenario.value.url}`, 'info')
await new Promise(r => setTimeout(r, 600)) // Simulate network latency
const { method, url, body } = currentScenario.value
// Router Logic Simulation
if (method === 'GET' && url === '/api/users') {
response.value = { status: 200, statusText: 'OK', body: db.value }
addLog('Matched route: GET /users -> listUsers()', 'success')
}
else if (method === 'GET' && url.match(/\/api\/users\/\d+/)) {
const id = parseInt(url.split('/').pop())
const user = db.value.find(u => u.id === id)
if (user) {
response.value = { status: 200, statusText: 'OK', body: user }
addLog(`Found user ${id}`, 'success')
} else {
response.value = { status: 404, statusText: 'Not Found', body: { error: "User not found" } }
addLog(`User ${id} not found in DB`, 'error')
}
}
else if (method === 'POST' && url === '/api/users') {
const newUser = { id: Math.max(0, ...db.value.map(u => u.id)) + 1, ...body }
db.value.push(newUser)
response.value = { status: 201, statusText: 'Created', body: newUser }
addLog(`Created user ${newUser.id}`, 'success')
}
else if (method === 'DELETE' && url.match(/\/api\/users\/\d+/)) {
const id = parseInt(url.split('/').pop())
const idx = db.value.findIndex(u => u.id === id)
if (idx !== -1) {
db.value.splice(idx, 1)
response.value = { status: 204, statusText: 'No Content', body: null }
addLog(`Deleted user ${id}`, 'success')
} else {
response.value = { status: 404, statusText: 'Not Found', body: { error: "User not found" } }
addLog(`User ${id} not found for deletion`, 'error')
}
}
processing.value = false
}
</script>
<style scoped>
.raf-root {
border: 1px solid var(--vp-c-divider);
border-radius: 8px;
overflow: hidden;
background: var(--vp-c-bg-soft);
margin: 1rem 0;
font-family: var(--vp-font-family-mono);
font-size: 13px;
}
.raf-layout {
display: flex;
min-height: 400px;
}
.raf-left, .raf-right {
flex: 1;
padding: 1.2rem;
display: flex;
flex-direction: column;
}
.raf-left {
border-right: 1px solid var(--vp-c-divider);
background: var(--vp-c-bg);
}
.raf-right {
background: var(--vp-c-bg-alt);
}
.raf-header {
display: flex;
align-items: center;
gap: 8px;
margin-bottom: 1rem;
font-weight: 600;
font-size: 1.1em;
color: var(--vp-c-text-1);
}
.raf-scenarios {
display: flex;
flex-wrap: wrap;
gap: 8px;
margin-bottom: 1.5rem;
}
.raf-chip {
border: 1px solid var(--vp-c-divider);
background: var(--vp-c-bg);
padding: 6px 12px;
border-radius: 20px;
font-size: 12px;
cursor: pointer;
transition: all 0.2s;
color: var(--vp-c-text-2);
}
.raf-chip:hover {
border-color: var(--vp-c-brand);
color: var(--vp-c-brand);
}
.raf-chip.active {
background: var(--vp-c-brand);
color: white;
border-color: var(--vp-c-brand);
}
.raf-request-box {
border: 1px solid var(--vp-c-divider);
border-radius: 6px;
padding: 1rem;
background: var(--vp-c-bg-soft);
margin-bottom: 1rem;
box-shadow: 0 2px 4px rgba(0,0,0,0.05);
}
.raf-http-line {
display: flex;
gap: 10px;
font-family: monospace;
margin-bottom: 8px;
align-items: center;
font-size: 1.1em;
}
.raf-method {
font-weight: bold;
}
.raf-method.GET { color: #61affe; }
.raf-method.POST { color: #49cc90; }
.raf-method.DELETE { color: #f93e3e; }
.raf-code-block {
background: var(--vp-c-bg);
padding: 10px;
border-radius: 4px;
font-size: 12px;
white-space: pre;
overflow-x: auto;
border: 1px solid var(--vp-c-divider);
color: var(--vp-c-text-2);
}
.raf-send-btn {
margin-top: 10px;
width: 100%;
padding: 10px;
background: var(--vp-c-brand);
color: white;
border: none;
border-radius: 6px;
cursor: pointer;
font-weight: 600;
transition: opacity 0.2s;
}
.raf-send-btn:hover {
opacity: 0.9;
}
.raf-send-btn:disabled {
opacity: 0.5;
cursor: not-allowed;
}
.raf-response-box {
margin-top: auto;
border-top: 1px solid var(--vp-c-divider);
padding-top: 1rem;
animation: slideUp 0.3s ease-out;
}
@keyframes slideUp {
from { opacity: 0; transform: translateY(10px); }
to { opacity: 1; transform: translateY(0); }
}
.raf-status-line {
margin-bottom: 8px;
display: flex;
align-items: center;
}
.raf-status-badge {
padding: 2px 8px;
border-radius: 4px;
font-size: 12px;
font-weight: bold;
margin-left: 8px;
}
.status-success { background: #d1fae5; color: #065f46; }
.status-error { background: #fee2e2; color: #991b1b; }
.status-neutral { background: #f3f4f6; color: #374151; }
.raf-db-view {
display: flex;
flex-direction: column;
gap: 6px;
}
.raf-db-item {
display: flex;
gap: 10px;
padding: 8px 12px;
background: var(--vp-c-bg);
border-radius: 6px;
border: 1px solid var(--vp-c-divider);
font-size: 12px;
align-items: center;
}
.raf-db-id { color: var(--vp-c-text-3); font-family: monospace; }
.raf-db-name { font-weight: bold; }
.raf-db-role { color: var(--vp-c-brand); font-size: 0.9em; }
.raf-logs {
height: 180px;
overflow-y: auto;
background: #1e1e1e;
color: #d4d4d4;
padding: 12px;
border-radius: 6px;
font-family: monospace;
font-size: 11px;
line-height: 1.5;
}
.raf-log-line {
display: flex;
gap: 8px;
margin-bottom: 4px;
}
.raf-log-time { color: #6b7280; flex-shrink: 0; }
.info { color: #93c5fd; }
.success { color: #86efac; }
.error { color: #fca5a5; }
.raf-section-title {
font-size: 12px;
text-transform: uppercase;
letter-spacing: 0.5px;
font-weight: bold;
margin-bottom: 8px;
color: var(--vp-c-text-3);
margin-top: 1.5rem;
}
.raf-section:first-child .raf-section-title { margin-top: 0; }
.raf-empty {
color: var(--vp-c-text-3);
font-style: italic;
padding: 10px;
text-align: center;
}
.list-enter-active,
.list-leave-active {
transition: all 0.3s ease;
}
.list-enter-from,
.list-leave-to {
opacity: 0;
transform: translateX(20px);
}
@media (max-width: 768px) {
.raf-layout { flex-direction: column; }
.raf-left { border-right: none; border-bottom: 1px solid var(--vp-c-divider); }
}
</style>
@@ -14,30 +14,15 @@
>
<h5>传统部署</h5>
<div class="server-stack">
<div class="layer-item app">
应用 A
</div>
<div
v-if="showConflict"
class="layer-item conflict"
>
依赖冲突!
</div>
<div class="layer-item deps">
依赖库 v1.0
</div>
<div class="layer-item os">
操作系统
</div>
<div class="layer-item hardware">
物理服务器
</div>
<div class="layer-item app">应用 A</div>
<div v-if="showConflict" class="layer-item conflict">依赖冲突!</div>
<div class="layer-item deps">依赖库 v1.0</div>
<div class="layer-item os">操作系统</div>
<div class="layer-item hardware">物理服务器</div>
</div>
</div>
<div class="vs-divider">
VS
</div>
<div class="vs-divider">VS</div>
<div
class="layer docker"
@@ -48,31 +33,17 @@
<div class="docker-stack">
<div class="containers">
<div class="container-box">
<div class="container-app">
应用 A
</div>
<div class="container-deps">
依赖 v1.0
</div>
<div class="container-app">应用 A</div>
<div class="container-deps">依赖 v1.0</div>
</div>
<div class="container-box">
<div class="container-app">
应用 B
</div>
<div class="container-deps">
依赖 v2.0
</div>
<div class="container-app">应用 B</div>
<div class="container-deps">依赖 v2.0</div>
</div>
</div>
<div class="docker-engine">
Docker Engine
</div>
<div class="host-os">
宿主机操作系统
</div>
<div class="hardware">
物理服务器
</div>
<div class="docker-engine">Docker Engine</div>
<div class="host-os">宿主机操作系统</div>
<div class="hardware">物理服务器</div>
</div>
</div>
</div>
@@ -110,9 +81,17 @@ const showDocker = ref(false)
const showConflict = ref(false)
const benefits = [
{ icon: '📦', title: '环境一致性', desc: '开发、测试、生产环境完全一致,告别"在我机器上能跑"' },
{
icon: '📦',
title: '环境一致性',
desc: '开发、测试、生产环境完全一致,告别"在我机器上能跑"'
},
{ icon: '🚀', title: '快速部署', desc: '秒级启动,镜像分发,滚动更新无停机' },
{ icon: '📊', title: '资源隔离', desc: 'CPU/内存限制,互不干扰,一台机器跑多个应用' },
{
icon: '📊',
title: '资源隔离',
desc: 'CPU/内存限制,互不干扰,一台机器跑多个应用'
},
{ icon: '🔄', title: '版本管理', desc: '镜像版本化,随时回滚,灰度发布' }
]
</script>
@@ -219,8 +198,13 @@ const benefits = [
}
@keyframes pulse {
0%, 100% { opacity: 1; }
50% { opacity: 0.5; }
0%,
100% {
opacity: 1;
}
50% {
opacity: 0.5;
}
}
.containers {
@@ -1,164 +1,82 @@
<template>
<div class="adder-demo">
<div class="demo-header">
<span class="icon">🧮</span>
<span class="title">加法器CPU 怎么做加法</span>
<span class="subtitle">从手算竖式理解"逐位计算"的原理</span>
<div class="demo-label">二进制加法器 输入 015 的两个数观察逐位计算过程</div>
<div class="control-row">
<label class="input-group">
<span class="input-label">A</span>
<input v-model.number="inputA" type="number" min="0" max="15" class="num-input" />
</label>
<span class="op-sign">+</span>
<label class="input-group">
<span class="input-label">B</span>
<input v-model.number="inputB" type="number" min="0" max="15" class="num-input" />
</label>
<span class="op-sign">=</span>
<span class="result-num">{{ resultDec }}</span>
</div>
<div class="intro-section">
<div class="intro-title">🎯 先看十进制竖式理解"逐位计算"</div>
<div class="decimal-demo">
<div class="decimal-column">
<div class="decimal-row label-row">被加数</div>
<div class="decimal-row num-row">
<span class="d-digit">{{ decimalA }}</span>
</div>
</div>
<div class="decimal-column op-col">
<div class="decimal-row label-row">+</div>
<div class="decimal-row num-row">
<span class="d-digit">{{ decimalB }}</span>
</div>
</div>
<div class="decimal-column">
<div class="decimal-row label-row">结果</div>
<div class="decimal-row num-row result">
<span class="d-digit">{{ decimalA + decimalB }}</span>
</div>
</div>
<div class="binary-display">
<div class="binary-row">
<span class="binary-label">A</span>
<span class="binary-bits">
<span v-for="(b, i) in bitsA" :key="'a'+i" class="bit" :class="{ hl: activeBit === (3 - i) }">{{ b }}</span>
</span>
<span class="binary-dec">= {{ clampedA }}</span>
</div>
<div class="intro-hint">
<span class="icon">💡</span>
<span>手算时我们从<strong>个位往高位</strong>一位一位算<strong>逢十进一</strong>CPU 做加法也一样只是它只认识 0 1所以要<strong>逢二进一</strong></span>
<div class="binary-row">
<span class="binary-label">B</span>
<span class="binary-bits">
<span v-for="(b, i) in bitsB" :key="'b'+i" class="bit" :class="{ hl: activeBit === (3 - i) }">{{ b }}</span>
</span>
<span class="binary-dec">= {{ clampedB }}</span>
</div>
<div class="binary-row sum-row">
<span class="binary-label">结果</span>
<span class="binary-bits">
<span v-for="(b, i) in bitsSum" :key="'s'+i" class="bit" :class="{ hl: activeBit === (3 - i) }">{{ b }}</span>
</span>
<span class="binary-dec">= {{ fourBitResult }}</span>
</div>
<div class="bit-labels">
<span v-for="i in 4" :key="i" class="bit-label">{{ 4 - i }}</span>
</div>
</div>
<div class="concept-section">
<div class="concept-title">📚 核心概念</div>
<div class="concepts-grid">
<div class="concept-card half-adder">
<div class="concept-name">半加器</div>
<div class="concept-simple">只算 A + B</div>
<div class="concept-detail">
<p>最右边一位用因为<strong>没有进位进来</strong></p>
<p class="formula">输入AB 输出(S)进位(C)</p>
</div>
</div>
<div class="concept-card full-adder">
<div class="concept-name">全加器</div>
<div class="concept-simple"> A + B + 进位</div>
<div class="concept-detail">
<p>其他位用因为<strong>要加上一位的进位</strong></p>
<p class="formula">输入ABCin 输出(S)进位(Cout)</p>
</div>
</div>
</div>
</div>
<div class="demo-section">
<div class="demo-title">🎮 动手试试二进制加法</div>
<div class="control-row">
<label class="input-group">
<span class="input-label">A被加数</span>
<input v-model.number="inputA" type="number" min="0" max="15" class="num-input" />
</label>
<span class="op-sign">+</span>
<label class="input-group">
<span class="input-label">B加数</span>
<input v-model.number="inputB" type="number" min="0" max="15" class="num-input" />
</label>
<span class="op-sign">=</span>
<span class="result-num">{{ resultDec }}</span>
</div>
<div class="binary-display">
<div class="binary-row">
<span class="binary-label">A</span>
<span class="binary-bits">
<span v-for="(b, i) in bitsA" :key="'a'+i" class="bit" :class="{ highlight: activeBit === (3 - i) }">{{ b }}</span>
<div class="stages-row">
<div
v-for="(stage, idx) in stages" :key="idx"
class="stage-card"
:class="{ active: activeBit === stage.bitPos }"
@mouseenter="activeBit = stage.bitPos"
@mouseleave="activeBit = null"
>
<div class="stage-head">
<span class="stage-pos">{{ stage.bitPos }}</span>
<span class="stage-type" :class="stage.carryIn !== null ? 'full' : 'half'">
{{ stage.carryIn !== null ? '全加器' : '半加器' }}
</span>
<span class="binary-dec">= {{ inputA }}</span>
</div>
<div class="binary-row">
<span class="binary-label">B</span>
<span class="binary-bits">
<span v-for="(b, i) in bitsB" :key="'b'+i" class="bit" :class="{ highlight: activeBit === (3 - i) }">{{ b }}</span>
</span>
<span class="binary-dec">= {{ inputB }}</span>
<div class="stage-io">
<span class="io-item"><span class="io-tag a">A</span>{{ stage.a }}</span>
<span class="io-item"><span class="io-tag b">B</span>{{ stage.b }}</span>
<span v-if="stage.carryIn !== null" class="io-item"><span class="io-tag cin">Cin</span>{{ stage.carryIn }}</span>
</div>
<div class="binary-row result-row">
<span class="binary-label">结果</span>
<span class="binary-bits">
<span v-for="(b, i) in bitsSum" :key="'s'+i" class="bit" :class="{ highlight: activeBit === (3 - i) }">{{ b }}</span>
</span>
<span class="binary-dec">= {{ fourBitResult }}</span>
</div>
<div class="bit-labels">
<span v-for="i in 4" :key="i" class="bit-label">{{ 4 - i }}</span>
</div>
</div>
<div class="stages-row">
<div
v-for="(stage, idx) in stages"
:key="idx"
class="stage-card"
:class="{ active: activeBit === stage.bitPos }"
@mouseenter="activeBit = stage.bitPos"
@mouseleave="activeBit = null"
>
<div class="stage-header">
<span class="stage-pos">{{ stage.bitPos }}</span>
<span class="stage-type" :class="stage.carryIn !== null ? 'full' : 'half'">
{{ stage.carryIn !== null ? '全加器' : '半加器' }}
</span>
</div>
<div class="stage-io">
<div class="io-line">
<span class="io-tag a">A</span>
<span class="io-val">{{ stage.a }}</span>
</div>
<div class="io-line">
<span class="io-tag b">B</span>
<span class="io-val">{{ stage.b }}</span>
</div>
<div v-if="stage.carryIn !== null" class="io-line">
<span class="io-tag cin">Cin</span>
<span class="io-val">{{ stage.carryIn }}</span>
</div>
</div>
<div class="stage-divider"></div>
<div class="stage-io">
<div class="io-line">
<span class="io-tag s">S</span>
<span class="io-val sum">{{ stage.sum }}</span>
</div>
<div class="io-line">
<span class="io-tag cout">Cout</span>
<span class="io-val">{{ stage.carryOut }}</span>
</div>
</div>
<div v-if="idx < 3" class="carry-arrow" :class="{ hasCarry: stage.carryOut }">
{{ stage.carryOut ? '→ 进位' : '' }}
</div>
<div class="stage-divider"></div>
<div class="stage-io">
<span class="io-item"><span class="io-tag s">S</span><strong>{{ stage.sum }}</strong></span>
<span class="io-item"><span class="io-tag cout">C</span>{{ stage.carryOut }}</span>
</div>
</div>
</div>
<div class="info-box">
<span class="icon">💡</span>
<strong>核心思想</strong>每位加法器接收 AB 和上一位的进位输出本位的和与传给下一位的进位就像手算竖式"逢二进一"只是用电路自动完成
</div>
<div class="demo-caption">鼠标悬停某一位查看该位加法器的输入 / 输出 · 就像手算竖式"逢二进一"</div>
</div>
</template>
<script setup>
import { ref, computed } from 'vue'
const decimalA = 35
const decimalB = 47
const inputA = ref(3)
const inputB = ref(2)
const activeBit = ref(null)
@@ -191,14 +109,7 @@ const stages = computed(() => {
sum = (a ^ b) ^ carryIn
carryOut = (a & b) | (carryIn & (a ^ b))
}
result.push({
bitPos: i,
a,
b,
carryIn: carryIn === null ? null : carryIn,
sum,
carryOut
})
result.push({ bitPos: i, a, b, carryIn: carryIn === null ? null : carryIn, sum, carryOut })
carryIn = carryOut
}
return result
@@ -215,7 +126,7 @@ const fourBitResult = computed(() =>
const overflow = computed(() => clampedA.value + clampedB.value > 15)
const resultDec = computed(() =>
overflow.value ? `${fourBitResult.value}4位溢出)` : String(fourBitResult.value)
overflow.value ? `${fourBitResult.value}(溢出)` : String(fourBitResult.value)
)
</script>
@@ -224,164 +135,25 @@ const resultDec = computed(() =>
border: 1px solid var(--vp-c-divider);
border-radius: 8px;
background: var(--vp-c-bg-soft);
padding: 1rem;
padding: 1rem 1.2rem;
margin: 1rem 0;
}
.demo-header {
display: flex;
align-items: center;
gap: 0.5rem;
margin-bottom: 0.75rem;
}
.demo-header .icon { font-size: 1.25rem; }
.demo-header .title { font-weight: bold; font-size: 1rem; }
.demo-header .subtitle { color: var(--vp-c-text-2); font-size: 0.85rem; margin-left: 0.25rem; }
.intro-section {
background: var(--vp-c-bg-alt);
border-radius: 6px;
padding: 0.75rem;
margin-bottom: 0.75rem;
}
.intro-title {
.demo-label {
font-size: 0.78rem;
font-weight: bold;
font-size: 0.85rem;
margin-bottom: 0.5rem;
color: var(--vp-c-text-1);
}
.decimal-demo {
display: flex;
align-items: center;
gap: 0.5rem;
margin-bottom: 0.5rem;
}
.decimal-column {
display: flex;
flex-direction: column;
gap: 0.15rem;
}
.decimal-column.op-col {
min-width: 2rem;
text-align: center;
}
.decimal-row {
font-size: 0.85rem;
}
.decimal-row.label-row {
color: var(--vp-c-text-3);
font-size: 0.75rem;
}
.decimal-row.num-row {
font-family: monospace;
font-size: 1.1rem;
font-weight: bold;
}
.decimal-row.num-row.result {
color: var(--vp-c-brand-1);
}
.d-digit {
display: inline-block;
min-width: 1.5rem;
text-align: center;
}
.intro-hint {
display: flex;
align-items: flex-start;
gap: 0.35rem;
font-size: 0.8rem;
color: var(--vp-c-text-2);
line-height: 1.5;
}
.intro-hint .icon {
flex-shrink: 0;
}
.concept-section {
margin-bottom: 0.75rem;
letter-spacing: 0.2px;
}
.concept-title {
font-weight: bold;
font-size: 0.85rem;
margin-bottom: 0.5rem;
color: var(--vp-c-text-1);
}
.concepts-grid {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 0.5rem;
}
.concept-card {
background: var(--vp-c-bg);
border: 1px solid var(--vp-c-divider);
border-radius: 6px;
padding: 0.6rem;
}
.concept-name {
font-weight: bold;
font-size: 0.9rem;
margin-bottom: 0.15rem;
}
.concept-simple {
font-size: 0.8rem;
color: var(--vp-c-brand-1);
font-weight: 600;
margin-bottom: 0.25rem;
}
.concept-detail {
font-size: 0.75rem;
color: var(--vp-c-text-2);
line-height: 1.5;
}
.concept-detail p {
margin: 0;
}
.concept-detail .formula {
margin-top: 0.2rem;
font-family: monospace;
color: var(--vp-c-text-3);
}
.half-adder .concept-name { color: var(--vp-c-brand-1); }
.full-adder .concept-name { color: #8b5cf6; }
.demo-section {
margin-bottom: 0.5rem;
}
.demo-title {
font-weight: bold;
font-size: 0.85rem;
margin-bottom: 0.5rem;
color: var(--vp-c-text-1);
}
/* ── controls ── */
.control-row {
display: flex;
align-items: center;
gap: 0.4rem;
gap: 0.5rem;
flex-wrap: wrap;
margin-bottom: 0.5rem;
margin-bottom: 0.6rem;
}
.input-group {
@@ -391,67 +163,70 @@ const resultDec = computed(() =>
}
.input-label {
font-size: 0.8rem;
font-size: 0.82rem;
font-weight: 600;
color: var(--vp-c-text-2);
}
.num-input {
width: 3rem;
padding: 0.2rem 0.35rem;
width: 3.2rem;
padding: 0.25rem 0.4rem;
border: 1px solid var(--vp-c-divider);
border-radius: 4px;
font-size: 0.85rem;
font-size: 0.9rem;
background: var(--vp-c-bg);
color: var(--vp-c-text-1);
}
.op-sign {
font-weight: bold;
color: var(--vp-c-text-2);
color: var(--vp-c-text-3);
}
.result-num {
font-weight: bold;
color: var(--vp-c-brand-1);
font-size: 0.95rem;
font-size: 1rem;
}
/* ── binary ── */
.binary-display {
background: var(--vp-c-bg-alt);
border-radius: 6px;
padding: 0.5rem 0.75rem;
margin-bottom: 0.5rem;
margin-bottom: 0.6rem;
}
.binary-row {
display: flex;
align-items: center;
gap: 0.5rem;
margin-bottom: 0.2rem;
margin-bottom: 0.15rem;
font-size: 0.85rem;
}
.binary-label {
color: var(--vp-c-text-2);
min-width: 2.5rem;
font-weight: 600;
}
.binary-bits {
display: flex;
gap: 0.2rem;
font-family: monospace;
font-family: 'JetBrains Mono', monospace;
}
.bit {
display: inline-block;
min-width: 1.2rem;
min-width: 1.3rem;
text-align: center;
padding: 0.1rem 0.15rem;
border-radius: 3px;
transition: all 0.15s ease;
transition: all 0.15s;
}
.bit.highlight {
.bit.hl {
background: var(--vp-c-brand-soft);
color: var(--vp-c-brand-1);
font-weight: bold;
@@ -459,11 +234,11 @@ const resultDec = computed(() =>
.binary-dec {
color: var(--vp-c-text-3);
font-size: 0.8rem;
font-size: 0.78rem;
margin-left: 0.25rem;
}
.result-row .binary-bits {
.sum-row .binary-bits {
font-weight: bold;
color: var(--vp-c-brand-1);
}
@@ -472,30 +247,31 @@ const resultDec = computed(() =>
display: flex;
gap: 0.2rem;
margin-left: 3rem;
margin-top: 0.15rem;
margin-top: 0.1rem;
}
.bit-label {
min-width: 1.2rem;
min-width: 1.3rem;
text-align: center;
font-size: 0.65rem;
font-size: 0.6rem;
color: var(--vp-c-text-3);
}
/* ── stages ── */
.stages-row {
display: grid;
grid-template-columns: repeat(4, 1fr);
gap: 0.4rem;
margin-bottom: 0.5rem;
}
.stage-card {
background: var(--vp-c-bg);
border: 1px solid var(--vp-c-divider);
border-radius: 6px;
padding: 0.4rem;
padding: 0.45rem;
cursor: pointer;
transition: all 0.15s ease;
position: relative;
transition: all 0.15s;
}
.stage-card.active {
@@ -503,25 +279,25 @@ const resultDec = computed(() =>
box-shadow: 0 0 0 1px var(--vp-c-brand-1);
}
.stage-header {
.stage-head {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 0.25rem;
padding-bottom: 0.2rem;
margin-bottom: 0.2rem;
padding-bottom: 0.15rem;
border-bottom: 1px solid var(--vp-c-divider);
}
.stage-pos {
font-size: 0.7rem;
font-size: 0.68rem;
font-weight: bold;
color: var(--vp-c-text-2);
}
.stage-type {
font-size: 0.65rem;
font-size: 0.6rem;
font-weight: bold;
padding: 0.1rem 0.25rem;
padding: 0.08rem 0.25rem;
border-radius: 3px;
}
@@ -538,22 +314,24 @@ const resultDec = computed(() =>
.stage-io {
display: flex;
flex-direction: column;
gap: 0.15rem;
gap: 0.1rem;
}
.io-line {
.io-item {
display: flex;
align-items: center;
justify-content: space-between;
gap: 0.2rem;
gap: 0.25rem;
font-family: 'JetBrains Mono', monospace;
font-size: 0.78rem;
}
.io-tag {
font-size: 0.6rem;
font-size: 0.55rem;
font-weight: bold;
padding: 0.05rem 0.2rem;
padding: 0.04rem 0.18rem;
border-radius: 2px;
color: white;
font-family: system-ui;
}
.io-tag.a { background: var(--vp-c-brand-1); }
@@ -562,59 +340,21 @@ const resultDec = computed(() =>
.io-tag.s { background: var(--vp-c-green-1, #16a34a); }
.io-tag.cout { background: #d97706; }
.io-val {
font-family: monospace;
font-size: 0.8rem;
font-weight: bold;
}
.io-val.sum {
color: var(--vp-c-green-1, #16a34a);
}
.stage-divider {
height: 1px;
background: var(--vp-c-divider);
margin: 0.2rem 0;
}
.carry-arrow {
position: absolute;
right: -0.5rem;
top: 50%;
transform: translateY(-50%);
font-size: 0.6rem;
color: #d97706;
white-space: nowrap;
}
.carry-arrow.hasCarry {
font-weight: bold;
}
.info-box {
display: flex;
align-items: flex-start;
gap: 0.35rem;
background: var(--vp-c-bg-alt);
padding: 0.6rem 0.75rem;
border-radius: 6px;
font-size: 0.8rem;
color: var(--vp-c-text-2);
line-height: 1.5;
}
.info-box .icon {
flex-shrink: 0;
.demo-caption {
font-size: 0.72rem;
color: var(--vp-c-text-3);
text-align: center;
}
@media (max-width: 600px) {
.stages-row {
grid-template-columns: repeat(2, 1fr);
}
.concepts-grid {
grid-template-columns: 1fr;
}
}
</style>
@@ -0,0 +1,380 @@
<template>
<div class="cpu-architecture-demo">
<div class="demo-label">
CPU 核心组件与指令执行周期演示 点击"时钟脉冲"执行一个指令周期
</div>
<div class="cpu-container">
<div class="cpu-frame">
<h3 class="cpu-title">CPU (中央处理器)</h3>
<div class="components-grid">
<!-- Control Unit -->
<div
class="cu-box component"
:class="{ active: currentStage === 1 || currentStage === 2 }"
>
<div class="comp-title">控制单元 (CU)</div>
<div class="comp-state">{{ cuState }}</div>
</div>
<!-- ALU -->
<div
class="alu-box component"
:class="{ active: currentStage === 3 }"
>
<div class="comp-title">算术逻辑单元 (ALU)</div>
<div class="comp-state">{{ aluState }}</div>
</div>
<!-- Registers -->
<div
class="reg-box component"
:class="{ active: currentStage === 4 }"
>
<div class="comp-title">寄存器组</div>
<div class="reg-list">
<span class="reg">R0: {{ r0 }}</span>
<span class="reg">R1: {{ r1 }}</span>
<span class="reg">PC: {{ pc }}</span>
</div>
</div>
</div>
</div>
<!-- Memory -->
<div
class="mem-frame component"
:class="{ active: currentStage === 1 || currentStage === 4 }"
>
<h3 class="cpu-title">内存 (Memory)</h3>
<div class="mem-list">
<div class="mem-loc" :class="{ 'hl-mem': pc === 10 }">
<span class="addr">M[10]</span> 取指LOAD R0, #5
</div>
<div class="mem-loc" :class="{ 'hl-mem': pc === 11 }">
<span class="addr">M[11]</span> 译码ADD R1, R0
</div>
<div class="mem-loc" :class="{ 'hl-mem': pc === 12 }">
<span class="addr">M[12]</span> 执行ALU 计算
</div>
<div class="mem-loc" :class="{ 'hl-mem': pc === 13 }">
<span class="addr">M[13]</span> 写回将结果保存
</div>
</div>
</div>
</div>
<!-- Stages progress -->
<div class="pipeline">
<div class="stage" :class="{ active: currentStage === 1 }">
<span class="step-num">1. Fetch</span>
<span class="step-desc">取指</span>
</div>
<div class="stage" :class="{ active: currentStage === 2 }">
<span class="step-num">2. Decode</span>
<span class="step-desc">译码</span>
</div>
<div class="stage" :class="{ active: currentStage === 3 }">
<span class="step-num">3. Execute</span>
<span class="step-desc">执行</span>
</div>
<div class="stage" :class="{ active: currentStage === 4 }">
<span class="step-num">4. WriteBack</span>
<span class="step-desc">写回</span>
</div>
</div>
<div class="controls">
<button class="clock-btn" @click="nextStage">
<span class="clock-icon"></span> 给一个时钟脉冲 (Next Stage)
</button>
<button class="reset-btn" @click="reset">重置</button>
</div>
<div class="logic-explain">
<p>
当前阶段状态<strong>{{ statusMsg }}</strong>
</p>
</div>
</div>
</template>
<script setup>
import { ref, computed } from 'vue'
const currentStage = ref(0) // 0 = Idle, 1 = Fetch, 2 = Decode, 3 = Execute, 4 = Writeback
const cycleCount = ref(0)
const pc = ref(10)
const r0 = ref(0)
const r1 = ref(0)
const cuState = ref('等待时钟信号...')
const aluState = ref('空闲')
const instructions = [
'LOAD R0, #5',
'LOAD R1, #3',
'ADD R0, R1',
'STORE M[14], R0'
]
const statusMsg = computed(() => {
if (currentStage.value === 0)
return '系统启动,等待接收时钟脉冲开始运行程序。'
if (currentStage.value === 1)
return `CPU 内部的控制单元根据程序计数器 (PC=${pc.value}),从内存取出当前指令。`
if (currentStage.value === 2)
return `控制单元翻译指令为硬件控制信号:准备执行操作。`
if (currentStage.value === 3)
return `ALU 进行计算或控制流转移,当前在处理实际数据...`
if (currentStage.value === 4)
return `将运算结果写入寄存器组或写回内存,更新程序计数器(PC)。`
return ''
})
function nextStage() {
if (currentStage.value === 0 || currentStage.value === 4) {
currentStage.value = 1
cuState.value = `取指: 读取指令`
aluState.value = '空闲'
if (currentStage.value === 4) pc.value++
} else if (currentStage.value === 1) {
currentStage.value = 2
cuState.value = `译码: 准备相关电路`
} else if (currentStage.value === 2) {
currentStage.value = 3
cuState.value = '等待 ALU 结果'
aluState.value = '计算进行中...'
} else if (currentStage.value === 3) {
currentStage.value = 4
cuState.value = '完成'
aluState.value = '结果输出'
// Fake logic update
if (cycleCount.value === 0) r0.value = 5
if (cycleCount.value === 1) r1.value = 3
if (cycleCount.value === 2) r0.value = r0.value + r1.value
cycleCount.value++
if (pc.value >= 13) {
pc.value = 9
}
}
}
function reset() {
currentStage.value = 0
cycleCount.value = 0
pc.value = 10
r0.value = 0
r1.value = 0
cuState.value = '等待时钟信号...'
aluState.value = '空闲'
}
</script>
<style scoped>
.cpu-architecture-demo {
border: 1px solid var(--vp-c-divider);
border-radius: 8px;
background: var(--vp-c-bg-soft);
padding: 1rem 1.2rem;
margin: 1rem 0;
}
.demo-label {
font-size: 0.78rem;
font-weight: bold;
color: var(--vp-c-text-2);
margin-bottom: 0.75rem;
letter-spacing: 0.2px;
}
.cpu-container {
display: flex;
gap: 1.5rem;
margin-bottom: 1.5rem;
}
.cpu-frame {
flex: 2;
border: 2px dashed var(--vp-c-brand-1);
border-radius: 8px;
padding: 1rem;
background: var(--vp-c-bg);
}
.mem-frame {
flex: 1;
border: 2px solid var(--vp-c-divider);
border-radius: 8px;
padding: 1rem;
background: var(--vp-c-bg-alt);
}
.cpu-title {
margin: 0 0 1rem 0;
font-size: 0.95rem;
font-weight: bold;
color: var(--vp-c-brand-1);
text-align: center;
}
.components-grid {
display: flex;
flex-direction: column;
gap: 1rem;
}
.component {
border: 1px solid var(--vp-c-divider);
background: var(--vp-c-bg-soft);
padding: 0.8rem;
border-radius: 6px;
transition: all 0.3s ease;
}
.component.active {
background: var(--vp-c-brand-soft);
border-color: var(--vp-c-brand-1);
box-shadow: 0 0 5px rgba(0, 0, 0, 0.1);
}
.comp-title {
font-size: 0.8rem;
font-weight: bold;
color: var(--vp-c-text-2);
margin-bottom: 0.3rem;
}
.comp-state {
font-size: 0.75rem;
color: var(--vp-c-text-1);
font-family: monospace;
}
.reg-list {
display: flex;
gap: 0.5rem;
}
.reg {
font-family: monospace;
font-size: 0.75rem;
background: var(--vp-c-bg);
padding: 0.2rem 0.4rem;
border-radius: 3px;
border: 1px solid var(--vp-c-divider);
}
.mem-list {
display: flex;
flex-direction: column;
gap: 0.5rem;
font-family: monospace;
font-size: 0.75rem;
}
.mem-loc {
padding: 0.3rem 0.5rem;
background: var(--vp-c-bg);
border-radius: 4px;
border: 1px solid var(--vp-c-divider);
}
.mem-loc.hl-mem {
background: #fef08a;
color: #a16207;
border-color: #a16207;
font-weight: bold;
}
.addr {
color: var(--vp-c-text-3);
margin-right: 0.5rem;
}
/* Pipeline Stages */
.pipeline {
display: flex;
justify-content: space-between;
margin-bottom: 1.5rem;
background: var(--vp-c-bg);
border-radius: 6px;
border: 1px solid var(--vp-c-divider);
overflow: hidden;
}
.stage {
flex: 1;
text-align: center;
padding: 0.5rem 0;
border-right: 1px solid var(--vp-c-divider);
transition: all 0.2s;
}
.stage:last-child {
border-right: none;
}
.stage.active {
background: var(--vp-c-brand-1);
color: white;
}
.step-num {
display: block;
font-size: 0.7rem;
font-weight: 600;
margin-bottom: 0.1rem;
}
.step-desc {
display: block;
font-size: 0.8rem;
}
/* Controls */
.controls {
display: flex;
gap: 1rem;
justify-content: center;
}
.clock-btn,
.reset-btn {
padding: 0.5rem 1rem;
border-radius: 4px;
font-size: 0.85rem;
font-weight: bold;
cursor: pointer;
}
.clock-btn {
background: var(--vp-c-brand-1);
color: white;
border: none;
}
.clock-btn:hover {
background: var(--vp-c-brand-2);
}
.reset-btn {
background: transparent;
border: 1px solid var(--vp-c-divider);
color: var(--vp-c-text-2);
}
.logic-explain {
margin-top: 1rem;
padding: 0.8rem;
background: var(--vp-c-bg);
border-radius: 6px;
font-size: 0.85rem;
text-align: center;
color: var(--vp-c-text-2);
}
@media (max-width: 600px) {
.cpu-container {
flex-direction: column;
}
}
</style>
@@ -1,303 +1,314 @@
<template>
<div class="filesystem-demo">
<div class="demo-header">
<span class="title">文件系统数据的"档案柜"</span>
<span class="subtitle">操作系统如何组织和管理文件</span>
</div>
<div class="demo-content">
<div class="fs-tree">
<div class="tree-header">
<span class="header-icon">📂</span>
<span>目录结构</span>
</div>
<div class="tree-content">
<div
v-for="item in fileTree"
:key="item.path"
class="tree-item"
:class="{ selected: selectedItem === item.path }"
:style="{ paddingLeft: (item.level * 12) + 'px' }"
@click="selectItem(item)"
>
<span class="item-icon">{{ item.icon }}</span>
<span class="item-name">{{ item.name }}</span>
</div>
</div>
</div>
<div class="fs-detail">
<div class="detail-header">
<span class="detail-icon">{{ selectedItemInfo?.icon }}</span>
<span class="detail-name">{{ selectedItemInfo?.name }}</span>
<div class="demo-wrapper">
<!-- 文件树逻辑视角 -->
<div class="logical-view">
<div class="view-title">
<span>📁 你的视角 (文件系统)</span>
<span class="subtitle">漂亮整洁的目录树</span>
</div>
<div
v-if="selectedItemInfo"
class="detail-info"
>
<div class="info-row">
<span class="info-label">类型</span>
<span class="info-value">{{ selectedItemInfo.type }}</span>
<div class="file-tree">
<div class="tree-node folder expanded">
<span class="icon">💾</span> D盘 (根目录)
</div>
<div class="info-row">
<span class="info-label">路径</span>
<span class="info-value">{{ selectedItemInfo.path }}</span>
</div>
<div
v-if="selectedItemInfo.type === '文件'"
class="info-row"
>
<span class="info-label">大小</span>
<span class="info-value">{{ selectedItemInfo.size }}</span>
</div>
<div class="info-row">
<span class="info-label">权限</span>
<span class="info-value">{{ selectedItemInfo.permission }}</span>
</div>
</div>
<div
v-if="selectedItemInfo?.type === '文件'"
class="inode-info"
>
<div class="inode-title">
inode 信息
</div>
<div class="inode-visual">
<div class="inode-block">
<span class="inode-label">inode 编号</span>
<span class="inode-value">{{ selectedItemInfo.inode }}</span>
<div class="tree-children">
<div class="tree-node folder expanded">
<span class="icon">📂</span> 照片
</div>
<div class="inode-block">
<span class="inode-label">数据块</span>
<div class="data-blocks">
<span
v-for="b in selectedItemInfo.blocks"
:key="b"
class="block"
>{{ b }}</span>
<div class="tree-children">
<div
class="tree-node file"
:class="{ active: activeFile === 'pet' }"
@click="selectFile('pet')"
>
<span class="icon">🖼</span> 宠物.jpg
<span class="size-badge">3 </span>
</div>
<div
class="tree-node file"
:class="{ active: activeFile === 'vacation' }"
@click="selectFile('vacation')"
>
<span class="icon">🖼</span> 旅游.png
<span class="size-badge">2 </span>
</div>
</div>
<div class="tree-node folder expanded">
<span class="icon">📂</span> 工作
</div>
<div class="tree-children">
<div
class="tree-node file"
:class="{ active: activeFile === 'doc' }"
@click="selectFile('doc')"
>
<span class="icon">📄</span> 总结.docx
<span class="size-badge">4 </span>
</div>
</div>
</div>
</div>
</div>
<!-- 翻译官动画 -->
<div class="translator">
<div class="arrow"></div>
<div class="badge">文件系统账本<br/>(inode表)</div>
<div class="arrow"></div>
</div>
<!-- 磁盘块物理视角 -->
<div class="physical-view">
<div class="view-title">
<span>🖨 硬盘的视角 (物理存储)</span>
<span class="subtitle">无序零散的数据块</span>
</div>
<div class="disk-grid">
<div
v-for="block in 24"
:key="block"
class="disk-block"
:class="[
getBlockOwner(block),
{ active: isBlockActive(block) }
]"
>
{{ block }}
</div>
</div>
</div>
</div>
<div class="explanation-box" v-if="activeFile">
<span v-if="activeFile === 'pet'">
💡 宠物.jpg 其实被切碎分别放在了第 3814 文件系统帮你做好了翻译你只需双击它
</span>
<span v-if="activeFile === 'vacation'">
💡 旅游.png 放在了第 56
</span>
<span v-if="activeFile === 'doc'">
💡 总结.docx 被分散存放在 10111822 如果没有文件系统你得自己背下这些数字才能打开文件
</span>
</div>
<div class="explanation-box default" v-else>
试着点击左侧的文件看看它们在硬盘里到底长什么样
</div>
<div class="info-box">
<strong>核心思想</strong>文件系统用"目录树"组织文件"inode"记录文件元数据文件名只是给人看的系统通过 inode 编号找到真正的数据
</div>
</div>
</template>
<script setup>
import { ref, computed } from 'vue'
import { ref } from 'vue'
const selectedItem = ref('/home')
const activeFile = ref(null)
const fileTree = ref([
{ name: '/', path: '/', icon: '📁', level: 0, type: '目录', permission: 'rwxr-xr-x' },
{ name: 'home', path: '/home', icon: '📁', level: 1, type: '目录', permission: 'rwxr-xr-x' },
{ name: 'user', path: '/home/user', icon: '📁', level: 2, type: '目录', permission: 'rwxr-xr-x' },
{ name: 'documents', path: '/home/user/documents', icon: '📁', level: 3, type: '目录', permission: 'rwxr-xr-x' },
{ name: 'report.pdf', path: '/home/user/documents/report.pdf', icon: '📄', level: 4, type: '文件', size: '2.5MB', permission: 'rw-r--r--', inode: 12345, blocks: ['块1', '块2', '块3'] },
{ name: 'photos', path: '/home/user/photos', icon: '📁', level: 3, type: '目录', permission: 'rwxr-xr-x' },
{ name: 'vacation.jpg', path: '/home/user/photos/vacation.jpg', icon: '🖼️', level: 4, type: '文件', size: '4.2MB', permission: 'rw-r--r--', inode: 12346, blocks: ['块4', '块5', '块6', '块7'] },
{ name: 'etc', path: '/etc', icon: '📁', level: 1, type: '目录', permission: 'rwxr-xr-x' },
{ name: 'config.yml', path: '/etc/config.yml', icon: '⚙️', level: 2, type: '文件', size: '1.2KB', permission: 'rw-r--r--', inode: 10001, blocks: ['块8'] },
{ name: 'var', path: '/var', icon: '📁', level: 1, type: '目录', permission: 'rwxr-xr-x' },
{ name: 'log', path: '/var/log', icon: '📁', level: 2, type: '目录', permission: 'rwxr-xr-x' },
{ name: 'system.log', path: '/var/log/system.log', icon: '📝', level: 3, type: '文件', size: '128MB', permission: 'rw-r-----', inode: 20001, blocks: ['块9', '块10', '...'] }
])
//
const fileMap = {
pet: [3, 8, 14],
vacation: [5, 6],
doc: [10, 11, 18, 22]
}
const selectedItemInfo = computed(() => {
return fileTree.value.find(item => item.path === selectedItem.value)
})
const selectFile = (file) => {
activeFile.value = file
}
const selectItem = (item) => {
selectedItem.value = item.path
const getBlockOwner = (block) => {
for (const [key, blocks] of Object.entries(fileMap)) {
if (blocks.includes(block)) return `owner-${key}`
}
return 'empty'
}
const isBlockActive = (block) => {
if (!activeFile.value) return false
return fileMap[activeFile.value].includes(block)
}
</script>
<style scoped>
.filesystem-demo {
background: var(--vp-c-bg-soft);
border: 1px solid var(--vp-c-divider);
border-radius: 8px;
background: var(--vp-c-bg-soft);
padding: 1rem;
margin: 1rem 0;
border-radius: 12px;
padding: 1.5rem;
margin: 1.5rem 0;
font-family: var(--vp-font-family-base);
}
.demo-header {
display: flex;
align-items: center;
gap: 0.5rem;
margin-bottom: 0.75rem;
}
.demo-header .title { font-weight: bold; font-size: 1rem; }
.demo-header .subtitle { color: var(--vp-c-text-2); font-size: 0.85rem; margin-left: 0.5rem; }
.demo-content {
.demo-wrapper {
display: flex;
align-items: stretch;
gap: 1rem;
flex-wrap: wrap;
margin-bottom: 1.5rem;
}
.fs-tree {
@media (max-width: 768px) {
.demo-wrapper {
flex-direction: column;
}
.translator {
transform: rotate(90deg);
margin: 1rem 0;
}
}
.logical-view, .physical-view {
flex: 1;
min-width: 250px;
background: var(--vp-c-bg);
border-radius: 6px;
overflow: hidden;
}
.tree-header {
display: flex;
align-items: center;
gap: 0.5rem;
padding: 0.5rem;
background: var(--vp-c-bg-alt);
font-weight: bold;
font-size: 0.85rem;
border-radius: 10px;
padding: 1rem;
border: 1px solid var(--vp-c-divider);
}
.tree-content {
max-height: 280px;
overflow-y: auto;
}
.tree-item {
display: flex;
align-items: center;
gap: 0.25rem;
padding: 0.35rem 0.5rem;
cursor: pointer;
transition: all 0.2s;
font-size: 0.85rem;
}
.tree-item:hover {
background: var(--vp-c-bg-soft);
}
.tree-item.selected {
background: var(--vp-c-brand-soft);
}
.item-icon {
font-size: 0.9rem;
}
.fs-detail {
flex: 1;
min-width: 250px;
}
.detail-header {
display: flex;
align-items: center;
gap: 0.5rem;
padding: 0.5rem;
background: var(--vp-c-bg-alt);
border-radius: 4px;
margin-bottom: 0.75rem;
}
.detail-icon {
font-size: 1.5rem;
}
.detail-name {
font-weight: bold;
font-size: 1rem;
}
.detail-info {
background: var(--vp-c-bg);
padding: 0.5rem;
border-radius: 4px;
margin-bottom: 0.75rem;
}
.info-row {
display: flex;
justify-content: space-between;
padding: 0.25rem 0;
font-size: 0.85rem;
border-bottom: 1px solid var(--vp-c-divider);
}
.info-row:last-child {
border-bottom: none;
}
.info-label {
color: var(--vp-c-text-2);
}
.info-value {
font-weight: 500;
}
.inode-info {
background: var(--vp-c-bg-alt);
padding: 0.5rem;
border-radius: 4px;
}
.inode-title {
font-weight: bold;
font-size: 0.85rem;
margin-bottom: 0.5rem;
}
.inode-visual {
.view-title {
display: flex;
flex-direction: column;
gap: 0.5rem;
margin-bottom: 1rem;
padding-bottom: 0.5rem;
border-bottom: 1px dashed var(--vp-c-divider);
}
.inode-block {
background: var(--vp-c-bg);
padding: 0.5rem;
border-radius: 4px;
}
.inode-label {
display: block;
font-size: 0.75rem;
color: var(--vp-c-text-2);
margin-bottom: 0.25rem;
}
.inode-value {
.view-title span {
font-weight: bold;
font-size: 0.95rem;
}
.view-title .subtitle {
font-size: 0.75rem;
color: var(--vp-c-text-3);
font-weight: normal;
margin-top: 0.2rem;
}
/* File Tree Styles */
.file-tree {
font-size: 0.9rem;
}
.data-blocks {
display: flex;
gap: 0.25rem;
flex-wrap: wrap;
}
.block {
padding: 0.15rem 0.4rem;
background: var(--vp-c-brand-soft);
border-radius: 3px;
font-size: 0.75rem;
}
.info-box {
background: var(--vp-c-bg-alt);
padding: 0.75rem;
.tree-node {
padding: 0.4rem 0.5rem;
border-radius: 6px;
font-size: 0.85rem;
color: var(--vp-c-text-2);
margin-top: 0.75rem;
display: flex;
gap: 0.25rem;
align-items: center;
gap: 0.5rem;
cursor: pointer;
transition: all 0.2s;
}
.tree-node:hover {
background: var(--vp-c-bg-mute);
}
.tree-node.file.active {
background: var(--vp-c-brand-soft);
color: var(--vp-c-brand-1);
font-weight: bold;
}
.tree-children {
padding-left: 1.5rem;
border-left: 1px dashed var(--vp-c-divider);
margin-left: 0.6rem;
}
.size-badge {
margin-left: auto;
font-size: 0.7rem;
background: var(--vp-c-bg-mute);
padding: 0.1rem 0.4rem;
border-radius: 4px;
color: var(--vp-c-text-2);
}
.tree-node.active .size-badge {
background: var(--vp-c-brand-1);
color: white;
}
/* Translator */
.translator {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
gap: 0.5rem;
}
.translator .badge {
background: var(--vp-c-brand-1);
color: white;
padding: 0.5rem 1rem;
border-radius: 8px;
font-size: 0.8rem;
font-weight: bold;
text-align: center;
box-shadow: 0 4px 12px rgba(16, 185, 129, 0.2);
}
.arrow {
width: 2px;
height: 20px;
background: var(--vp-c-divider);
position: relative;
}
.arrow::after {
content: '';
position: absolute;
bottom: -4px;
left: -4px;
border-width: 5px;
border-style: solid;
border-color: var(--vp-c-divider) transparent transparent transparent;
}
/* Disk Grid */
.disk-grid {
display: grid;
grid-template-columns: repeat(4, 1fr);
gap: 0.4rem;
}
.disk-block {
aspect-ratio: 1;
display: flex;
align-items: center;
justify-content: center;
font-size: 0.75rem;
color: var(--vp-c-text-3);
background: var(--vp-c-bg);
border: 1px solid var(--vp-c-divider);
border-radius: 4px;
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
}
.disk-block.owner-pet { background: rgba(16, 185, 129, 0.1); border-color: rgba(16, 185, 129, 0.3); }
.disk-block.owner-vacation { background: rgba(59, 130, 246, 0.1); border-color: rgba(59, 130, 246, 0.3); }
.disk-block.owner-doc { background: rgba(245, 158, 11, 0.1); border-color: rgba(245, 158, 11, 0.3); }
.disk-block.active {
transform: scale(1.1);
color: white;
font-weight: bold;
box-shadow: 0 4px 12px rgba(0,0,0,0.1);
z-index: 2;
}
.disk-block.owner-pet.active { background: var(--vp-c-success-1); border-color: var(--vp-c-success-1); }
.disk-block.owner-vacation.active { background: var(--vp-c-brand-1); border-color: var(--vp-c-brand-1); }
.disk-block.owner-doc.active { background: var(--vp-c-warning-1); border-color: var(--vp-c-warning-1); }
.explanation-box {
padding: 1rem;
background: rgba(16, 185, 129, 0.1);
border-left: 4px solid var(--vp-c-success-1);
border-radius: 0 8px 8px 0;
font-size: 0.95rem;
animation: fadeIn 0.3s;
}
.explanation-box.default {
background: var(--vp-c-bg-alt);
border-left-color: var(--vp-c-text-3);
color: var(--vp-c-text-2);
}
@keyframes fadeIn {
from { opacity: 0; transform: translateX(-10px); }
to { opacity: 1; transform: translateX(0); }
}
</style>
@@ -0,0 +1,369 @@
<template>
<div class="functional-unit-demo">
<div class="demo-label">
常见功能单元 切换不同模块查看其实际工作原理
</div>
<div class="tabs">
<button
v-for="tab in tabs"
:key="tab.id"
class="tab-btn"
:class="{ active: currentTab === tab.id }"
@click="currentTab = tab.id"
>
{{ tab.name }}
</button>
</div>
<div class="demo-content">
<!-- MUX Demo -->
<div v-if="currentTab === 'mux'" class="demo-panel">
<div class="panel-desc">
<strong>多路选择器 (MUX)</strong>像铁路道岔一样根据"选择信号"决定让哪一路数据通过
</div>
<div class="mux-container">
<div class="inputs">
<div class="input-line">
<span class="label">数据 0 (D0)</span>
<button
class="toggle-btn"
:class="{ on: muxD0 }"
@click="muxD0 = !muxD0"
>
{{ muxD0 ? '1' : '0' }}
</button>
</div>
<div class="input-line">
<span class="label">数据 1 (D1)</span>
<button
class="toggle-btn"
:class="{ on: muxD1 }"
@click="muxD1 = !muxD1"
>
{{ muxD1 ? '1' : '0' }}
</button>
</div>
</div>
<div class="mux-chip">
<div class="chip-body">MUX</div>
<div class="select-pin">
<span class="label">选择 (Sel)</span>
<button
class="select-btn"
:class="{ on: muxSel }"
@click="muxSel = !muxSel"
>
{{ muxSel ? '1' : '0' }}
</button>
</div>
</div>
<div class="outputs">
<div class="output-line" :class="{ active: muxResult }">
<span class="label">输出 (Out)</span>
<span class="out-val">{{ muxResult ? '1' : '0' }}</span>
</div>
</div>
</div>
<div class="logic-explain">
<p>
当前选择信号为 {{ muxSel ? '1' : '0' }}因此输出等于 数据
{{ muxSel ? '1 (D1)' : '0 (D0)' }} 的值<strong>{{
muxResult ? '1' : '0'
}}</strong>
</p>
</div>
</div>
<!-- Decoder Demo -->
<div v-if="currentTab === 'decoder'" class="demo-panel">
<div class="panel-desc">
<strong>译码器 (Decoder)</strong>将二进制输入转换为特定输出线的激活信号例如 2位输入可以激活
4根输出线中的一根
</div>
<div class="decoder-container">
<div class="inputs vertical">
<div class="input-line">
<button
class="toggle-btn"
:class="{ on: decA1 }"
@click="decA1 = !decA1"
>
{{ decA1 ? '1' : '0' }}
</button>
<span class="label">A1 (高位)</span>
</div>
<div class="input-line">
<button
class="toggle-btn"
:class="{ on: decA0 }"
@click="decA0 = !decA0"
>
{{ decA0 ? '1' : '0' }}
</button>
<span class="label">A0 (低位)</span>
</div>
</div>
<div class="decoder-chip">
<div class="chip-body">2-to-4<br />译码器</div>
</div>
<div class="outputs vertical-out">
<div class="output-line" :class="{ active: decResult === 0 }">
<span class="out-val">{{ decResult === 0 ? '1' : '0' }}</span>
<span class="label">Y0 (当输入 00 )</span>
</div>
<div class="output-line" :class="{ active: decResult === 1 }">
<span class="out-val">{{ decResult === 1 ? '1' : '0' }}</span>
<span class="label">Y1 (当输入 01 )</span>
</div>
<div class="output-line" :class="{ active: decResult === 2 }">
<span class="out-val">{{ decResult === 2 ? '1' : '0' }}</span>
<span class="label">Y2 (当输入 10 )</span>
</div>
<div class="output-line" :class="{ active: decResult === 3 }">
<span class="out-val">{{ decResult === 3 ? '1' : '0' }}</span>
<span class="label">Y3 (当输入 11 )</span>
</div>
</div>
</div>
<div class="logic-explain">
<p>
当前输入为二进制的 {{ decA1 ? '1' : '0'
}}{{ decA0 ? '1' : '0' }} (十进制 {{ decResult }})因此只有
<strong>Y{{ decResult }}</strong> 被激活输出 1
</p>
</div>
</div>
</div>
</div>
</template>
<script setup>
import { ref, computed } from 'vue'
const tabs = [
{ id: 'mux', name: '多路选择器 (MUX)' },
{ id: 'decoder', name: '译码器 (Decoder)' }
]
const currentTab = ref('mux')
// MUX State
const muxD0 = ref(false)
const muxD1 = ref(true)
const muxSel = ref(false)
const muxResult = computed(() => (muxSel.value ? muxD1.value : muxD0.value))
// Decoder State
const decA1 = ref(false)
const decA0 = ref(false)
const decResult = computed(() => (decA1.value ? 2 : 0) + (decA0.value ? 1 : 0))
</script>
<style scoped>
.functional-unit-demo {
border: 1px solid var(--vp-c-divider);
border-radius: 8px;
background: var(--vp-c-bg-soft);
padding: 1rem 1.2rem;
margin: 1rem 0;
}
.demo-label {
font-size: 0.78rem;
font-weight: bold;
color: var(--vp-c-text-2);
margin-bottom: 0.75rem;
letter-spacing: 0.2px;
}
.tabs {
display: flex;
gap: 0.5rem;
margin-bottom: 1rem;
border-bottom: 1px solid var(--vp-c-divider);
padding-bottom: 0.5rem;
}
.tab-btn {
padding: 0.4rem 0.8rem;
font-size: 0.85rem;
border-radius: 4px;
background: var(--vp-c-bg);
border: 1px solid var(--vp-c-divider);
color: var(--vp-c-text-2);
cursor: pointer;
transition: all 0.2s;
}
.tab-btn:hover {
border-color: var(--vp-c-brand-1);
}
.tab-btn.active {
background: var(--vp-c-brand-soft);
color: var(--vp-c-brand-1);
border-color: var(--vp-c-brand-1);
font-weight: bold;
}
.panel-desc {
font-size: 0.85rem;
color: var(--vp-c-text-2);
margin-bottom: 1rem;
}
/* common elements */
.toggle-btn {
width: 2rem;
height: 2rem;
border-radius: 4px;
background: var(--vp-c-bg);
border: 1px solid var(--vp-c-divider);
font-weight: bold;
font-family: monospace;
cursor: pointer;
transition: all 0.2s;
}
.toggle-btn.on {
background: var(--vp-c-green-soft, #dcfce7);
color: var(--vp-c-green-1, #16a34a);
border-color: var(--vp-c-green-1, #16a34a);
}
.out-val {
display: inline-flex;
align-items: center;
justify-content: center;
width: 2rem;
height: 2rem;
border-radius: 4px;
background: var(--vp-c-bg);
border: 1px solid var(--vp-c-divider);
font-weight: bold;
font-family: monospace;
}
.output-line.active .out-val {
background: var(--vp-c-brand-soft);
color: var(--vp-c-brand-1);
border-color: var(--vp-c-brand-1);
}
.output-line.active .label {
color: var(--vp-c-brand-1);
font-weight: bold;
}
.logic-explain {
margin-top: 1rem;
padding: 0.8rem;
background: var(--vp-c-bg);
border-radius: 6px;
font-size: 0.85rem;
text-align: center;
color: var(--vp-c-text-2);
}
/* MUX Layout */
.mux-container {
display: flex;
align-items: center;
justify-content: center;
gap: 2rem;
padding: 1rem;
}
.inputs {
display: flex;
flex-direction: column;
gap: 1rem;
}
.input-line {
display: flex;
align-items: center;
gap: 0.5rem;
}
.label {
font-size: 0.8rem;
color: var(--vp-c-text-3);
font-variant-numeric: tabular-nums;
}
.mux-chip {
position: relative;
display: flex;
flex-direction: column;
align-items: center;
}
.chip-body {
width: 4rem;
height: 6rem;
background: var(--vp-c-bg-alt);
border: 2px solid var(--vp-c-divider);
display: flex;
align-items: center;
justify-content: center;
font-weight: bold;
clip-path: polygon(0 0, 100% 20%, 100% 80%, 0 100%);
}
.select-pin {
position: absolute;
bottom: -2.5rem;
display: flex;
flex-direction: column;
align-items: center;
gap: 0.3rem;
}
.select-btn {
width: 2rem;
height: 1.5rem;
border-radius: 4px;
background: var(--vp-c-bg);
border: 1px solid var(--vp-c-divider);
font-size: 0.8rem;
cursor: pointer;
}
.select-btn.on {
background: #fef08a; /* yellow soft */
color: #a16207;
border-color: #a16207;
}
/* Decoder Layout */
.decoder-container {
display: flex;
align-items: center;
justify-content: center;
gap: 2rem;
padding: 1rem;
}
.inputs.vertical,
.outputs.vertical-out {
display: flex;
flex-direction: column;
gap: 0.8rem;
}
.decoder-chip .chip-body {
width: 5rem;
height: 8rem;
background: var(--vp-c-bg-alt);
border: 2px solid var(--vp-c-divider);
display: flex;
align-items: center;
justify-content: center;
font-weight: bold;
text-align: center;
clip-path: none;
border-radius: 4px;
}
</style>
@@ -1,56 +1,56 @@
<template>
<div class="logic-gate-demo">
<div class="demo-header">
<span class="title">逻辑门用开关做运算</span>
<div class="demo-label">四种基本逻辑门 真值表一览</div>
<div class="gates-grid">
<div v-for="gate in gates" :key="gate.name" class="gate-card">
<div class="gate-name">{{ gate.name }}</div>
<div class="gate-rule">{{ gate.rule }}</div>
<table class="mini-truth">
<thead>
<tr>
<th>A</th>
<th v-if="gate.name !== 'NOT'">B</th>
<th>结果</th>
</tr>
</thead>
<tbody>
<tr v-for="(row, i) in gate.rows" :key="i">
<td>{{ row[0] }}</td>
<td v-if="gate.name !== 'NOT'">{{ row[1] }}</td>
<td class="result-cell" :class="{ one: row[row.length - 1] === 1 }">{{ row[row.length - 1] }}</td>
</tr>
</tbody>
</table>
</div>
</div>
<p class="intro">
输入 AB 只能是 0 1四种门按不同规则输出一个 0 1下面表格列出所有 4 种输入组合的结果
</p>
<div class="truth-section">
<table>
<thead>
<tr>
<th>A</th>
<th>B</th>
<th>AND</th>
<th>OR</th>
<th>NOT(A)</th>
<th>XOR</th>
</tr>
</thead>
<tbody>
<tr v-for="row in truthRows" :key="`${row.a}-${row.b}`">
<td>{{ row.a }}</td>
<td>{{ row.b }}</td>
<td>{{ row.and }}</td>
<td>{{ row.or }}</td>
<td>{{ row.not }}</td>
<td>{{ row.xor }}</td>
</tr>
</tbody>
</table>
<ul class="col-meaning">
<li><strong>AND</strong>两个都是 1 才输出 1像串联都通才通</li>
<li><strong>OR</strong>有一个 1 就输出 1像并联一通就通</li>
<li><strong>NOT(A)</strong> A 取反0110</li>
<li><strong>XOR</strong>两个不同输出 1相同输出 0</li>
</ul>
</div>
<div class="info-box">
<strong>核心思想</strong>逻辑门用晶体管的开关组合实现这四种运算复杂计算都由它们组合而成
</div>
<div class="demo-caption">所有数字计算都由这四种门的组合实现</div>
</div>
</template>
<script setup>
const truthRows = [
{ a: 0, b: 0, and: 0, or: 0, not: 1, xor: 0 },
{ a: 0, b: 1, and: 0, or: 1, not: 1, xor: 1 },
{ a: 1, b: 0, and: 0, or: 1, not: 0, xor: 1 },
{ a: 1, b: 1, and: 1, or: 1, not: 0, xor: 0 }
const gates = [
{
name: 'AND',
rule: '都为 1 才得 1',
rows: [[0,0,0],[0,1,0],[1,0,0],[1,1,1]]
},
{
name: 'OR',
rule: '有一个 1 就得 1',
rows: [[0,0,0],[0,1,1],[1,0,1],[1,1,1]]
},
{
name: 'NOT',
rule: '取反',
rows: [[0,1],[1,0]]
},
{
name: 'XOR',
rule: '不同才得 1',
rows: [[0,0,0],[0,1,1],[1,0,1],[1,1,0]]
}
]
</script>
@@ -59,82 +59,81 @@ const truthRows = [
border: 1px solid var(--vp-c-divider);
border-radius: 8px;
background: var(--vp-c-bg-soft);
padding: 1rem;
padding: 1rem 1.2rem;
margin: 1rem 0;
}
.demo-header {
.demo-label {
font-size: 0.78rem;
font-weight: bold;
color: var(--vp-c-text-2);
margin-bottom: 0.75rem;
letter-spacing: 0.2px;
}
.gates-grid {
display: grid;
grid-template-columns: repeat(4, 1fr);
gap: 0.5rem;
}
.gate-card {
background: var(--vp-c-bg);
border: 1px solid var(--vp-c-divider);
border-radius: 6px;
padding: 0.6rem;
text-align: center;
}
.gate-name {
font-weight: bold;
font-size: 0.9rem;
color: var(--vp-c-brand-1);
margin-bottom: 0.15rem;
}
.gate-rule {
font-size: 0.72rem;
color: var(--vp-c-text-3);
margin-bottom: 0.5rem;
}
.demo-header .title {
font-weight: bold;
font-size: 1rem;
}
.intro {
font-size: 0.9rem;
color: var(--vp-c-text-2);
margin: 0 0 0.75rem;
line-height: 1.5;
}
.truth-section {
margin-bottom: 0;
}
table {
.mini-truth {
width: 100%;
border-collapse: collapse;
table-layout: fixed;
font-size: 0.88rem;
margin-bottom: 0.75rem;
font-size: 0.8rem;
font-variant-numeric: tabular-nums;
}
th,
td {
.mini-truth th,
.mini-truth td {
border: 1px solid var(--vp-c-divider);
padding: 0.4rem 0.5rem;
vertical-align: middle;
padding: 0.2rem 0.3rem;
text-align: center;
font-variant-numeric: tabular-nums;
}
th {
.mini-truth th {
background: var(--vp-c-bg-alt);
font-size: 0.72rem;
font-weight: 600;
}
.col-meaning {
margin: 0;
padding-left: 1.25rem;
font-size: 0.85rem;
color: var(--vp-c-text-2);
line-height: 1.6;
}
.col-meaning li {
margin-bottom: 0.25rem;
.result-cell.one {
color: var(--vp-c-brand-1);
font-weight: bold;
}
.col-meaning strong {
color: var(--vp-c-text-1);
font-variant-numeric: tabular-nums;
.demo-caption {
font-size: 0.72rem;
color: var(--vp-c-text-3);
margin-top: 0.6rem;
text-align: center;
}
.info-box {
display: flex;
gap: 0.25rem;
background: var(--vp-c-bg-alt);
padding: 0.75rem;
border-radius: 6px;
font-size: 0.85rem;
color: var(--vp-c-text-2);
margin-top: 0.75rem;
}
.info-box strong {
white-space: nowrap;
flex-shrink: 0;
@media (max-width: 600px) {
.gates-grid {
grid-template-columns: repeat(2, 1fr);
}
}
</style>
@@ -1,80 +1,65 @@
<template>
<div class="memory-demo">
<div class="demo-header">
<span class="title">内存管理程序的"工作台"</span>
<span class="subtitle">操作系统如何分配和管理内存</span>
<div class="demo-controls">
<button class="allocate-btn wechat" @click="allocate('wechat')" :disabled="!hasFreeSpace">
+ 给微信分配数据
</button>
<button class="allocate-btn game" @click="allocate('game')" :disabled="!hasFreeSpace">
+ 给游戏分配数据
</button>
<button class="reset-btn" @click="reset">
重置
</button>
</div>
<div class="demo-content">
<div class="memory-visual">
<div class="mem-header">
<span>虚拟内存空间 (4GB)</span>
<span class="used-info">已用: {{ usedMemory }}MB / 4096MB</span>
</div>
<div class="mem-blocks">
<div
v-for="(block, i) in memoryBlocks"
:key="i"
class="mem-block"
:class="{ allocated: block.allocated, selected: selectedBlock === i }"
:style="{ height: block.size + '%' }"
@click="selectedBlock = i"
>
<span
v-if="block.size > 5"
class="block-label"
>{{ block.name }}</span>
<span
v-if="block.size > 8"
class="block-size"
>{{ block.sizeMB }}MB</span>
</div>
</div>
</div>
<div class="memory-info">
<div class="info-section">
<div class="section-title">
内存分配策略
</div>
<div class="strategy-tabs">
<button
v-for="s in strategies"
:key="s.name"
:class="['strat-btn', { active: activeStrategy === s.name }]"
@click="activeStrategy = s.name"
>
{{ s.name }}
</button>
</div>
<div class="strategy-desc">
{{ currentStrategy.desc }}
<div class="system-view">
<!-- 虚拟内存试图 -->
<div class="virtual-cluster">
<div class="process-vm wechat">
<div class="title">💬 微信的虚拟内存<br/>(它认为自己独占了空间)</div>
<div class="vm-blocks">
<div v-for="i in 4" :key="'w'+i" class="block" :class="{ filled: wechatBlocks >= i }">
{{ wechatBlocks >= i ? '数据 ' + i : '虚拟空闲' }}
</div>
</div>
</div>
<div class="info-section">
<div class="section-title">
虚拟内存的作用
</div>
<div class="vm-benefits">
<div
v-for="b in benefits"
:key="b.title"
class="benefit-item"
>
<span class="benefit-icon">{{ b.icon }}</span>
<div class="benefit-content">
<span class="benefit-title">{{ b.title }}</span>
<span class="benefit-desc">{{ b.desc }}</span>
</div>
<div class="process-vm game">
<div class="title">🎮 游戏的虚拟内存<br/>(它也认为自己独占了空间)</div>
<div class="vm-blocks">
<div v-for="i in 4" :key="'g'+i" class="block" :class="{ filled: gameBlocks >= i }">
{{ gameBlocks >= i ? '数据 ' + i : '虚拟空闲' }}
</div>
</div>
</div>
</div>
</div>
<div class="info-box">
<strong>核心思想</strong>虚拟内存让每个进程都以为自己独占整个内存空间实际由操作系统统一管理和映射实现隔离和保护
<!-- OS 页表 (映射表) -->
<div class="os-page-table">
<div class="title">保安大叔 (OS 页表)</div>
<div class="table-info">
当程序存数据时<br/>由我暗中转移到真正的物理缝隙里
</div>
</div>
<!-- 物理内存 -->
<div class="physical-memory">
<div class="title">🗄 真实的物理内存条<br/>(其实像个大杂烩一样乱)</div>
<div class="pm-blocks">
<div
v-for="(block, idx) in physicalBlocks"
:key="'p'+idx"
class="block"
:class="[block.type, { occupied: block.type !== 'empty' }]"
>
{{ block.label }}
</div>
</div>
</div>
</div>
<div class="explanation-box" v-if="wechatBlocks > 0 || gameBlocks > 0">
💡 发现了没尽管右侧真正的物理内存已经被塞得像个狗皮膏药但在左侧的微信和游戏眼里自己的内存条永远是连续且干净的更重要的是微信绝对访问不到橘色的物理块保证了安全
</div>
</div>
</template>
@@ -82,215 +67,273 @@
<script setup>
import { ref, computed } from 'vue'
const selectedBlock = ref(0)
const activeStrategy = ref('首次适应')
const wechatBlocks = ref(0)
const gameBlocks = ref(0)
const memoryBlocks = ref([
{ name: '内核空间', size: 25, allocated: true, sizeMB: 1024 },
{ name: '进程A', size: 15, allocated: true, sizeMB: 600 },
{ name: '空闲', size: 5, allocated: false, sizeMB: 200 },
{ name: '进程B', size: 20, allocated: true, sizeMB: 800 },
{ name: '空闲', size: 10, allocated: false, sizeMB: 400 },
{ name: '进程C', size: 10, allocated: true, sizeMB: 400 },
{ name: '空闲', size: 15, allocated: false, sizeMB: 600 }
])
const strategies = [
{ name: '首次适应', desc: '从内存开始找,找到第一个足够大的空闲块就分配。速度快,但可能产生小碎片。' },
{ name: '最佳适应', desc: '找最小的能满足需求的空闲块。内存利用率高,但可能产生很多小碎片。' },
{ name: '最坏适应', desc: '找最大的空闲块分配。减少小碎片,但大块内存很快用完。' }
//
// empty = , os =
const initialPhysicalBlocks = [
{ type: 'os', label: '系统核心占用' },
{ type: 'empty', label: '空闲' },
{ type: 'os', label: '系统保留' },
{ type: 'empty', label: '空闲' },
{ type: 'empty', label: '空闲' },
{ type: 'empty', label: '空闲' },
{ type: 'os', label: '系统驱动' },
{ type: 'empty', label: '空闲' },
]
const benefits = [
{ icon: '🔒', title: '内存隔离', desc: '进程间互不干扰,一个崩溃不影响其他' },
{ icon: '📦', title: '内存保护', desc: '防止进程访问不该访问的内存区域' },
{ icon: '💾', title: '内存扩展', desc: '用磁盘当内存用,突破物理内存限制' }
]
const physicalBlocks = ref(JSON.parse(JSON.stringify(initialPhysicalBlocks)))
const currentStrategy = computed(() => {
return strategies.find(s => s.name === activeStrategy.value)
const freeSpaceCount = computed(() => {
return physicalBlocks.value.filter(b => b.type === 'empty').length
})
const usedMemory = computed(() => {
return memoryBlocks.value
.filter(b => b.allocated)
.reduce((sum, b) => sum + b.sizeMB, 0)
})
const hasFreeSpace = computed(() => freeSpaceCount.value > 0)
const allocate = (process) => {
if (!hasFreeSpace.value) return
// Find a process block logic
if (process === 'wechat' && wechatBlocks.value < 4) {
wechatBlocks.value++
fillRandomEmptyBlock('wechat', `微信数据 ${wechatBlocks.value}`)
} else if (process === 'game' && gameBlocks.value < 4) {
gameBlocks.value++
fillRandomEmptyBlock('game', `游戏数据 ${gameBlocks.value}`)
}
}
const fillRandomEmptyBlock = (type, label) => {
const emptyIndices = []
physicalBlocks.value.forEach((b, i) => {
if (b.type === 'empty') emptyIndices.push(i)
})
if (emptyIndices.length > 0) {
const randomIndex = emptyIndices[Math.floor(Math.random() * emptyIndices.length)]
physicalBlocks.value[randomIndex] = { type, label }
}
}
const reset = () => {
wechatBlocks.value = 0
gameBlocks.value = 0
physicalBlocks.value = JSON.parse(JSON.stringify(initialPhysicalBlocks))
}
</script>
<style scoped>
.memory-demo {
border: 1px solid var(--vp-c-divider);
border-radius: 8px;
background: var(--vp-c-bg-soft);
padding: 1rem;
margin: 1rem 0;
border: 1px solid var(--vp-c-divider);
border-radius: 12px;
padding: 1.5rem;
margin: 1.5rem 0;
font-family: var(--vp-font-family-base);
}
.demo-header {
display: flex;
align-items: center;
gap: 0.5rem;
margin-bottom: 0.75rem;
}
.demo-header .title { font-weight: bold; font-size: 1rem; }
.demo-header .subtitle { color: var(--vp-c-text-2); font-size: 0.85rem; margin-left: 0.5rem; }
.demo-content {
.demo-controls {
display: flex;
gap: 1rem;
margin-bottom: 2rem;
justify-content: center;
flex-wrap: wrap;
}
.memory-visual {
flex: 1;
min-width: 200px;
.allocate-btn {
color: white;
border: none;
padding: 0.6rem 1.2rem;
border-radius: 8px;
font-weight: 600;
cursor: pointer;
transition: all 0.2s;
}
.allocate-btn:disabled {
opacity: 0.5;
cursor: not-allowed;
filter: grayscale(1);
}
.allocate-btn.wechat {
background: var(--vp-c-success-1);
}
.allocate-btn.wechat:not(:disabled):hover {
filter: brightness(1.1);
}
.allocate-btn.game {
background: var(--vp-c-warning-1);
}
.allocate-btn.game:not(:disabled):hover {
filter: brightness(1.1);
}
.mem-header {
.reset-btn {
background: transparent;
color: var(--vp-c-text-2);
border: 1px solid var(--vp-c-divider);
padding: 0.6rem 1.2rem;
border-radius: 8px;
cursor: pointer;
transition: all 0.2s;
}
.reset-btn:hover {
background: var(--vp-c-bg-mute);
color: var(--vp-c-text-1);
}
.system-view {
display: flex;
justify-content: space-between;
font-size: 0.8rem;
margin-bottom: 0.5rem;
padding: 0.25rem 0.5rem;
background: var(--vp-c-bg-alt);
border-radius: 4px;
align-items: stretch;
gap: 1.5rem;
}
.used-info {
color: var(--vp-c-brand);
@media (max-width: 768px) {
.system-view {
flex-direction: column;
}
}
.title {
font-size: 0.85rem;
font-weight: bold;
text-align: center;
margin-bottom: 1rem;
color: var(--vp-c-text-1);
min-height: 2.5rem;
}
.mem-blocks {
.virtual-cluster {
display: flex;
gap: 1rem;
flex: 2;
}
.process-vm {
flex: 1;
background: var(--vp-c-bg-alt);
border: 2px dashed var(--vp-c-divider);
border-radius: 10px;
padding: 1rem;
}
.vm-blocks {
display: flex;
flex-direction: column;
gap: 2px;
height: 250px;
border: 1px solid var(--vp-c-divider);
border-radius: 4px;
overflow: hidden;
gap: 0.5rem;
}
.mem-block {
.block {
padding: 0.6rem;
border-radius: 6px;
text-align: center;
font-size: 0.8rem;
font-weight: bold;
transition: all 0.3s;
}
.process-vm .block {
background: var(--vp-c-bg-mute);
border: 1px solid var(--vp-c-divider);
color: var(--vp-c-text-3);
opacity: 0.5;
}
.process-vm.wechat .block.filled {
background: rgba(16, 185, 129, 0.15);
border: 1px solid var(--vp-c-success-1);
color: var(--vp-c-success-1);
opacity: 1;
}
.process-vm.game .block.filled {
background: rgba(245, 158, 11, 0.15);
border: 1px solid var(--vp-c-warning-1);
color: var(--vp-c-warning-1);
opacity: 1;
}
.os-page-table {
flex: 1;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
cursor: pointer;
transition: all 0.2s;
background: var(--vp-c-bg-alt);
border-radius: 10px;
padding: 1rem;
position: relative;
border: 2px solid var(--vp-c-brand-1);
box-shadow: 0 4px 12px rgba(0,0,0,0.05);
}
.mem-block.allocated {
background: var(--vp-c-brand-soft);
}
.mem-block:not(.allocated) {
background: var(--vp-c-bg);
border: 1px dashed var(--vp-c-divider);
}
.mem-block.selected {
outline: 2px solid var(--vp-c-brand);
}
.block-label {
font-size: 0.75rem;
font-weight: bold;
}
.block-size {
font-size: 0.65rem;
color: var(--vp-c-text-2);
}
.memory-info {
flex: 1;
min-width: 280px;
}
.info-section {
margin-bottom: 1rem;
}
.section-title {
font-weight: bold;
font-size: 0.9rem;
margin-bottom: 0.5rem;
}
.strategy-tabs {
display: flex;
gap: 0.25rem;
margin-bottom: 0.5rem;
}
.strat-btn {
padding: 0.25rem 0.5rem;
border: 1px solid var(--vp-c-divider);
background: var(--vp-c-bg);
border-radius: 4px;
font-size: 0.75rem;
cursor: pointer;
}
.strat-btn.active {
background: var(--vp-c-brand);
color: white;
border-color: var(--vp-c-brand);
}
.strategy-desc {
.os-page-table .table-info {
font-size: 0.8rem;
color: var(--vp-c-text-2);
text-align: center;
background: var(--vp-c-bg);
padding: 0.5rem;
border-radius: 4px;
padding: 0.8rem;
border-radius: 8px;
}
.vm-benefits {
display: flex;
flex-direction: column;
gap: 0.5rem;
}
.benefit-item {
display: flex;
gap: 0.5rem;
padding: 0.5rem;
background: var(--vp-c-bg);
border-radius: 4px;
}
.benefit-icon {
font-size: 1.2rem;
}
.benefit-content {
display: flex;
flex-direction: column;
}
.benefit-title {
font-weight: bold;
font-size: 0.85rem;
}
.benefit-desc {
font-size: 0.75rem;
color: var(--vp-c-text-2);
}
.info-box {
.physical-memory {
flex: 1;
background: var(--vp-c-bg-alt);
padding: 0.75rem;
border-radius: 6px;
font-size: 0.85rem;
color: var(--vp-c-text-2);
margin-top: 0.75rem;
display: flex;
gap: 0.25rem;
border-radius: 10px;
padding: 1rem;
border: 2px solid var(--vp-c-text-3);
}
.pm-blocks {
display: flex;
flex-direction: column;
gap: 0.4rem;
}
.pm-blocks .block {
padding: 0.5rem;
border-radius: 4px;
background: var(--vp-c-bg);
border: 1px solid var(--vp-c-divider);
color: var(--vp-c-text-3);
font-size: 0.75rem;
}
.pm-blocks .block.os {
background: var(--vp-c-bg-mute);
color: var(--vp-c-text-2);
border-style: dashed;
}
.pm-blocks .block.wechat {
background: var(--vp-c-success-1);
color: white;
border-color: var(--vp-c-success-1);
animation: popIn 0.3s ease-out;
}
.pm-blocks .block.game {
background: var(--vp-c-warning-1);
color: white;
border-color: var(--vp-c-warning-1);
animation: popIn 0.3s ease-out;
}
@keyframes popIn {
0% { transform: scale(0.9); opacity: 0; }
50% { transform: scale(1.05); }
100% { transform: scale(1); opacity: 1; }
}
.explanation-box {
margin-top: 1.5rem;
padding: 1rem;
background: rgba(16, 185, 129, 0.1);
border-left: 4px solid var(--vp-c-success-1);
border-radius: 0 8px 8px 0;
font-size: 0.95rem;
animation: fadeIn 0.5s;
}
@keyframes fadeIn {
from { opacity: 0; transform: translateY(10px); }
to { opacity: 1; transform: translateY(0); }
}
</style>
@@ -1,272 +1,356 @@
<template>
<div class="process-demo">
<div class="demo-header">
<span class="title">进程程序的"分身术"</span>
<span class="subtitle">一个程序如何同时运行多个实例</span>
<div class="controls-section">
<button class="action-btn" :class="{ active: isRunning }" @click="toggleSimulation">
{{ isRunning ? '⏸ 暂停时间片轮转' : '▶️ 启动 CPU' }}
</button>
<div class="speed-control">
<label>时间流速:</label>
<button :class="{ active: speed === 'slow' }" @click="setSpeed('slow')">极慢动作</button>
<button :class="{ active: speed === 'fast' }" @click="setSpeed('fast')">真实速度</button>
</div>
</div>
<div class="demo-content">
<div class="process-list">
<div class="process-header">
<span class="col-name">进程名</span>
<span class="col-pid">PID</span>
<span class="col-state">状态</span>
<span class="col-mem">内存</span>
<div class="cpu-container">
<div class="cpu-core" :class="{ active: isRunning }">
<div class="cpu-title">单核 CPU</div>
<div class="current-task">
<span v-if="activeProcess" class="task-badge">
正在处理: {{ activeProcess.icon }} {{ activeProcess.name }}
</span>
<span v-else class="task-badge idle">
空闲中...
</span>
</div>
</div>
<!-- 连接线动画 -->
<div class="connector">
<div
v-for="p in processes"
:key="p.pid"
class="process-item"
:class="{ running: p.state === '运行中', selected: selectedPid === p.pid }"
@click="selectedPid = p.pid"
>
<span class="col-name">
<span class="process-icon">{{ p.icon }}</span>
{{ p.name }}
</span>
<span class="col-pid">{{ p.pid }}</span>
<span class="col-state">
<span
class="state-badge"
:class="p.state === '运行中' ? 'running' : 'waiting'"
>
{{ p.state }}
</span>
</span>
<span class="col-mem">{{ p.memory }}</span>
</div>
</div>
<div
v-if="selectedProcess"
class="process-detail"
>
<div class="detail-title">
进程详情{{ selectedProcess.name }}
</div>
<div class="detail-grid">
<div class="detail-item">
<span class="label">进程ID (PID)</span>
<span class="value">{{ selectedProcess.pid }}</span>
</div>
<div class="detail-item">
<span class="label">父进程ID</span>
<span class="value">{{ selectedProcess.ppid }}</span>
</div>
<div class="detail-item">
<span class="label">内存占用</span>
<span class="value">{{ selectedProcess.memory }}</span>
</div>
<div class="detail-item">
<span class="label">CPU 占用</span>
<span class="value">{{ selectedProcess.cpu }}%</span>
</div>
</div>
<div class="memory-layout">
<div class="layout-title">
进程内存布局
</div>
<div class="layout-visual">
<div
v-for="seg in memorySegments"
:key="seg.name"
class="segment"
:style="{ height: seg.height }"
>
<span class="seg-name">{{ seg.name }}</span>
</div>
</div>
class="data-flow"
:class="[ `flow-${activeProcessId}`, { running: isRunning }]">
</div>
</div>
</div>
<div class="info-box">
<strong>核心思想</strong>进程是程序的"运行实例"同一个程序可以启动多个进程每个进程有独立的内存空间互不干扰
<div class="processes-grid">
<div
v-for="p in processes"
:key="p.id"
class="process-card"
:class="{ active: p.id === activeProcessId }"
>
<div class="p-header">
<div class="p-title">
<span class="icon">{{ p.icon }}</span>
<span class="name">{{ p.name }}</span>
</div>
<span class="status-badge" :class="p.id === activeProcessId ? 'running' : 'waiting'">
{{ p.id === activeProcessId ? '独占 CPU' : '排队等待' }}
</span>
</div>
<div class="p-progress">
<div class="progress-track">
<div class="progress-fill" :style="{ width: p.progress + '%' }"></div>
</div>
<div class="progress-text">{{ Math.floor(p.progress) }}% 完成</div>
</div>
</div>
</div>
<div class="explanation-box" :class="{ show: isRunning && speed === 'fast' }">
💡 **关键启示**当切换速度足够快时肉眼已经无法分辨谁在等待这也就是为什么只有一个 CPU 核心的电脑依然能让你一边听歌一边打字
</div>
</div>
</template>
<script setup>
import { ref, computed } from 'vue'
import { ref, computed, onUnmounted } from 'vue'
const selectedPid = ref(1001)
const isRunning = ref(false)
const activeProcessId = ref(null)
const speed = ref('slow')
let interval = null
const processes = ref([
{ pid: 1001, name: 'Chrome', icon: '🌐', state: '运行中', memory: '512MB', cpu: 15, ppid: 1 },
{ pid: 1002, name: 'VS Code', icon: '📝', state: '运行中', memory: '384MB', cpu: 8, ppid: 1 },
{ pid: 1003, name: '微信', icon: '💬', state: '等待中', memory: '256MB', cpu: 2, ppid: 1 },
{ pid: 1004, name: '终端', icon: '⬛', state: '等待中', memory: '32MB', cpu: 0, ppid: 1002 },
{ pid: 1005, name: '音乐', icon: '🎵', state: '运行中', memory: '128MB', cpu: 3, ppid: 1 }
{ id: 1, name: '微信接收', icon: '💬', progress: 0 },
{ id: 2, name: '音乐播放', icon: '🎵', progress: 0 },
{ id: 3, name: '游戏渲染', icon: '🎮', progress: 0 }
])
const selectedProcess = computed(() => {
return processes.value.find(p => p.pid === selectedPid.value)
})
const activeProcess = computed(() => processes.value.find(p => p.id === activeProcessId.value))
const memorySegments = [
{ name: '栈区 (Stack)', height: '20%' },
{ name: '堆区 (Heap)', height: '35%' },
{ name: '数据段 (Data)', height: '15%' },
{ name: '代码段 (Text)', height: '30%' }
]
const setSpeed = (s) => {
speed.value = s
if (isRunning.value) {
clearInterval(interval)
startLoop()
}
}
const startLoop = () => {
const switchTime = speed.value === 'slow' ? 1200 : 80; // 1.2s
if (!activeProcessId.value) {
activeProcessId.value = 1
}
interval = setInterval(() => {
//
const curr = processes.value.find(p => p.id === activeProcessId.value)
if (curr) {
curr.progress += (speed.value === 'slow' ? 15 : 4)
if (curr.progress >= 100) curr.progress = 0
}
//
let nextId = activeProcessId.value + 1
if (nextId > 3) nextId = 1
activeProcessId.value = nextId
}, switchTime)
}
const toggleSimulation = () => {
if (isRunning.value) {
clearInterval(interval)
isRunning.value = false
activeProcessId.value = null
} else {
isRunning.value = true
startLoop()
}
}
onUnmounted(() => {
if (interval) clearInterval(interval)
})
</script>
<style scoped>
.process-demo {
border: 1px solid var(--vp-c-divider);
border-radius: 8px;
background: var(--vp-c-bg-soft);
padding: 1rem;
margin: 1rem 0;
border: 1px solid var(--vp-c-divider);
border-radius: 12px;
padding: 1.5rem;
margin: 1.5rem 0;
font-family: var(--vp-font-family-base);
}
.demo-header {
.controls-section {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 2rem;
flex-wrap: wrap;
gap: 1rem;
}
.action-btn {
background: var(--vp-c-brand-1);
color: white;
border: none;
padding: 0.6rem 1.2rem;
border-radius: 8px;
font-weight: 600;
cursor: pointer;
transition: all 0.3s ease;
min-width: 160px;
}
.action-btn.active {
background: var(--vp-c-danger-1);
}
.action-btn:hover {
filter: brightness(1.1);
transform: translateY(-1px);
}
.speed-control {
display: flex;
align-items: center;
gap: 0.5rem;
margin-bottom: 0.75rem;
font-size: 0.9rem;
color: var(--vp-c-text-2);
}
.demo-header .title { font-weight: bold; font-size: 1rem; }
.demo-header .subtitle { color: var(--vp-c-text-2); font-size: 0.85rem; margin-left: 0.5rem; }
.demo-content {
display: flex;
gap: 1rem;
flex-wrap: wrap;
}
.process-list {
flex: 1;
min-width: 280px;
font-size: 0.85rem;
}
.process-header {
display: grid;
grid-template-columns: 2fr 1fr 1.5fr 1fr;
gap: 0.5rem;
padding: 0.5rem;
background: var(--vp-c-bg-alt);
border-radius: 4px;
font-weight: bold;
margin-bottom: 0.25rem;
}
.process-item {
display: grid;
grid-template-columns: 2fr 1fr 1.5fr 1fr;
gap: 0.5rem;
padding: 0.5rem;
border-radius: 4px;
.speed-control button {
background: transparent;
border: 1px solid var(--vp-c-divider);
padding: 0.3rem 0.8rem;
border-radius: 6px;
cursor: pointer;
transition: all 0.2s;
}
.process-item:hover {
background: var(--vp-c-bg);
}
.process-item.selected {
.speed-control button.active {
background: var(--vp-c-brand-soft);
}
.process-icon {
margin-right: 0.25rem;
}
.state-badge {
padding: 0.1rem 0.4rem;
border-radius: 3px;
font-size: 0.75rem;
}
.state-badge.running {
background: var(--vp-c-success);
color: white;
}
.state-badge.waiting {
background: var(--vp-c-divider);
color: var(--vp-c-text-2);
}
.process-detail {
flex: 1;
min-width: 250px;
}
.detail-title {
color: var(--vp-c-brand-1);
border-color: var(--vp-c-brand-1);
font-weight: bold;
font-size: 0.9rem;
margin-bottom: 0.5rem;
}
.detail-grid {
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 0.5rem;
margin-bottom: 0.75rem;
}
.detail-item {
background: var(--vp-c-bg);
padding: 0.5rem;
border-radius: 4px;
}
.detail-item .label {
display: block;
font-size: 0.75rem;
color: var(--vp-c-text-2);
}
.detail-item .value {
font-weight: bold;
font-size: 0.9rem;
}
.memory-layout {
background: var(--vp-c-bg-alt);
padding: 0.5rem;
border-radius: 4px;
}
.layout-title {
font-size: 0.8rem;
font-weight: bold;
margin-bottom: 0.5rem;
}
.layout-visual {
.cpu-container {
display: flex;
flex-direction: column;
gap: 2px;
height: 120px;
align-items: center;
margin-bottom: 2rem;
}
.segment {
.cpu-core {
width: 240px;
height: 90px;
background: var(--vp-c-bg-alt);
border: 2px solid var(--vp-c-divider);
border-radius: 12px;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
box-shadow: 0 4px 12px rgba(0,0,0,0.05);
transition: all 0.3s;
position: relative;
}
.cpu-core.active {
border-color: var(--vp-c-brand-1);
box-shadow: 0 0 20px var(--vp-c-brand-soft);
}
.cpu-title {
font-weight: 800;
font-size: 1.1rem;
color: var(--vp-c-text-1);
margin-bottom: 0.5rem;
letter-spacing: 2px;
}
.current-task {
height: 28px;
display: flex;
align-items: center;
justify-content: center;
background: var(--vp-c-brand-soft);
border-radius: 2px;
}
.task-badge {
background: var(--vp-c-brand-1);
color: white;
padding: 0.2rem 0.8rem;
border-radius: 12px;
font-size: 0.85rem;
font-weight: 600;
}
.task-badge.idle {
background: var(--vp-c-text-3);
}
.seg-name {
font-size: 0.7rem;
/* 连接线动画占位,简化效果,用发亮的虚线替代 */
.connector {
width: 2px;
height: 30px;
background: var(--vp-c-divider);
margin-top: 5px;
position: relative;
}
.processes-grid {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 1rem;
}
@media (max-width: 640px) {
.processes-grid {
grid-template-columns: 1fr;
}
}
.process-card {
background: var(--vp-c-bg-alt);
border: 1px solid var(--vp-c-divider);
border-radius: 10px;
padding: 1rem;
transition: all 0.3s;
position: relative;
overflow: hidden;
}
.process-card.active {
border-color: var(--vp-c-brand-1);
transform: translateY(-2px);
box-shadow: 0 6px 16px var(--vp-c-brand-soft);
}
.process-card.active::before {
content: '';
position: absolute;
top: 0; left: 0; right: 0;
height: 4px;
background: var(--vp-c-brand-1);
}
.p-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 1rem;
}
.p-title {
display: flex;
align-items: center;
gap: 0.4rem;
font-weight: 600;
}
.status-badge {
font-size: 0.75rem;
padding: 0.1rem 0.5rem;
border-radius: 4px;
font-weight: bold;
}
.info-box {
background: var(--vp-c-bg-alt);
padding: 0.75rem;
border-radius: 6px;
font-size: 0.85rem;
.status-badge.waiting {
background: var(--vp-c-bg-soft);
color: var(--vp-c-text-2);
margin-top: 0.75rem;
display: flex;
gap: 0.25rem;
}
.status-badge.running {
background: rgba(16, 185, 129, 0.15);
color: var(--vp-c-success-1);
}
.p-progress {
display: flex;
flex-direction: column;
gap: 0.5rem;
}
.progress-track {
width: 100%;
height: 8px;
background: var(--vp-c-bg-soft);
border-radius: 4px;
overflow: hidden;
}
.progress-fill {
height: 100%;
background: var(--vp-c-brand-1);
transition: width 0.1s linear;
}
.process-card.active .progress-fill {
background: var(--vp-c-success-1);
}
.progress-text {
font-size: 0.75rem;
color: var(--vp-c-text-2);
text-align: right;
font-variant-numeric: tabular-nums;
}
.explanation-box {
margin-top: 1.5rem;
padding: 1rem;
background: rgba(16, 185, 129, 0.1);
border-left: 4px solid var(--vp-c-success-1);
border-radius: 0 8px 8px 0;
font-size: 0.95rem;
opacity: 0;
transform: translateY(10px);
transition: all 0.5s ease;
}
.explanation-box.show {
opacity: 1;
transform: translateY(0);
}
</style>
@@ -1,60 +1,38 @@
<template>
<div class="register-demo">
<div class="demo-header">
<span class="title">寄存器记住一个 0 1 的小单元</span>
<span class="subtitle">只有点写入时才会把当前输入记下来平时改输入不会影响已存的值</span>
</div>
<div class="demo-label">1 位寄存器 只在"写入"时更新存储值</div>
<div class="why-what-box">
<p class="why-p">
<strong>为啥要看这个</strong>CPU 算到一半要暂时记住中间结果寄存器就是干这个的它和直接连线不同改输入不会立刻改变里面存的东西必须主动点一次写入才会更新
</p>
<p class="what-p">
<strong>这些词是啥</strong>
<span class="term">输入</span>你想写进去的 0 1
<span class="term">写入</span>点一下把当前输入锁进寄存器
<span class="term">存储值</span>寄存器里现在记着的数只有写入时才会变
<span class="term">输出</span>从寄存器读出来的数和存储值一样
</p>
</div>
<div class="control-panel">
<label class="ctrl-group">
<span class="ctrl-label">输入</span>
<div class="reg-panel">
<!-- Input -->
<div class="reg-block">
<span class="reg-title">输入</span>
<button
class="input-toggle"
class="toggle-btn"
:class="{ on: inputData === 1 }"
@click="inputData = inputData === 1 ? 0 : 1"
>
{{ inputData }}
</button>
</label>
<button class="write-btn" :class="{ flash: isWriting }" @click="writeOnce">
写入
</button>
<label class="ctrl-group">
<span class="ctrl-label">存储</span>
<span class="stored-val" :class="{ on: storedData === 1 }">{{ storedData }}</span>
</label>
<span class="ctrl-group">
<span class="ctrl-label">输出</span>
<span class="output-val" :class="{ on: storedData === 1 }">{{ storedData }}</span>
</span>
</div>
<div class="visualization-area">
<div class="flow-strip">
<span class="flow-item">输入 {{ inputData }}</span>
<span class="flow-arrow" :class="{ active: isWriting }">{{ isWriting ? '写入中 →' : '— 点「写入」才更新 →' }}</span>
<span class="flow-item flow-store" :class="{ flash: isWriting }"> {{ storedData }}</span>
>{{ inputData }}</button>
</div>
<!-- Write -->
<button class="write-btn" :class="{ flash: isWriting }" @click="writeOnce">
写入
</button>
<!-- Stored -->
<div class="reg-block">
<span class="reg-title">存储</span>
<span class="val-box" :class="{ on: storedData === 1, flash: isWriting }">{{ storedData }}</span>
</div>
<!-- Output -->
<div class="reg-block">
<span class="reg-title">输出</span>
<span class="val-box out" :class="{ on: storedData === 1 }">{{ storedData }}</span>
</div>
<p class="flow-hint">
{{ inputData !== storedData ? '输入和存储不一样:点「写入」会把当前输入记进去。' : '输入和存储已一致。' }}
</p>
</div>
<div class="info-box">
<strong>核心思想</strong>寄存器只在写入那一刻更新其余时间一直保持原来的值所以 CPU 能稳定保存中间结果
<div class="status-line">
{{ inputData !== storedData ? '⚡ 输入≠存储 → 点"写入"即可更新' : '✓ 输入与存储一致' }}
</div>
</div>
</template>
@@ -69,9 +47,7 @@ const isWriting = ref(false)
const writeOnce = () => {
isWriting.value = true
storedData.value = inputData.value
window.setTimeout(() => {
isWriting.value = false
}, 400)
setTimeout(() => { isWriting.value = false }, 400)
}
</script>
@@ -80,75 +56,44 @@ const writeOnce = () => {
border: 1px solid var(--vp-c-divider);
border-radius: 8px;
background: var(--vp-c-bg-soft);
padding: 1rem;
padding: 1rem 1.2rem;
margin: 1rem 0;
}
.demo-header {
margin-bottom: 0.5rem;
}
.demo-header .title {
display: block;
.demo-label {
font-size: 0.78rem;
font-weight: bold;
font-size: 1rem;
}
.demo-header .subtitle {
display: block;
font-size: 0.82rem;
color: var(--vp-c-text-2);
font-weight: normal;
}
.why-what-box {
background: var(--vp-c-bg-alt);
border: 1px solid var(--vp-c-divider);
border-radius: 6px;
padding: 0.65rem 0.85rem;
margin-bottom: 0.75rem;
font-size: 0.85rem;
color: var(--vp-c-text-2);
line-height: 1.55;
letter-spacing: 0.2px;
}
.why-what-box .why-p {
margin: 0 0 0.4rem;
}
.why-what-box .what-p {
margin: 0;
}
.why-what-box .term {
font-weight: 600;
color: var(--vp-c-text-1);
}
.control-panel {
/* ── panel ── */
.reg-panel {
display: flex;
flex-wrap: wrap;
align-items: center;
gap: 0.6rem;
padding: 0.5rem 0.7rem;
flex-wrap: wrap;
padding: 0.6rem 0.8rem;
border: 1px solid var(--vp-c-divider);
border-radius: 8px;
background: var(--vp-c-bg);
margin-bottom: 0.75rem;
}
.ctrl-group {
display: inline-flex;
.reg-block {
display: flex;
flex-direction: column;
align-items: center;
gap: 0.35rem;
gap: 0.25rem;
}
.ctrl-label {
font-size: 0.82rem;
color: var(--vp-c-text-2);
.reg-title {
font-size: 0.7rem;
font-weight: 600;
color: var(--vp-c-text-3);
}
.input-toggle {
.toggle-btn {
width: 2.2rem;
height: 2.2rem;
border-radius: 6px;
@@ -156,26 +101,28 @@ const writeOnce = () => {
background: var(--vp-c-bg-alt);
font-weight: bold;
font-size: 1rem;
font-family: 'JetBrains Mono', monospace;
cursor: pointer;
transition: all 0.2s;
}
.input-toggle.on {
border-color: var(--vp-c-brand);
color: var(--vp-c-brand);
.toggle-btn.on {
border-color: var(--vp-c-brand-1);
color: var(--vp-c-brand-1);
background: var(--vp-c-brand-soft);
}
.write-btn {
padding: 0.35rem 0.8rem;
padding: 0.35rem 0.7rem;
border-radius: 6px;
border: 2px solid var(--vp-c-warning-1, #d97706);
background: var(--vp-c-bg);
color: var(--vp-c-warning-1, #d97706);
font-size: 0.85rem;
font-size: 0.82rem;
font-weight: bold;
cursor: pointer;
transition: all 0.2s;
white-space: nowrap;
}
.write-btn:hover {
@@ -187,93 +134,44 @@ const writeOnce = () => {
color: white;
}
.stored-val,
.output-val {
.val-box {
display: inline-flex;
align-items: center;
justify-content: center;
min-width: 2rem;
height: 2rem;
padding: 0 0.4rem;
width: 2.2rem;
height: 2.2rem;
border-radius: 6px;
border: 2px solid var(--vp-c-divider);
background: var(--vp-c-bg-alt);
font-weight: bold;
font-family: monospace;
font-family: 'JetBrains Mono', monospace;
font-size: 1rem;
transition: all 0.2s;
}
.stored-val.on,
.output-val.on {
border-color: var(--vp-c-brand);
color: var(--vp-c-brand);
.val-box.on {
border-color: var(--vp-c-brand-1);
color: var(--vp-c-brand-1);
background: var(--vp-c-brand-soft);
}
.visualization-area {
margin-bottom: 0.75rem;
.val-box.flash {
box-shadow: 0 0 0 3px var(--vp-c-warning-soft, rgba(217, 119, 6, 0.25));
}
.flow-strip {
display: flex;
flex-wrap: wrap;
align-items: center;
gap: 0.4rem;
padding: 0.6rem 0.8rem;
border: 1px solid var(--vp-c-divider);
border-radius: 8px;
background: var(--vp-c-bg);
font-size: 0.9rem;
.val-box.out {
border-style: dashed;
}
.flow-item {
font-weight: bold;
}
.flow-store {
color: var(--vp-c-brand);
}
.flow-store.flash {
box-shadow: 0 0 0 2px var(--vp-c-warning-1);
border-radius: 4px;
}
.flow-arrow {
.status-line {
margin-top: 0.5rem;
font-size: 0.78rem;
color: var(--vp-c-text-3);
font-size: 0.82rem;
}
.flow-arrow.active {
color: var(--vp-c-warning-1);
font-weight: bold;
}
.flow-hint {
margin: 0.4rem 0 0;
font-size: 0.82rem;
color: var(--vp-c-text-2);
}
.info-box {
display: flex;
gap: 0.25rem;
background: var(--vp-c-bg-alt);
padding: 0.75rem;
border-radius: 6px;
font-size: 0.85rem;
color: var(--vp-c-text-2);
}
.info-box strong {
white-space: nowrap;
flex-shrink: 0;
}
@media (max-width: 520px) {
.control-panel {
flex-direction: column;
align-items: flex-start;
@media (max-width: 480px) {
.reg-panel {
justify-content: center;
}
}
</style>
@@ -0,0 +1,168 @@
<template>
<div class="sand-demo">
<div class="demo-label">从沙子到智能 每一层都是对下一层的封装</div>
<div class="layers">
<div
v-for="(layer, i) in layers" :key="i"
class="layer-row"
:class="{ active: activeLayer === i }"
@mouseenter="activeLayer = i"
@mouseleave="activeLayer = null"
>
<div class="layer-num">{{ i + 1 }}</div>
<div class="layer-icon">{{ layer.icon }}</div>
<div class="layer-body">
<div class="layer-name">{{ layer.name }}</div>
<div class="layer-desc">{{ layer.desc }}</div>
</div>
<div class="layer-scale">{{ layer.scale }}</div>
<div v-if="i < layers.length - 1" class="arrow-down">
<span class="arrow-label">{{ layer.arrow }}</span>
</div>
</div>
</div>
<div class="demo-caption">层层抽象封装最底层的物理材料最终变成通用计算平台</div>
</div>
</template>
<script setup>
import { ref } from 'vue'
const activeLayer = ref(null)
const layers = [
{ icon: '🏖️', name: '沙子(硅)', desc: '地球上最丰富的元素之一,提炼出高纯度硅', scale: '原材料', arrow: '提纯 → 切割' },
{ icon: '💿', name: '硅晶圆', desc: '直径约 30cm 的单晶硅片,表面极其光滑', scale: '基底', arrow: '光刻 → 蚀刻 → 掺杂' },
{ icon: '🔌', name: '晶体管(开关)', desc: 'Gate=1 导通,Gate=0 断开,用电压控制电流', scale: '数百亿 / 芯片', arrow: '组合成逻辑电路' },
{ icon: '🔲', name: '逻辑门', desc: 'AND / OR / NOT / XOR,实现基本布尔运算', scale: '数十亿', arrow: '组合成功能模块' },
{ icon: '⚙️', name: '功能单元', desc: '加法器、寄存器、多路选择器……各司其职', scale: '数百个', arrow: '集成为处理器' },
{ icon: '🧠', name: 'CPU 核心', desc: 'ALU + 控制器 + 寄存器组,取指→解码→执行→写回', scale: '1128 核', arrow: '软件编程' },
{ icon: '🚀', name: '软件应用', desc: '操作系统 / AI / 游戏 / 网页……一切皆指令', scale: '无限可能', arrow: '' },
]
</script>
<style scoped>
.sand-demo {
border: 1px solid var(--vp-c-divider);
border-radius: 8px;
background: var(--vp-c-bg-soft);
padding: 1rem 1.2rem;
margin: 1rem 0;
}
.demo-label {
font-size: 0.78rem;
font-weight: bold;
color: var(--vp-c-text-2);
margin-bottom: 0.75rem;
letter-spacing: 0.2px;
}
.layers {
display: flex;
flex-direction: column;
gap: 0;
}
.layer-row {
display: flex;
align-items: center;
gap: 0.6rem;
padding: 0.55rem 0.7rem;
border-radius: 6px;
position: relative;
transition: all 0.15s;
cursor: default;
}
.layer-row:hover,
.layer-row.active {
background: var(--vp-c-bg);
}
.layer-num {
width: 1.4rem;
height: 1.4rem;
border-radius: 50%;
background: var(--vp-c-bg-alt);
border: 1px solid var(--vp-c-divider);
display: flex;
align-items: center;
justify-content: center;
font-size: 0.65rem;
font-weight: bold;
color: var(--vp-c-text-3);
flex-shrink: 0;
}
.layer-row.active .layer-num {
background: var(--vp-c-brand-soft);
border-color: var(--vp-c-brand-1);
color: var(--vp-c-brand-1);
}
.layer-icon {
font-size: 1.2rem;
flex-shrink: 0;
}
.layer-body {
flex: 1;
min-width: 0;
}
.layer-name {
font-size: 0.88rem;
font-weight: bold;
color: var(--vp-c-text-1);
line-height: 1.3;
}
.layer-desc {
font-size: 0.75rem;
color: var(--vp-c-text-3);
line-height: 1.4;
}
.layer-scale {
font-size: 0.68rem;
font-weight: 600;
color: var(--vp-c-text-3);
white-space: nowrap;
flex-shrink: 0;
background: var(--vp-c-bg-alt);
padding: 0.15rem 0.45rem;
border-radius: 4px;
}
/* ── arrow between layers ── */
.arrow-down {
position: absolute;
left: 1.1rem;
bottom: -0.55rem;
z-index: 1;
}
.arrow-label {
font-size: 0.6rem;
color: var(--vp-c-text-3);
background: var(--vp-c-bg-soft);
padding: 0 0.3rem;
white-space: nowrap;
}
.demo-caption {
font-size: 0.72rem;
color: var(--vp-c-text-3);
margin-top: 0.6rem;
text-align: center;
}
@media (max-width: 600px) {
.layer-scale {
display: none;
}
}
</style>
@@ -1,47 +1,45 @@
<template>
<div class="transistor-demo">
<div class="demo-header">
<span class="title">晶体管数字世界的开关</span>
<span class="subtitle">Gate 电压决定电流能否通过</span>
</div>
<div class="demo-label">MOSFET 晶体管示意 点击切换 Gate 电压</div>
<div class="states">
<div class="state-card">
<div class="state-label">Gate = 0低电压</div>
<div class="channel-row">
<span class="terminal">源极</span>
<div class="channel-track off">
<span class="block-icon"> 断开</span>
</div>
<span class="terminal">漏极</span>
</div>
<div class="output-line">输出<strong>0</strong></div>
<div class="schematic" @click="gateOn = !gateOn">
<!-- Source terminal -->
<div class="terminal-box source">
<span class="pin-label">源极<br><span class="en">Source</span></span>
<div class="pin-wire" :class="{ active: gateOn }"></div>
</div>
<div class="state-card">
<div class="state-label">Gate = 1高电压</div>
<div class="channel-row">
<span class="terminal">源极</span>
<div class="channel-track on">
<span class="flow-dot d1" />
<span class="flow-dot d2" />
<span class="flow-dot d3" />
<span class="flow-label">导通</span>
</div>
<span class="terminal">漏极</span>
<!-- Channel -->
<div class="channel-area">
<div class="gate-indicator" :class="{ on: gateOn }">
<span class="gate-label">Gate</span>
<span class="gate-val">{{ gateOn ? '1' : '0' }}</span>
</div>
<div class="output-line">输出<strong>1</strong></div>
<div class="channel-bar" :class="{ conducting: gateOn }">
<template v-if="gateOn">
<span class="electron e1"></span>
<span class="electron e2"></span>
<span class="electron e3"></span>
</template>
<span v-else class="block-mark"></span>
</div>
<div class="channel-status">{{ gateOn ? '导通 → 输出 1' : '断开 → 输出 0' }}</div>
</div>
<!-- Drain terminal -->
<div class="terminal-box drain">
<div class="pin-wire" :class="{ active: gateOn }"></div>
<span class="pin-label">漏极<br><span class="en">Drain</span></span>
</div>
</div>
<div class="info-box">
<strong>核心思想</strong>晶体管是电控开关Gate=1 导通Gate=0 断开所有数字计算都建立在这种 0/1 开关之上
</div>
<div class="tap-hint">👆 点击切换 Gate 电压</div>
</div>
</template>
<script setup>
//
import { ref } from 'vue'
const gateOn = ref(false)
</script>
<style scoped>
@@ -49,149 +47,168 @@
border: 1px solid var(--vp-c-divider);
border-radius: 8px;
background: var(--vp-c-bg-soft);
padding: 1rem;
padding: 1rem 1.2rem;
margin: 1rem 0;
cursor: pointer;
user-select: none;
}
.demo-header {
display: flex;
align-items: center;
gap: 0.5rem;
margin-bottom: 0.75rem;
}
.demo-header .title {
.demo-label {
font-size: 0.78rem;
font-weight: bold;
font-size: 1rem;
}
.demo-header .subtitle {
color: var(--vp-c-text-2);
font-size: 0.85rem;
margin-left: 0.5rem;
}
.states {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 0.75rem;
margin-bottom: 0.75rem;
letter-spacing: 0.2px;
}
.state-card {
border: 1px solid var(--vp-c-divider);
border-radius: 8px;
background: var(--vp-c-bg);
padding: 0.75rem;
}
.state-label {
font-size: 0.85rem;
color: var(--vp-c-text-2);
margin-bottom: 0.5rem;
}
.channel-row {
/* ── layout ── */
.schematic {
display: flex;
align-items: center;
gap: 0.5rem;
justify-content: center;
gap: 0;
}
.terminal {
font-size: 0.82rem;
/* ── terminals ── */
.terminal-box {
display: flex;
align-items: center;
gap: 0;
}
.pin-label {
font-size: 0.78rem;
font-weight: 600;
color: var(--vp-c-text-2);
flex-shrink: 0;
line-height: 1.3;
text-align: center;
}
.channel-track {
flex: 1;
min-height: 2.5rem;
.pin-label .en {
font-size: 0.65rem;
color: var(--vp-c-text-3);
font-weight: normal;
}
.pin-wire {
width: 2.5rem;
height: 3px;
background: var(--vp-c-divider);
transition: background 0.3s;
}
.pin-wire.active {
background: var(--vp-c-brand-1);
box-shadow: 0 0 6px var(--vp-c-brand-soft);
}
/* ── channel ── */
.channel-area {
display: flex;
flex-direction: column;
align-items: center;
gap: 0.35rem;
min-width: 7rem;
}
.gate-indicator {
display: flex;
align-items: center;
gap: 0.35rem;
padding: 0.25rem 0.65rem;
border-radius: 6px;
border: 2px solid var(--vp-c-divider);
background: var(--vp-c-bg);
transition: all 0.3s;
}
.gate-indicator.on {
border-color: var(--vp-c-brand-1);
background: var(--vp-c-brand-soft);
}
.gate-label {
font-size: 0.72rem;
font-weight: 600;
color: var(--vp-c-text-2);
}
.gate-val {
font-family: 'JetBrains Mono', monospace;
font-size: 1rem;
font-weight: bold;
color: var(--vp-c-text-1);
transition: color 0.3s;
}
.gate-indicator.on .gate-val {
color: var(--vp-c-brand-1);
}
.channel-bar {
width: 100%;
height: 2rem;
border: 2px solid var(--vp-c-divider);
border-radius: 6px;
background: var(--vp-c-bg-alt);
display: flex;
align-items: center;
justify-content: center;
position: relative;
overflow: hidden;
transition: all 0.3s;
}
.channel-track.off {
background: var(--vp-c-bg-alt);
.channel-bar.conducting {
background: var(--vp-c-success-soft, rgba(22, 163, 74, 0.12));
border-color: var(--vp-c-success, #16a34a);
}
.channel-track.on {
background: var(--vp-c-success-soft);
border-color: var(--vp-c-success);
}
.block-icon {
.block-mark {
font-size: 0.9rem;
font-weight: bold;
color: var(--vp-c-text-3);
}
.flow-dot {
width: 0.4rem;
height: 0.4rem;
.electron {
width: 6px;
height: 6px;
border-radius: 50%;
background: var(--vp-c-success);
background: var(--vp-c-success, #16a34a);
position: absolute;
left: -10%;
animation: flow 1.5s linear infinite;
animation: flow 1.2s linear infinite;
}
.flow-dot.d2 {
animation-delay: 0.45s;
}
.flow-dot.d3 {
animation-delay: 0.9s;
}
.flow-label {
margin-left: 0.4rem;
font-size: 0.85rem;
color: var(--vp-c-success-1);
font-weight: 600;
}
.electron.e2 { animation-delay: 0.4s; }
.electron.e3 { animation-delay: 0.8s; }
@keyframes flow {
from {
left: -10%;
}
to {
left: 105%;
}
0% { left: -8%; opacity: 0; }
10% { opacity: 1; }
90% { opacity: 1; }
100% { left: 108%; opacity: 0; }
}
.output-line {
margin-top: 0.5rem;
font-size: 0.9rem;
color: var(--vp-c-text-1);
}
.output-line strong {
color: var(--vp-c-brand-1);
}
.info-box {
display: flex;
gap: 0.25rem;
background: var(--vp-c-bg-alt);
padding: 0.75rem;
border-radius: 6px;
font-size: 0.85rem;
.channel-status {
font-size: 0.8rem;
font-weight: 600;
color: var(--vp-c-text-2);
transition: color 0.3s;
}
.info-box strong {
white-space: nowrap;
flex-shrink: 0;
.channel-bar.conducting + .channel-status {
color: var(--vp-c-success, #16a34a);
}
@media (max-width: 640px) {
.states {
grid-template-columns: 1fr;
}
.tap-hint {
text-align: center;
font-size: 0.72rem;
color: var(--vp-c-text-3);
margin-top: 0.6rem;
}
@media (max-width: 480px) {
.pin-wire { width: 1.5rem; }
.channel-area { min-width: 5rem; }
}
</style>
@@ -6,13 +6,19 @@
<div class="mode-buttons">
<button
:class="['mode-btn', { active: mode === 'serial' }]"
@click="mode = 'serial'; reset()"
@click="
mode = 'serial';
reset()
"
>
串行传输现代
</button>
<button
:class="['mode-btn', { active: mode === 'parallel' }]"
@click="mode = 'parallel'; reset()"
@click="
mode = 'parallel';
reset()
"
>
并行传输旧时代
</button>
@@ -31,7 +37,7 @@
:key="i"
class="bit"
:class="{ sent: sentBits.includes(i) }"
>{{ bit }}</span>
>{{ bit }}</span>
</div>
</div>
@@ -45,7 +51,7 @@
:key="'p' + i"
class="particle"
:style="{ left: p.progress + '%', top: '50%' }"
>{{ p.bit }}</span>
>{{ p.bit }}</span>
</div>
</div>
<div v-if="mode === 'parallel'" class="wire-group parallel-group">
@@ -55,7 +61,7 @@
v-if="parallelParticle && parallelParticle.lane === l - 1"
class="particle"
:style="{ left: parallelParticle.progress + '%', top: '50%' }"
>{{ parallelBits[l - 1] || '·' }}</span>
>{{ parallelBits[l - 1] || '·' }}</span>
</div>
</div>
</div>
@@ -69,9 +75,13 @@
v-for="(bit, i) in receivedBits"
:key="'r' + i"
class="bit received"
>{{ bit }}</span>
>{{ bit }}</span>
</div>
<div v-if="checksumResult !== null" class="checksum-badge" :class="checksumResult ? 'ok' : 'fail'">
<div
v-if="checksumResult !== null"
class="checksum-badge"
:class="checksumResult ? 'ok' : 'fail'"
>
{{ checksumResult ? '✓ 校验通过' : '✕ 校验失败' }}
</div>
</div>
@@ -85,7 +95,9 @@
</div>
<div class="status-item">
<span class="s-label">传输速率</span>
<span class="s-val">{{ mode === 'serial' ? '1 位/次' : '8 位/次' }}</span>
<span class="s-val">{{
mode === 'serial' ? '1 位/次' : '8 位/次'
}}</span>
</div>
<div class="status-item">
<span class="s-label">状态</span>
@@ -99,8 +111,10 @@
</button>
<div class="note-box">
<strong>提示等等串行不是更慢吗</strong><br>
表面上是的但现代串行接口USB 4PCIe传输频率高达每秒 <strong>数百亿次</strong>而并行线路之间会产生 <em>信号串扰Crosstalk</em>反而限制了速度所以高速接口全面转向了串行
<strong>提示等等串行不是更慢吗</strong><br />
表面上是的但现代串行接口USB 4PCIe传输频率高达每秒
<strong>数百亿次</strong>而并行线路之间会产生
<em>信号串扰Crosstalk</em>反而限制了速度所以高速接口全面转向了串行
</div>
</div>
</template>
@@ -109,7 +123,7 @@
import { ref, computed } from 'vue'
const mode = ref('serial')
const dataBits = ref([1,0,1,1,0,0,1,0]) // "Hello" first byte 0b10110010
const dataBits = ref([1, 0, 1, 1, 0, 0, 1, 0]) // "Hello" first byte 0b10110010
const sentBits = ref([])
const receivedBits = ref([])
const particles = ref([])
@@ -141,7 +155,9 @@ const statusColor = computed(() => {
return ''
})
function sleep(ms) { return new Promise(r => setTimeout(r, ms)) }
function sleep(ms) {
return new Promise((r) => setTimeout(r, ms))
}
async function send() {
if (isSending.value) return
@@ -156,7 +172,7 @@ async function send() {
// Checksum simulation
await sleep(400)
checksumResult.value = true // always pass in demo
checksumResult.value = true // always pass in demo
isSending.value = false
}
@@ -171,7 +187,7 @@ async function sendSerial() {
p.progress = prog
await sleep(35)
}
particles.value = particles.value.filter(x => x !== p)
particles.value = particles.value.filter((x) => x !== p)
receivedBits.value.push(bit)
await sleep(30)
}
@@ -181,7 +197,10 @@ async function sendParallel() {
sentBits.value = dataBits.value.map((_, i) => i)
parallelBits.value = [...dataBits.value]
for (let prog = 0; prog <= 100; prog += 8) {
parallelParticle.value = { progress: prog, lane: Math.floor(Math.random() * 8) }
parallelParticle.value = {
progress: prog,
lane: Math.floor(Math.random() * 8)
}
await sleep(40)
}
parallelParticle.value = null
@@ -249,10 +268,17 @@ async function sendParallel() {
width: 100px;
}
.device-icon { font-size: 2rem; }
.device-label { font-size: 0.8rem; font-weight: bold; color: var(--vp-c-text-2); }
.device-icon {
font-size: 2rem;
}
.device-label {
font-size: 0.8rem;
font-weight: bold;
color: var(--vp-c-text-2);
}
.data-bits, .received-bits {
.data-bits,
.received-bits {
display: flex;
flex-wrap: wrap;
gap: 2px;
@@ -273,8 +299,15 @@ async function sendParallel() {
transition: all 0.2s;
}
.bit.sent { background: var(--vp-c-brand-soft); border-color: var(--vp-c-brand); }
.bit.received { background: #d1fae5; border-color: #059669; color: #065f46; }
.bit.sent {
background: var(--vp-c-brand-soft);
border-color: var(--vp-c-brand);
}
.bit.received {
background: #d1fae5;
border-color: #059669;
color: #065f46;
}
.checksum-badge {
margin-top: 4px;
@@ -283,8 +316,14 @@ async function sendParallel() {
border-radius: 4px;
font-weight: bold;
}
.checksum-badge.ok { background: #d1fae5; color: #065f46; }
.checksum-badge.fail { background: #fee2e2; color: #991b1b; }
.checksum-badge.ok {
background: #d1fae5;
color: #065f46;
}
.checksum-badge.fail {
background: #fee2e2;
color: #991b1b;
}
/* Wires */
.wire-container {
@@ -311,8 +350,14 @@ async function sendParallel() {
overflow: hidden;
}
.wire-group.serial .wire { height: 20px; }
.parallel-group { display: flex; flex-direction: column; gap: 2px; }
.wire-group.serial .wire {
height: 20px;
}
.parallel-group {
display: flex;
flex-direction: column;
gap: 2px;
}
.particle {
position: absolute;
@@ -337,11 +382,25 @@ async function sendParallel() {
padding: 0.6rem 0.85rem;
}
.status-item { display: flex; flex-direction: column; gap: 2px; }
.s-label { font-size: 0.72rem; color: var(--vp-c-text-3); }
.s-val { font-size: 0.88rem; font-weight: bold; }
.s-val.green { color: #059669; }
.s-val.yellow { color: #d97706; }
.status-item {
display: flex;
flex-direction: column;
gap: 2px;
}
.s-label {
font-size: 0.72rem;
color: var(--vp-c-text-3);
}
.s-val {
font-size: 0.88rem;
font-weight: bold;
}
.s-val.green {
color: #059669;
}
.s-val.yellow {
color: #d97706;
}
.send-btn {
padding: 0.5rem 1.2rem;
@@ -356,7 +415,10 @@ async function sendParallel() {
align-self: flex-start;
}
.send-btn:disabled { opacity: 0.6; cursor: not-allowed; }
.send-btn:disabled {
opacity: 0.6;
cursor: not-allowed;
}
.note-box {
background: var(--vp-c-bg-alt);
@@ -35,8 +35,10 @@ const scopes = [
const updateExplanation = () => {
const texts = {
global: '在全局作用域,只能使用全局变量 appName',
function: '在函数作用域,可以使用自己的 message 和全局的 appName(作用域链查找)',
block: '在块级作用域,可以使用自己的 greeting,以及外层的 message 和 appName'
function:
'在函数作用域,可以使用自己的 message 和全局的 appName(作用域链查找)',
block:
'在块级作用域,可以使用自己的 greeting,以及外层的 message 和 appName'
}
explanation.value = texts[activeScope.value]
}
@@ -68,13 +70,13 @@ updateExplanation()
v-for="scope in scopes"
:key="scope.id"
class="level"
:class="{ active: activeScope === scope.id, dimmed: activeScope !== scope.id }"
:class="{
active: activeScope === scope.id,
dimmed: activeScope !== scope.id
}"
:style="{ borderLeftColor: scope.color }"
>
<div
class="level-header"
:style="{ color: scope.color }"
>
<div class="level-header" :style="{ color: scope.color }">
{{ scope.name }}
</div>
<div class="level-vars">
@@ -86,10 +88,7 @@ updateExplanation()
>
<span class="var-name">{{ v.name }}</span>
<span class="var-value">= {{ v.value }}</span>
<span
v-if="!v.own"
class="var-from"
> {{ v.from }}</span>
<span v-if="!v.own" class="var-from"> {{ v.from }}</span>
</div>
</div>
</div>
@@ -97,9 +96,7 @@ updateExplanation()
<!-- 说明 -->
<div class="explanation-box">
<div class="explanation-title">
💡 当前位置可见的变量
</div>
<div class="explanation-title">💡 当前位置可见的变量</div>
<div class="explanation-text">
{{ explanation }}
</div>
@@ -86,14 +86,18 @@ const bestPractices = ref([
const codeComparisons = ref([
{
scenario: '函数返回值',
withInference: 'function add(a: number, b: number) {\n return a + b // 推断为 number\n}',
withAnnotation: 'function add(a: number, b: number): number {\n return a + b\n}',
withInference:
'function add(a: number, b: number) {\n return a + b // 推断为 number\n}',
withAnnotation:
'function add(a: number, b: number): number {\n return a + b\n}',
recommendation: '推荐使用推断'
},
{
scenario: '复杂对象',
withInference: 'const user = {\n name: "张三",\n age: 25,\n email: "test@example.com"\n} // 类型自动推断',
withAnnotation: 'interface User {\n name: string\n age: number\n email: string\n}\n\nconst user: User = { ... }',
withInference:
'const user = {\n name: "张三",\n age: 25,\n email: "test@example.com"\n} // 类型自动推断',
withAnnotation:
'interface User {\n name: string\n age: number\n email: string\n}\n\nconst user: User = { ... }',
recommendation: '复杂结构建议用接口'
}
])
@@ -107,12 +111,13 @@ const codeComparisons = ref([
<!-- 概念说明 -->
<div class="concept-section">
<div class="concept-card">
<div class="concept-icon">
🧠
</div>
<div class="concept-icon">🧠</div>
<div class="concept-content">
<h4>什么是类型推断</h4>
<p>TypeScript 很聪明它能根据你写的代码自动推断出变量的类型不需要每次都手动标注</p>
<p>
TypeScript
很聪明它能根据你写的代码自动推断出变量的类型不需要每次都手动标注
</p>
</div>
</div>
</div>
@@ -124,15 +129,16 @@ const codeComparisons = ref([
<div
v-for="example in codeExamples"
:key="example.id"
:class="['example-card', { active: currentExample.id === example.id }]"
:class="[
'example-card',
{ active: currentExample.id === example.id }
]"
@click="selectExample(example)"
>
<div class="example-code">
{{ example.code }}
</div>
<div class="example-type">
{{ example.inferredType }}
</div>
<div class="example-type"> {{ example.inferredType }}</div>
</div>
</div>
</div>
@@ -148,9 +154,7 @@ const codeComparisons = ref([
<pre><code class="typescript">{{ currentExample.code }}</code></pre>
</div>
<div class="inference-arrow">
</div>
<div class="inference-arrow"></div>
<div class="type-panel">
<div class="panel-header">
@@ -164,9 +168,7 @@ const codeComparisons = ref([
</div>
<div class="explanation">
<div class="explanation-icon">
💡
</div>
<div class="explanation-icon">💡</div>
<div class="explanation-text">
{{ currentExample.explanation }}
</div>
@@ -183,16 +185,8 @@ const codeComparisons = ref([
<!-- 操作按钮 -->
<div class="controls">
<button
class="btn-danger"
@click="tryTypeError"
>
尝试类型错误
</button>
<button
class="btn-secondary"
@click="showError = false; errorMessage = ''"
>
<button class="btn-danger" @click="tryTypeError">尝试类型错误</button>
<button class="btn-secondary" @click="showError = false; errorMessage = ''">
清除消息
</button>
</div>
@@ -210,10 +204,7 @@ const codeComparisons = ref([
{{ practice.title }}
</div>
<ul class="practice-list">
<li
v-for="(item, i) in practice.items"
:key="i"
>
<li v-for="(item, i) in practice.items" :key="i">
{{ item }}
</li>
</ul>
@@ -234,15 +225,11 @@ const codeComparisons = ref([
</div>
<div class="comparison-codes">
<div class="comparison-code">
<div class="code-label">
使用推断
</div>
<div class="code-label">使用推断</div>
<pre><code class="typescript">{{ comparison.withInference }}</code></pre>
</div>
<div class="comparison-code">
<div class="code-label">
显式注解
</div>
<div class="code-label">显式注解</div>
<pre><code class="typescript">{{ comparison.withAnnotation }}</code></pre>
</div>
</div>
@@ -265,7 +252,8 @@ const codeComparisons = ref([
background: var(--vp-c-bg);
}
h3, h4 {
h3,
h4 {
margin: 0 0 16px 0;
font-weight: 600;
color: var(--vp-c-text-1);
@@ -380,7 +368,8 @@ h4 {
}
}
.code-panel, .type-panel {
.code-panel,
.type-panel {
flex: 1;
border: 2px solid var(--vp-c-border);
border-radius: 8px;
@@ -398,7 +387,8 @@ h4 {
gap: 8px;
}
.code-icon, .type-icon {
.code-icon,
.type-icon {
font-size: 16px;
}
+8
View File
@@ -559,6 +559,10 @@ import RestfulUrlDemo from './components/appendix/api-design/RestfulUrlDemo.vue'
import StatusCodeDemo from './components/appendix/api-design/StatusCodeDemo.vue'
import ErrorHandlingDemo from './components/appendix/api-design/ErrorHandlingDemo.vue'
import ApiVersioningDemo from './components/appendix/api-design/ApiVersioningDemo.vue'
import ApiStyleCompare from './components/appendix/api-design/ApiStyleCompare.vue'
import ResponseStructureDemo from './components/appendix/api-design/ResponseStructureDemo.vue'
import DataFieldDesignDemo from './components/appendix/api-design/DataFieldDesignDemo.vue'
import ErrorResponseDesignDemo from './components/appendix/api-design/ErrorResponseDesignDemo.vue'
// JavaScript Intro Components
import VariableBoxDemo from './components/appendix/javascript-intro/VariableBoxDemo.vue'
@@ -1172,6 +1176,10 @@ export default {
app.component('StatusCodeDemo', StatusCodeDemo)
app.component('ErrorHandlingDemo', ErrorHandlingDemo)
app.component('ApiVersioningDemo', ApiVersioningDemo)
app.component('ApiStyleCompare', ApiStyleCompare)
app.component('ResponseStructureDemo', ResponseStructureDemo)
app.component('DataFieldDesignDemo', DataFieldDesignDemo)
app.component('ErrorResponseDesignDemo', ErrorResponseDesignDemo)
// Database Intro Extra Components Registration
app.component('DatabaseEvolutionDemo', DatabaseEvolutionDemo)
@@ -1,625 +1,84 @@
# 网络:两台电脑如何对话
# 计算机网络:从输入网址到返回结果的过程
::: tip 🎯 核心问题
**当你在浏览器输入 www.baidu.com 并按下回车,到底发生了什么?** 这个简单动作背后,其实隐藏着一个庞大的"快递系统":从填写订单(URL)到查询地址簿(DNS),从建立运输通道(TCP)到快递员送货(HTTP),最终在你屏幕上展示(渲染)。本章带你完整理解这个神奇的过程。
**当你在浏览器输入 www.google.com 并按下回车到底发生了什么**
这个看似简单的动作,背后隐藏着一个庞大精密的跨国“快递系统”。从填写订单(URL解析)到查询地址簿(DNS解析),从建立运输通道(TCP握手)到快递员送货(HTTP请求与响应),最终在你屏幕上拆开包裹组装(浏览器渲染)。本章带你零基础、完整理解这个神奇的过程。
:::
---
## 0. 五层模型总览:快递公司的组织架构
## 全景演示:网络世界的快递系统
现代计算机网络就像一个**快递公司**,采用五层分层模型,每层负责不同的工作:
你可以通过下方的交互组件,直观地体验从输入网址到看到网页的 5 个关键步骤。先自己点一点,然后再看底下的详细解释!
<CFNetworkLayers />
::: tip 💡 为什么需要分层?
想象一个没有分工的快递公司:
- **每个人什么都干**:接电话、分拣、打包、开车送货...
- **效率极低**:没人专精,什么都做不好
- **难以扩展**:想加个"航空运输",所有员工都要重新培训
**分层设计**解决了这些问题:
- **模块化**:每层独立设计和实现,改一层不影响其他层
- **易维护**:网络慢了?查物理层和数据链路层;安全问题?查应用层
- **标准化**:统一的接口和协议,不同厂商的设备能互相通信
- **可扩展**:新技术可以替换某一层,比如从铜线换成光纤,只需改物理层
:::
| 层级 | 技术名称 | 快递公司类比 | 核心职责 | 常见协议/设备 |
| ----- | ---------- | ---------------- | ---------------------------------------- | ------------------ |
| **5** | 应用层 | **客户服务部门** | 处理具体业务(网页、邮件、文件传输) | HTTP, FTP, SMTP |
| **4** | 传输层 | **包裹分拣组** | 确保包裹可靠送达(_TCP_)或快速送达(_UDP_) | TCP, UDP |
| **3** | 网络层 | **路由规划部** | 规划最佳运输路线,选择走哪条路 | IP, 路由器 |
| **2** | 数据链路层 | **车队管理** | 管理车辆之间的通信,MAC 地址寻址 | 以太网, 交换机 |
| **1** | 物理层 | **道路和车辆** | 实际的物理传输(电缆、光纤、无线电波) | 网线, 光纤, 无线电 |
::: tip 📊 逐行解读这张表
**第5层(应用层)**:这是你直接接触的层。浏览器打开网页、邮件客户端收发邮件,都是在调用这一层的服务。它负责处理"具体的业务逻辑"。
**第4层(传输层)**:应用层把数据给它,它负责决定用什么方式"寄送"。TCP 像挂号信(可靠但慢),UDP 像平信(快但可能丢)。这一层用**端口号**区分不同的应用程序。
**第3层(网络层)**:这是"全球定位系统"层。IP 地址就在这一层,路由器根据 IP 地址规划路线:"从北京到上海,应该走哪条高速公路?"
**第2层(数据链路层)**:这一层负责"两站之间"的运输。就像快递车从北京分拣中心开到天津分拣中心,这一段路的通信规则由数据链路层规定。MAC 地址(设备身份证)也在这一层。
**第1层(物理层)**:这是最底层,实实在在的物理介质。网线里的电信号、光纤里的光信号、Wi-Fi 的无线电波,都是物理层负责的。
:::
<UrlToBrowserDemo />
---
## 1. 物理层:道路和车辆
## 1. 填写购物单 (URL 解析)
### 1.1 基本概念
当你在浏览器的地址栏中输入 `https://www.google.com` 这样一段地址并按下回车,这就像是你准备去商店买东西,首先要在**购物单**上写清楚:
::: tip 💡 物理层是什么?
物理层负责在物理介质上传输原始的比特流(0 和 1)
- **交通方式 (Protocol)**:例如 `https://`,代表你想坐安全级别的最高的“运钞车”(加密通信)去。如果是单纯的 `http://`,就相当于坐普通的“大巴”(明文传输),路上可能会被人偷看行李。
- **店铺地址 (Host)**:例如 `www.google.com`,也就是你要去哪家店(域名)
- **商品位置 (Path)**:例如 `/search`,意思是进了商店之后,你要去哪个货架找什么东西(即请求的具体资源路径)。
**生活类比**:想象快递公司需要有**道路**和**运输车辆**:
- 道路可以是:高速公路(光纤)、普通公路(网线)、航空线路(无线电波)
- 车辆可以是:卡车(有线传输)、飞机(无线传输)
- 货物(数据)最终都要变成能在这些道路上运输的形式
:::
**关键任务**:
- **定义物理设备标准**:RJ45 网线接口长什么样、光纤接口怎么接
- **规定传输介质**:
- 有线:双绞线(网线)、光纤、同轴电缆
- 无线:Wi-Fi、蓝牙、4G/5G
- **确定电气特性**:
- 用多少电压代表 0 和 1?
- 信号频率是多少?
- 怎么编码(比如曼彻斯特编码)?
### 1.2 传输介质
**有线介质**:
| 类型 | 速度 | 距离 | 特点 | 用途 |
| ------------ | ---------------- | -------- | ------------------------ | ------------------------ |
| **双绞线** | 100Mbps - 10Gbps | 100m | 成本低,易安装,抗干扰一般 | 家庭、办公室网络 |
| **光纤** | 1Gbps - 100Tbps | 几十公里 | 速度极快,抗干扰强,成本高 | 长距离、高带宽(跨海光缆) |
| **同轴电缆** | 10Mbps - 1Gbps | 500m | 抗干扰好,但较粗 | 早期以太网、有线电视 |
::: tip 💡 为什么光纤这么快?
光纤用**光**而不是电信号传输:
- 光的频率极高,能调制大量数据(就像用不同颜色的光同时传输)
- 光在光纤中几乎不衰减,能传输几十公里
- 不受电磁干扰(高压电线、雷电都不怕)
这就像用电信号寄快递(铜线)vs 用光速寄快递(光纤),速度差异是本质级别的。
:::
**无线介质**:
| 类型 | 频段 | 速度 | 距离 | 用途 |
| --------- | -------------------- | ------------------- | ------ | -------------------- |
| **Wi-Fi** | 2.4GHz / 5GHz / 6GHz | 几十 Mbps - 几 Gbps | 几十米 | 家庭、办公室无线网络 |
| **蓝牙** | 2.4GHz | 1-3 Mbps | 10m | 耳机、键鼠等短距设备 |
| **4G/5G** | 700MHz - 39GHz | 10Mbps - 10Gbps | 几公里 | 移动网络 |
### 1.3 常见设备
**中继器(Repeater)**:
- **作用**:放大信号,延长传输距离
- **生活类比**:快递中转站。快递车开了 500 公里需要加油、司机换班,中继器就是让信号"休息充电"的地方
- **为什么需要**:电信号在铜线传输会衰减,传几百米就弱得识别不出了
**集线器(Hub)**:
- **作用**:多端口中继器,从一个口收到的信号复制到所有口
- **缺点**:效率低,已被**交换机**取代
- **生活类比**:一个大厅,一个人喊话,所有人都能听到,但不是喊给谁的
浏览器第一步要做的,就是把这段“人类语言”拆解开,看看你到底想要什么。
---
## 2. 数据链路层:车队管理
## 2. 查找店铺地址 (DNS 解析)
### 2.1 基本概念
网络世界的“快递员”(路由器设备)是不懂英文的,它们只认数字(也就是 **IP 地址**)。
::: tip 💡 数据链路层做什么?
数据链路层负责在**直连的两个节点**间传输数据帧。
它们需要知道对方的精确数字坐标!这就像快递员不知道“王府井百货”在哪,他必须先查地图,找到“北京市东城区王府井大街255号”这个确切的门牌号(比如 `142.250.66.4`)。
**生活类比**:快递公司的**车队管理**:
- 快递车从北京分拣中心开到天津分拣中心(点对点)
- 车上有司机(负责驾驶)、装卸工(负责搬运)
- 两边分拣中心之间有约定:"每天 8 点发车""用标准尺寸的快递箱"
:::
**核心功能**:
- **物理地址寻址(MAC 地址)**:每个网卡都有全球唯一的身份证号
- **帧的封装和解封装**:把网络层的数据包"装进车厢"
- **错误检测**:通过 CRC 校验,发现数据是否损坏
- **介质访问控制**:多个设备共享一条线时,谁先谁后?(比如 Wi-Fi 多台设备连一个路由器)
### 2.2 MAC 地址:设备的身份证
**MAC 地址格式**:`00:1A:2B:3C:4D:5E`
::: tip 💡 MAC 地址 vs IP 地址
这是初学者最容易混淆的两个概念:
| 特性 | MAC 地址 | IP 地址 |
| ------------ | ----------------------- | --------------------------- |
| **作用范围** | 局域网内(同一个 Wi-Fi) | 全球互联网 |
| **分配方式** | 网卡出厂时烧录,全球唯一 | 由网络管理员动态分配 |
| **变化** | 一般不变(除非换网卡) | 经常变化(连不同 Wi-Fi 会变) |
| **类比** | 身份证号(跟随你一生) | 家庭住址(搬家就变) |
| **层级** | 数据链路层(第2层) | 网络层(第3层) |
**生活类比**:你要寄快递:
- **MAC 地址** = 收件人身份证号(唯一标识这个人)
- **IP 地址** = 收件人家庭住址(用于路由)
快递员实际上需要"住址"才能送货,但身份证号能确保"这个人"是唯一的。
:::
**查看你的 MAC 地址**:
```bash
# Windows
ipconfig /all
# 找到 "物理地址",类似: 00-1A-2B-3C-4D-5E
# macOS/Linux
ifconfig
# 找到 "ether",类似: 00:1a:2b:3c:4d:5e
```
### 2.3 以太网帧:快递车厢的结构
**以太网帧**就是数据链路层的"快递车厢",有一套标准格式:
```
+------------+----------+---------+-----+----------+
| 目标 MAC | 源 MAC | 类型 | 数据 | FCS |
| (6 bytes) | (6 bytes) | (2 bytes)| | (4 bytes) |
+------------+----------+---------+-----+----------+
```
::: tip 💡 逐行理解帧结构
**目标 MAC (6字节)**:这帧数据给谁的?就像快递单上的收件人
**源 MAC (6字节)**:这帧数据谁发的?就像寄件人信息
**类型 (2字节)**:车厢里装的是什么?
- `0x0800` = IPv4 数据包
- `0x0806` = ARP 请求(查询 MAC 地址)
- `0x86DD` = IPv6 数据包
**数据(46-1500字节)**:实际要传输的内容,就是网络层的 IP 数据包
**FCS (4字节)**:帧校验序列。接收方用这个检查数据是否损坏,就像快递单上的"完好无损"签章
:::
### 2.4 交换机:聪明的交通指挥
**交换机**是数据链路层的核心设备。
::: tip 💡 交换机 vs 集线器
**集线器**:
- 收到数据后,简单地"广播"到所有端口
- 所有设备都能看到,不是给自己的也得收下来再丢弃
- 效率低,安全性差
**交换机**:
- **学习 MAC 地址**:记住哪个端口连了哪个 MAC 地址
- **智能转发**:只把数据发到目标设备所在的端口
- **效率高**:设备 A 和 B 通信,设备 C 不会收到
:::
**交换机工作流程**:
1. **学习**:设备 A (MAC: 11:11...) 发数据给交换机端口1
- 交换机记下:"11:11... 在端口1"
2. **转发**:设备 A 要发数据给设备 B (MAC: 22:22...)
- 交换机查表:"22:22... 在端口3"
- 只把数据从端口3 发出去
3. **广播**:如果交换机不知道目标 MAC 在哪(比如第一次通信)
- 向所有端口(除了来源端口)广播
- "谁是 22:22...?" 目标设备回应后,交换机学习到它的位置
- **本地缓存**:浏览器会先翻翻自己的备忘录(看之前有没有访问过该网站)。
- **DNS 系统**:如果在本地找不到,它就会向互联网的“查号台”(DNS 服务器)打电话询问:“请问 google.com 的数字地址是什么?”。一旦获得了对应的 IP 地址,浏览器的快递车就知道该往哪里开了。
---
## 3. 网络层:路由规划部
## 3. 建立通话 (TCP 握手)
### 3.1 IP 地址:互联网的门牌号
拿到了地址,浏览器不能直接冲过去,万一店今天没开门呢?所以,要先进行一次**“电话确认”**(这叫建立 TCP 连接)。为了确保通话稳定可靠,会有三次非常严谨的“确认打招呼”机制,行业里叫**三次握手 (Three-way Handshake)**
::: tip 💡 IP 地址是什么?
**IP 地址**就像互联网上的**家庭住址**,每台联网设备都需要一个。
- **第一次握手 (浏览器)**:“喂,你好,我要来买东西,你在吗?” (SYN)
- **第二次握手 (服务器)**:“我在的,欢迎光临!你也听得到我说话吗?” (SYN-ACK)
- **第三次握手 (浏览器)**:“我也听到了!那我就要过来了!” (ACK)
**IPv4 地址格式**:`192.168.1.1`
- 32 位,通常用点分十进制表示
- 分为**网络部分**(前3段)和**主机部分**(最后1段)
- `192.168.1` 是网络号(这个小区)
- `.1` 是主机号(这个小区的1号房)
:::
**IP 地址分类(像城市规模)**:
| 类别 | 范围示例 | 网络数 | 每个网络主机数 | 用途 | 类比 |
| -------- | --------------------- | --------- | -------------- | ---------------- | ---------- |
| **A 类** | 1.0.0.0 - 126.x.x.x | 126 | 16,777,214 | 超大型网络(早期) | 特大城市 |
| **B 类** | 128.0.0.0 - 191.x.x.x | 16,384 | 65,534 | 中型网络 | 中等城市 |
| **C 类** | 192.0.0.0 - 223.x.x.x | 2,097,152 | 254 | 小型网络(最常见) | 小区、村庄 |
::: tip 💡 私有 IP 地址:内网 vs 外网
有些 IP 地址段被保留为"私有",不能直接在互联网上使用:
| 类别 | 私有 IP 范围 | 为什么用私有 IP? |
| -------- | ------------------------------- | -------------------- |
| **A 类** | `10.0.0.0 - 10.255.255.255` | 大型企业内网 |
| **B 类** | `172.16.0.0 - 172.31.255.255` | 中型企业内网 |
| **C 类** | `192.168.0.0 - 192.168.255.255` | 家庭、小公司(最常见) |
**生活类比**:
- **私有 IP** = 你家的门牌号("3单元501室")
- **公网 IP** = 你家在地图上的地址("XX市XX区XX路XX号")
快递员(互联网)只能送到公网地址(你家楼门口),然后需要"路由器/NAT"转换到你家的私有地址。
:::
### 3.2 子网划分:把大楼分成多个单元
::: tip 💡 为什么要划分子网?
想象一个公司:
- **不划分子网**:财务部、技术部、市场部都在 `192.168.1.0` 网段
- 广播风暴:一个人发广播,所有人都能收到
- 安全问题:技术部的开发服务器,市场部也能访问
- 管理混乱:网络出问题,不知道是哪个部门的
**划分子网**:
- 财务部:`192.168.1.0/24`
- 技术部:`192.168.2.0/24`
- 市场部:`192.168.3.0/24`
各部门隔离,广播不出部门,管理更清晰。
:::
**子网掩码的作用**:
子网掩码用来区分 IP 地址的哪部分是"网络号",哪部分是"主机号"。
```
IP: 192.168.1.10
掩码: 255.255.255.0
-----------------------
网络号: 192.168.1.0 (前3段)
主机号: .10 (最后1段)
```
**CIDR 表示法**:`192.168.1.0/24`
- `/24` 表示前 24 位是网络位
- 剩余 8 位是主机位(2^8 - 2 = 254 个可用 IP)
<CFSubnetCalculator />
### 3.3 路由器:GPS 导航
**路由器**是网络层的核心设备,负责"规划最佳路线"。
::: tip 💡 路由器怎么工作?
**生活类比**:GPS 导航软件
- 你输入:"从北京天安门到上海外滩"
- GPS 查询地图数据库,规划出最佳路线
- 路线可能是:"北京 → 天津 → 济南 → 南京 → 上海"
**路由器的工作**:
1. 收到数据包,查看目标 IP 地址
2. 查询**路由表**(路由器的"地图数据库")
3. 选择最佳路径:"下一站该去哪个路由器?"
4. 转发到下一跳
:::
**路由表示例**:
```
目标网络 子网掩码 网关 接口
192.168.1.0 255.255.255.0 0.0.0.0 eth0
192.168.2.0 255.255.255.0 192.168.1.2 eth0
0.0.0.0 0.0.0.0 192.168.1.1 eth0 (默认网关)
```
::: tip 💡 理解路由表
**第1行**:"发往 192.168.1.0 网段的包,直接从 eth0 接口发出去"(本地网络,不需要网关)
**第2行**:"发往 192.168.2.0 网段的包,发给 192.168.1.2(它是这个网络的'门')"
**第3行(默认网关)**:"不知道怎么走的包,全部发给 192.168.1.1(它连接互联网,会继续帮你转发)"
这就像你去外地:
- 在本地:走路就到(直接路由)
- 去隔壁城市:坐大巴(走网关)
- 去国外:先到机场,再转机(默认网关 → 层层转发)
:::
### 3.4 ICMP:网络诊断工具
**ICMP (Internet Control Message Protocol)** 用于网络诊断,最常用的就是 `ping` 命令。
**Ping 命令**:
```bash
ping google.com
# 输出示例
PING google.com (142.250.185.238): 56 data bytes
64 bytes from 142.250.185.238: icmp_seq=0 ttl=117 time=12.4 ms
64 bytes from 142.250.185.238: icmp_seq=1 ttl=117 time=11.8 ms
```
::: tip 💡 理解 ping 的输出
**`64 bytes`**:数据包大小(64 字节)
**`icmp_seq=0`**:这是第 0 个包(序列号)
**`ttl=117`**:Time To Live(生存时间)
- 每经过一个路由器减 1
- 防止数据包在网络中无限循环
- 117 表示这个包经过了 255-117=138 个路由器
**`time=12.4 ms`**:往返时间(RTT, Round Trip Time)
- 你的电脑发送请求 → google.com 收到 → google.com 回应 → 你的电脑收到
- 整个过程花了 12.4 毫秒
- 数值越小,网络延迟越低,网速越快
:::
经过这三次确认,双方都知道了彼此的听力和表达能力都没问题,一条稳定可靠的通信通道就正式建立了。
---
## 4. 传输层:可靠送达 vs 快速送达
## 4. 购买商品 (HTTP 请求与响应)
### 4.1 端口:应用的门牌号
通道建好后,业务正式开始。
::: tip 💡 为什么需要端口号?
想象一台服务器:
- **只有 IP 地址**:数据包到了服务器,服务器不知道给哪个程序
- Web 服务器要?
- 邮件服务器要?
- 数据库服务器要?
**端口号**就像"公司里的部门号":
- IP: 公司地址(XX 市XX 路 XX 号)
- 端口: 部门(301 财务部、302 技术部、303 市场部)
数据包到了公司,前台(操作系统)根据"部门号"(端口)转发给对应部门(应用程序)。
:::
**端口号范围**:
| 范围 | 类型 | 示例 | 需要权限? |
| --------------- | -------- | ----------------------------- | --------------------------------- |
| **0-1023** | 系统端口 | 80(HTTP)、443(HTTPS)、22(SSH) | ✅ 需要(防止普通用户占用关键服务) |
| **1024-49151** | 注册端口 | 3306(MySQL)、5432(PostgreSQL) | ❌ 不需要 |
| **49152-65535** | 动态端口 | 客户端临时使用 | ❌ 不需要 |
**常见端口速查**:
| 端口 | 服务 | 用途 |
| --------- | ---------- | --------------- |
| **21** | FTP | 文件传输 |
| **22** | SSH | 远程登录(安全) |
| **80** | HTTP | 网页(不安全) |
| **443** | HTTPS | 网页(安全,加密) |
| **3306** | MySQL | 数据库 |
| **5432** | PostgreSQL | 数据库 |
| **6379** | Redis | 缓存数据库 |
| **27017** | MongoDB | 数据库 |
### 4.2 TCP vs UDP:挂号信 vs 平信
<CFTcpUdpComparison />
**选择建议**:
| 场景 | 选择 | 原因 |
| ------------------ | ------- | ----------------------------------------- |
| **邮件、文件传输** | **TCP** | 不能丢数据,一个字节错误都可能导致文件损坏 |
| **视频、直播** | **UDP** | 实时性优先,丢几帧没关系,但不能卡顿 |
| **网页浏览** | **TCP** | 可靠性重要,网页内容必须完整 |
| **在线游戏** | **UDP** | 速度优先,位置信息晚到比没到好 |
::: tip 💡 深入理解:TCP 为什么可靠?
TCP 通过以下机制保证可靠:
1. **三次握手**:确保双方都能发送和接收
2. **序列号**:每个字节都有编号,丢包能发现
3. **确认应答**:收到数据必须回复 ACK,没收到就重传
4. **流量控制**:接收方告诉发送方"我的缓冲区快满了,慢点发"
5. **拥塞控制**:网络拥堵时,降低发送速度,避免"堵死"
这就像寄挂号信:
- 要签收(ACK)
- 丢了邮政局会重传
- 太多信件会积压,需要控制发送速度
:::
### 4.3 TCP 三次握手:建立可靠连接
```
客户端 服务器
| |
| -------- SYN(seq=x) ---------> | 第1次:你好,我想和你通信(SYN)
| | (x 是随机数,防止伪造)
| |
| <--- SYN-ACK(seq=y, ack=x+1) ---| 第2次:收到!我也想和你通信(SYN)
| | 我收到了你的 x,所以 ack=x+1
| |
| -------- ACK(ack=y+1) --------> | 第3次:我收到了你的 y,所以 ack=y+1
| | 连接建立成功!
```
::: tip 💡 为什么需要三次,不是两次?
想象打电话:
- **A**:你好!(SYN)
- **B**:你好!(SYN-ACK) —- 此时 B 确认了 A 能收到,但 A 还不确定 B 能不能收到
- **A**:我听到了!(ACK) —- 现在双方都知道对方能收能发
如果只有两次:
- A 发 SYN
- B 回 SYN-ACK
- 连接建立...但 B 不知道 A 有没有收到 SYN-ACK!如果 A 没收到,会重复发 SYN,但 B 以为已经建立连接,会出现问题
:::
- **浏览器(买家)提交订单**:浏览器会打包一份极其规范的订单表格(**HTTP 请求报文**),里面写着:“老板,请给我拿一份你的主页 HTML 文件,我是用 Chrome 浏览器来访问的哦。”
- **服务器(卖家)根据订单发货**:位于地球另一端的 Google 服务器收到请求后,立刻开始在仓库里配货,生成网页的 HTML 代码,然后打包成包裹(**HTTP 响应报文**),发回给你的浏览器。包裹外面还会贴个标签“200 OK”,意思是“交易成功,你要的货全齐了”。
---
## 5. 应用层:具体的业务
## 5. 拆盒组装 (浏览器渲染)
### 5.1 HTTP/HTTPS:网页的对话协议
最后一步,货物送到了你的电脑。但发过来的只是一堆代码(HTML、CSS、JavaScript),这就好比你网购买了一箱乐高积木,还需要自己组装:
**HTTP (HyperText Transfer Protocol)** 是浏览器和服务器之间的"对话规则"
1. **看说明书 (解析 HTML)**:浏览器先把 HTML 代码解读出来,拼装成网页的骨架(DOM 树)
2. **涂抹颜色 (解析 CSS)**:然后检查 CSS 代码,看看字体要多大、按钮是什么颜色,给网页穿上漂亮的外衣(CSSOM 树)。
3. **计算布局并拼装 (Layout & Paint)**:浏览器计算好每个元素在屏幕上的确切位置,用画笔把它们画在你的显示器上。
4. **注入灵魂 (执行 JavaScript)**:最后,各种能点击、能滑动的交互效果都通过 JavaScript 激活。
| 特性 | HTTP | HTTPS |
| ---------- | ------------------------ | ----------------------------- |
| **加密** | ❌ 否(明文,任何人都能看) | ✅ 是(TLS/SSL 加密) |
| **端口** | 80 | 443 |
| **安全性** | 低(密码、账号会被窃取) | 高(即使被拦截,看到的也是乱码) |
| **性能** | 略快(无加密开销) | 略慢(加密解密需要时间) |
| **SEO** | 不友好(搜索引擎会降权) | 友好(搜索引擎优先收录 HTTPS) |
**HTTP 请求方法**:
| 方法 | 描述 | 生活类比 | 示例 |
| ---------- | ---------------------- | ---------------------------- | ---------------------- |
| **GET** | 获取资源 | "我要看这个商品的详情" | 查看网页、加载图片 |
| **POST** | 提交数据 | "我要下单,这是我的收货信息" | 登录、注册、提交表单 |
| **PUT** | 更新资源(整体替换) | "我要完整更新这个商品的信息" | 修改用户资料(全部字段) |
| **PATCH** | 部分更新 | "我只想改商品的名称" | 修改用户资料(只改名字) |
| **DELETE** | 删除资源 | "我要删除这个订单" | 删除文章、删除评论 |
| **HEAD** | 只获取响应头(不要内容) | "这个文件还在吗?有多大?" | 检查资源是否存在 |
**HTTP 状态码**(服务器给你的"回复"):
```
2xx 成功
- 200 OK:请求成功,这是你要的内容
- 201 Created:创建成功(比如注册新用户)
3xx 重定向
- 301 Moved Permanently:永久搬家了,请用新地址
- 302 Found:暂时搬迁,请访问新地址
4xx 客户端错误(你发的问题)
- 400 Bad Request:请求格式错误,服务器看不懂
- 401 Unauthorized:未授权,请先登录
- 403 Forbidden:禁止访问,即使登录也不行
- 404 Not Found:资源不存在(网址错了?)
5xx 服务器错误(服务器的问题)
- 500 Internal Server Error:服务器内部出错了
- 502 Bad Gateway:网关错误,服务器连不上后端
- 503 Service Unavailable:服务暂时不可用(过载或维护)
```
### 5.2 DNS:互联网的地址簿
**DNS (Domain Name System)** 域名系统,把人类可读的域名转换成机器可读的 IP 地址。
::: tip 💡 为什么需要 DNS?
**没有 DNS 的世界**:
- 你需要记住所有网站的 IP 地址
- 访问百度:`https://110.242.68.66`(你能记住吗?)
- IP 地址会变(服务器迁移),你需要重新记住
**有 DNS 的世界**:
- 记住域名:`baidu.com`
- DNS 帮你转换:`baidu.com``110.242.68.66`
- IP 变了?更新 DNS 记录就行,域名不用变
:::
**DNS 查询过程**:
```
你(浏览器)
↓ 问:baidu.com 的 IP 是多少?
本地 DNS 服务器(你的网络运营商,如电信/联通)
↓ 不知道? 问:
根域名服务器(全球13组,管理所有顶级域)
↓ 告诉:去问 .cn 的管理者
顶级域名服务器(Verisign 管理 .cn)
↓ 告诉:去问 baidu.com 的管理者
权威 DNS 服务器(Baidu 自己的 DNS)
↓ 告诉:baidu.com 的 IP 是 110.242.68.66
返回 IP 地址给浏览器
```
**DNS 记录类型**:
| 类型 | 用途 | 示例 |
| --------- | ---------------- | ------------------------------------------------------ |
| **A** | 域名 → IPv4 地址 | `www.example.com → 93.184.216.34` |
| **AAAA** | 域名 → IPv6 地址 | `www.example.com → 2606:2800:220:1:248:1893:25c8:1946` |
| **CNAME** | 别名 | `www.baidu.com → a.baidu.com`(多个域名指向同一个 IP) |
| **MX** | 邮件服务器 | `@example.com → mail.example.com`(邮件发到哪里) |
**只要短短的几百毫秒,所有的步骤就已全部完成,你也就看到了那个熟悉的页面!**
---
## 6. 总结:网络五层模型核心要点
## 总结:从微观到宏观
| 层级 | 核心概念 | 关键技术 | 生活类比 |
| -------------- | ------------------- | -------------------- | ---------------------------------- |
| **应用层** | 应用程序之间的通信 | HTTP, FTP, SMTP, DNS | 具体业务(寄快递、发邮件、浏览网页) |
| **传输层** | 端到端的可靠传输 | TCP(可靠), UDP(快速) | 快递方式(挂号信 vs 平信) |
| **网络层** | 路由选择,寻址 | IP, 路由器, ICMP | GPS 导航,规划路线 |
| **数据链路层** | 点对点传输,MAC 寻址 | 以太网, 交换机, MAC | 车队管理,车辆之间通信 |
| **物理层** | 实际的物理传输 | 光纤, 网线, 无线电波 | 道路和运输工具 |
如果我们把目光再拉远一点,整个网络通讯的本质,就是在做**接力跑和翻译**:
**学习建议**:
- 我们上面看到的这五步,大多是发生在你眼前的**应用程序**层面的事情。
- 在肉眼看不见的底层,刚才那个充满代码的 HTML 包裹,会被切分成无数块极小的碎片(数据包)。这些碎片顺着你家墙上的网线、海底的万兆光缆,像接力棒一样在各种路由器之间传递。
- 最终,这一切碎片完好无损地抵达,并在哪怕是几十个毫秒的时间里,化成你屏幕上的绚丽像素。
- ✅ **从应用层往下学**:你每天都在用 HTTP,DNS,从熟悉的开始
- ✅ **多用工具**:ping, traceroute, Wireshark,观察实际网络
- ✅ **理解协议细节**:阅读 RFC 文档(比如 RFC 791 定义 IP)
- ✅ **抓包分析**:用 Wireshark 观察 TCP 三次握手、HTTP 请求
- ✅ **关注安全**:了解 DDoS、中间人攻击等常见威胁
掌握计算机网络,你就能理解互联网的运作原理,写出更高效的网络应用!
---
## 附录:名词速查表
| 名词 | 英文 | 用人话解释 |
| --------------- | ----------------------------- | ------------------------------------------- |
| **OSI 模型** | Open Systems Interconnection | 七层网络模型(理论标准) |
| **TCP/IP 模型** | - | 实际使用的四层/五层模型 |
| **MAC 地址** | Media Access Control | 网卡的物理地址,全球唯一,像身份证 |
| **IP 地址** | Internet Protocol | 设备在互联网上的逻辑地址,像住址 |
| **子网掩码** | Subnet Mask | 区分 IP 地址的网络部分和主机部分 |
| **端口** | Port | 应用程序的"门牌号",区分同一台设备的不同服务 |
| **TCP** | Transmission Control Protocol | 可靠传输协议,三次握手,不丢包 |
| **UDP** | User Datagram Protocol | 快速传输协议,不保证可靠,可能丢包 |
| **DNS** | Domain Name System | 域名系统,把域名转成 IP 地址 |
| **HTTP** | HyperText Transfer Protocol | 超文本传输协议,网页通信规则 |
| **HTTPS** | HTTP Secure | 加密的 HTTP,更安全 |
| **路由器** | Router | 网络层设备,规划路线,连接不同网络 |
| **交换机** | Switch | 数据链路层设备,智能转发数据帧 |
| **TTL** | Time To Live | 生存时间,防止数据包无限循环 |
| **RTT** | Round Trip Time | 往返时间,数据从发送到接收确认的时间 |
这就是计算机网络的神奇魅力!
@@ -1,255 +1,99 @@
# 操作系统(进程 / 内存 / 文件系统)
# 操作系统:给电脑请个"大管家"
::: tip 🎯 核心问题
**操作系统是做什么的?** 你可能每天都在用 Windows、macOS 或 Linux,但你知道它到底在忙什么吗?为什么需要它?没有它电脑还能用吗?本章带你理解操作系统的三大核心职责:管理进程、管理内存、管理文件。
**有了完美的 CPU 和无限的内存,电脑就能直接用了吗?**
在上一章,我们见证了晶体管如何组合成强大的 CPU。但其实,如果直接使用这些冷冰冰的硬件,哪怕只是想在屏幕上打出一个字母,你都需要手写几百行晦涩的机器指令。
为了不让大家在每次用电脑时都被逼疯,前辈们创造了一个夹在“硬件”和“你”之间的超级管家——**操作系统(Operating System, 简称 OS)**。本章我们不谈深奥的理论,只聊聊这个大管家是怎么通过三大“障眼法”,把复杂的硬件调教得服服帖帖的。
:::
---
## 0. 全景图:操作系统的角色
## 0. 承上启下:如果没有操作系统会怎样?
想象你开了一家餐厅。你需要:
- **安排员工工作**:谁做菜、谁端盘子、谁收银(进程管理)
- **管理厨房空间**:冰箱放什么、操作台怎么分配(内存管理)
- **整理仓库物资**:食材怎么存放、怎么找(文件系统)
上一章我们提到,CPU 是一个不知疲倦的无情计算机器,通电后就会一行一行地执行指令。
操作系统就是电脑的"餐厅经理",它负责协调所有资源,让程序能顺利运行。
但这带来了几个现实的灾难:
1. **CPU 独占危机**:CPU 一次只能干一件事。如果你正在听歌,想切出去看个网页?抱歉,没有操作系统的调度,你的电脑必须停下音乐,才能去加载网页。
2. **内存踩踏事故**:微信和游戏都在使用内存。如果没有保安管理,游戏一不小心把数据写到了微信的内存地盘,微信当场崩溃。
3. **硬盘迷宫**:硬盘本质上只是一张密密麻麻刻满 0 和 1 的巨大光盘。要想找到你昨天存的照片,你必须准确记住它存放在第 12345 圈磁道的第 678 个扇区。
**操作系统的三大核心职责**
| 职责 | 管理对象 | 核心问题 | 类比 |
|------|---------|---------|------|
| **进程管理** | CPU 时间 | 谁先用 CPU?用多久? | 员工排班 |
| **内存管理** | 内存空间 | 程序放哪里?怎么不冲突? | 厨房空间分配 |
| **文件系统** | 磁盘数据 | 数据怎么存?怎么找? | 仓库物资管理 |
::: tip 📊 逐行解读这张表
**进程管理**:CPU 是最宝贵的资源,操作系统要决定哪个程序先用、用多久。就像餐厅经理安排员工轮班,不能让所有人同时挤在厨房里。
**内存管理**:内存是程序的"工作台",操作系统要给每个程序分配空间,还要保证它们互不干扰。就像厨房空间有限,要合理分配给不同的厨师。
**文件系统**:磁盘是"仓库",操作系统要把数据有序地存进去,需要时能快速找到。就像仓库管理员整理货架,按类别、编号存放。
:::
为了解决这些噩梦,操作系统诞生了。它对外提供了一套优雅的“幻觉”,这就是它的三大核心魔法**进程(管理 CPU)**、**虚拟内存(管理内存)** 和 **文件系统(管理硬盘)**
---
## 1. 进程管理:程序的"分身术"
## 1. 进程管理:制造“同时运行”的幻觉
### 1.1 什么是进程
你平时用电脑,常常是一边挂着微信,一边听着音乐,还能一边打字。但如果你买的电脑其实只有一个 CPU 核心,它是怎么同时做这三件事的
答案是:**它并没有同时做**。是操作系统在进行疯狂的“时间管理”。
<ProcessDemo />
::: tip 💡 程序 vs 进程
这是初学者最容易混淆的概念:
::: tip 💡 核心原理解析:时间片轮转(Time Slicing
操作系统把 CPU 的时间切成了极其微小的片段(比如 10 毫秒)。
- 第 1-10 毫秒:让 CPU 去执行**微信**的接收消息逻辑。
- 第 11-20 毫秒:把微信强制暂停,让 CPU 去执行**音乐**的播放逻辑。
- 第 21-30 毫秒:把音乐暂停,让 CPU 去响应你的**键盘打字**。
| 概念 | 定义 | 类比 | 特点 |
|------|------|------|------|
| **程序** | 静态的代码文件 | 菜谱 | 存在磁盘上,不会动 |
| **进程** | 程序的运行实例 | 正在按菜谱做菜 | 在内存中运行,会变化 |
因为切换的速度实在太快了(一秒钟切换成百上千次),在人类迟钝的感知中,就觉得这三个软件是“同时”在运行的。
**关键区别**
- 一个程序可以启动多个进程(比如打开多个浏览器窗口)
- 每个进程有独立的内存空间,互不干扰
- 进程有生命周期:创建、运行、等待、终止
在操作系统的术语里,运行中的程序就被称为**进程(Process)**。操作系统就是这群进程的冷酷无情的排班经理。
:::
### 1.2 进程的状态
进程在运行过程中会在不同状态之间切换:
| 状态 | 含义 | 什么时候进入 | 类比 |
|------|------|-------------|------|
| **就绪 (Ready)** | 准备好运行,等 CPU | 进程刚创建,或从等待恢复 | 员工在休息室等排班 |
| **运行 (Running)** | 正在 CPU 上执行 | 被调度器选中 | 员工正在工作 |
| **等待 (Waiting)** | 等待 I/O 或其他资源 | 需要读磁盘、等网络 | 员工在等食材送达 |
| **终止 (Terminated)** | 运行结束 | 程序退出或出错 | 员工下班 |
### 1.3 进程调度:谁先用 CPU?
::: tip 💡 为什么需要调度?
CPU 核心数有限,但进程可能有几十上百个。操作系统需要决定:
- 哪个进程先运行?
- 运行多久?
- 什么时候切换?
这就是**进程调度**要解决的问题。
:::
**常见调度算法:**
| 算法 | 思路 | 优点 | 缺点 |
|------|------|------|------|
| **先来先服务 (FCFS)** | 谁先到谁先运行 | 简单公平 | 短任务可能等很久 |
| **短作业优先 (SJF)** | 短任务优先 | 平均等待时间最短 | 需要预知任务长度 |
| **时间片轮转 (RR)** | 每人运行一小段时间 | 公平,响应快 | 切换开销大 |
| **优先级调度** | 重要任务优先 | 重要任务响应快 | 可能导致低优先级任务饿死 |
---
## 2. 内存管理:程序的"工作台"
## 2. 内存管理:给每个程序画个“海市蜃楼”
### 2.1 为什么需要内存管理?
解决了 CPU 轮流用的问题,接下来是存放数据的内存。如果所有的进程都挤在同一块物理内存里,很容易发生互相干扰和偷看数据的危险。
操作系统的第二大魔法,叫作**虚拟内存(Virtual Memory**。
<MemoryDemo />
::: tip 💡 如果没有内存管理会怎样?
想象一个没有管理的厨房:
::: tip 💡 核心原理解析:内存映射
操作系统对每一个启动的进程撒了一个弥天大谎:“嘿,你独占了整整 4GB 的纯净内存空间,随便用!”(这就是**虚拟内存**)。
- **冲突**:两个厨师同时用同一个灶台,菜都糊了
- **浪费**:有人占了整个厨房,其他人没地方做饭
- **安全问题**:有人偷吃了别人的食材
但实际上,当进程往这个“虚拟空间”里放东西时,操作系统的底层会拿出一个**映射表(页表)**,偷偷把数据塞进**真实物理内存(Physical Memory)**中各种零碎、不连续的角落里。
操作系统通过**内存管理**解决这些问题:
- 给每个进程分配独立的内存空间
- 防止进程互相干扰(内存保护)
- 高效利用有限的内存资源
**这么做有两个巨大的好处:**
1. **绝对安全**:微信永远只能看到自己的虚拟空间,它根本不知道音乐的数据在物理内存的哪个角落,自然就不会发生“踩踏”。
2. **碎片利用**:物理内存就算被用得像狗皮膏药一样稀碎,映射给进程的虚拟空间依然是连续且整齐的。
:::
### 2.2 虚拟内存:让每个进程都"以为"自己独占内存
::: tip 💡 什么是虚拟内存?
**虚拟内存**是操作系统的一个"魔术":
- 每个进程都以为自己有 4GB(或更多)的内存空间
- 实际上物理内存可能只有 8GB、16GB
- 操作系统通过"映射"把虚拟地址转换成物理地址
**生活类比**:想象一个酒店:
- 每个客人都以为自己独占整个酒店(虚拟空间)
- 实际上酒店只有 100 间房(物理内存)
- 前台(操作系统)负责分配房间、记录谁住哪里
:::
**虚拟内存的好处:**
| 好处 | 说明 | 为什么重要 |
|------|------|-----------|
| **隔离保护** | 进程间内存互不干扰 | 一个崩溃不影响其他 |
| **内存扩展** | 用磁盘当内存用 | 可以运行比物理内存大的程序 |
| **简化编程** | 不用关心物理地址 | 程序员写代码更简单 |
### 2.3 内存分配策略
当进程需要内存时,操作系统如何分配?
| 策略 | 思路 | 特点 |
|------|------|------|
| **首次适应** | 找到第一个够大的空闲块 | 速度快 |
| **最佳适应** | 找最小的够大的空闲块 | 内存利用率高 |
| **最坏适应** | 找最大的空闲块 | 减少小碎片 |
---
## 3. 文件系统:数据的"档案柜"
## 3. 文件系统:把“荒地”变成“档案馆”
### 3.1 什么是文件系统?
如果你买了一块崭新的硬盘,它里面其实是一片荒芜的存储单元。如果你想存一张照片,硬盘硬件只会问你:“请告诉我你要存在第几个字节地址?”这显然反人类。
操作系统的第三大魔法是**文件系统(File System)**,它为你构建了我们最熟悉的:文件夹(目录)和文件的概念。
<FilesystemDemo />
::: tip 💡 文件系统是什么?
**文件系统**是操作系统管理磁盘数据的方式。
**生活类比**:想象一个图书馆:
- 书架 = 磁盘
- 书 = 文件
- 目录卡片 = inode
- 分类编号 = 路径
没有文件系统,磁盘就是一堆杂乱的数据。有了文件系统,我们可以:
- 用"路径"找到文件(如 `/home/user/document.txt`
- 创建、删除、修改文件
- 控制谁能访问哪些文件
:::
### 3.2 inode:文件的"身份证"
::: tip 💡 inode 是什么?
每个文件都有一个 **inode**(索引节点),记录了文件的元数据:
| 信息 | 说明 |
|------|------|
| inode 编号 | 文件的唯一标识 |
| 文件大小 | 多少字节 |
| 权限 | 谁能读写 |
| 时间戳 | 创建、修改、访问时间 |
| 数据块位置 | 文件内容存在哪些磁盘块 |
**关键理解**
- 文件名不在 inode 里!文件名只是目录中的一个条目
- 一个文件可以有多个名字(硬链接)
- 删除文件只是删除目录项,inode 可能还在
:::
### 3.3 常见文件系统
| 文件系统 | 操作系统 | 特点 |
|---------|---------|------|
| **NTFS** | Windows | 支持大文件、权限控制 |
| **APFS** | macOS | 加密、快照、高效 |
| **ext4** | Linux | 稳定、高效、广泛使用 |
| **FAT32** | 通用 | 兼容性好,但单文件最大 4GB |
| **exFAT** | 通用 | 支持大文件,适合 U 盘 |
---
## 4. 进程、内存、文件系统的协作
这三个子系统是如何配合工作的?让我们看一个完整的例子:
**场景:打开一个文档文件**
```
1. 用户双击文件
2. 文件系统:根据路径找到 inode,读取文件内容
3. 进程管理:创建新进程(文档编辑器),分配 PID
4. 内存管理:为新进程分配内存,加载程序代码和数据
5. 进程运行:编辑器进程读取文件内容,显示在屏幕上
```
::: tip 💡 理解这个流程
每一步都涉及操作系统的核心功能:
1. **文件系统**:负责"找到文件"
2. **进程管理**:负责"启动程序"
3. **内存管理**:负责"给程序分配空间"
这三者紧密协作,才能完成一个看似简单的"打开文件"操作。
::: tip 💡 核心原理解析:从地址到路径
文件系统本质上是一个超级大型的“翻译官”加“账本”:
1. **账本功能**:它悄悄地把硬盘切分成无数个小块(Block),然后用一个账本记录下来“哪几个小块现在是空的可以存数据,哪几个小块已经存了东西”。
2. **翻译功能**:当你双击一层层文件夹,打开 `D盘/照片/宠物.jpg` 时,并不是硬盘真的长出了树枝一样的结构。而是文件系统在它的账本里疯狂翻阅,最终翻译出:哦,这个路径其实对应的是硬盘上的第 1056、1057 和 998 块小地方,然后把数据取出来交给你。
:::
---
## 5. 总结:操作系统是"大管家"
## 4. 总结:伟大的幕后英雄
让我们用一个比喻总结操作系统的三大职责:
让我们通过一个你每天都在经历的场景,串联起今天学到的知识。当你**双击鼠标打开一个游戏**时,为了伺候你,大管家做了什么?
| 职责 | 比喻 | 核心任务 |
|------|------|---------|
| **进程管理** | 餐厅排班员 | 安排谁先工作、工作多久 |
| **内存管理** | 厨房管理员 | 分配工作台、防止冲突 |
| **文件系统** | 仓库管理员 | 整理物资、快速查找 |
1. **文件系统**:立刻从底层硬盘的杂乱数据块中,拼凑出游戏的执行文件和美术资产。
2. **内存管理**:为你分配一个巨大的虚拟内存空间,制造出“这台电脑只有这一个游戏”的幻觉,并把刚才找到的文件放进物理内存的空隙里。
3. **进程管理**:在它的名册上新建一个“游戏进程”,并在下一个瞬间,立刻剥夺其他正在运行软件的 CPU 权利,把 CPU 的计算力全盘移交给你的游戏。
::: tip 💡 核心启示
**操作系统的本质是"资源管理"**。
- CPU 时间是资源 → 进程管理
- 内存空间是资源 → 内存管理
- 磁盘空间是资源 → 文件系统
理解了这一点,你就会明白:
- 为什么电脑会变慢(进程太多、内存不足)
- 为什么需要重启(清理资源、释放内存)
- 为什么文件要整理(提高查找效率)
:::
我们之所以能那么轻松、优雅地在数字世界里冲浪,全都是因为底层的操作系统在替我们负重前行。
---
## 延伸阅读
- **操作系统原理**:深入学习进程调度、内存分页、文件系统实现
- **Linux 系统编程**:学习如何与操作系统交互(系统调用
- **并发编程**:学习多进程、多线程编程
- **系统监控**:学习使用 top、htop、vmstat 等工具监控系统状态
如果你觉得操作系统的各种“管理学”十分有趣,你可以看看这些进阶话题:
- **进程与线程的区别**:除了进程,还有一种叫作“线程”的东西,它们是干什么用的?(为什么 Google Chrome 那么吃内存?
- **页面置换算法**:当物理内存全都塞满了,但你又打开了一个新软件,操作系统该把谁的数据临时踢到硬盘里?(LRU 算法)
- **操作系统的多态**Windows 和 macOS 会在底层实现上有什么不同?为什么有些软件只能在特定系统上运行?
@@ -8,96 +8,73 @@
## 0. 全景图:从沙子到智能
现代计算机的"思考"能力,归根结底来自于一个简单的东西:**开关**。
在探索计算机底层的过程中,常常会遇到一个最根本的问题:**现代计算机的思考能力,究竟从何而来?**
想象你有一个开关,可以控制灯的亮灭。现在,如果你有几十亿个这样的开关,并且能用它们组合出各种复杂的逻辑,会发生什么?这就是计算机的奥秘
如果剥开电脑闪亮的外壳,我们看到的通常只是一堆金属、塑料和硅晶片。它们本身没有生命,不懂数学,更不懂何为智能。但当电流穿过它们时,一切开始运转起来。归根结底,这一切都来自于一个再简单不过的物理抽象:**开关**
**从沙子到智能的层次结构:**
想象你面前有一个控制灯泡的开关。按下灯亮,表示为“1”;断开灯灭,表示为“0”。如果我们拥有几十亿个这样的开关,并且能够让**一个开关的输出去控制另一个开关**,从而组合出无比复杂的逻辑网络,会发生什么?
| 层级 | 名称 | 数量级 | 作用 | 类比 |
| ----- | -------- | ------ | ---------------------------- | -------- |
| **1** | 晶体管 | 数十亿 | 最基本的开关单元 | 一个开关 |
| **2** | 逻辑门 | 数亿 | 实现基本逻辑运算 | 开关组合 |
| **3** | 功能单元 | 数百 | 实现特定功能(加法、存储等) | 功能模块 |
| **4** | CPU 核心 | 1-128 | 完整的处理器 | 大脑 |
答案是一台能执行任意逻辑的通用计算平台。理解计算机系统的关键在于“抽象(Abstraction)”。就像搭积木一样,我们通过层叠的封装来控制底层的复杂度。以下是从沙子到智能的四个核心层级:
::: tip 逐行解读这张表
**第1层(晶体管)**:这是最底层的"开关"。现代 CPU 使用的是 MOSFET(金属氧化物半导体场效应晶体管),它的特点是:给栅极加电压,源极和漏极之间就导通;不加电压,就断开。这就是"用电控制电"的开关。
::: tip 逐层解构:从沙子到智能
- **第一层:晶体管(数百亿级)**
这是最底层的“开关”。现代 CPU 内部主要使用 MOSFET(金属氧化物半导体场效应晶体管)。给栅极施加电压,源极和漏极之间就导通。这就是“用电控制电”的物理起点,解决的核心问题是:**如何用电信号控制另一个电信号?**
**第2层(逻辑门)**:把晶体管组合起来,就能实现"与"、"或"、"非"等逻辑运算。比如 AND 门:两个输入都为 1 时输出才为 1。这就像两个串联的开关,必须都按下灯才会亮。
- **第二层:逻辑门(数十亿级)**
当我们把特定的晶体管串联或并联,奇妙的转换就发生了——电路变成了数学。例如 AND(与)门必须两个输入都是 1,输出才是 1;这构成了布尔代数在物理电路上的映射,解决的核心问题是:**如何把物理通断转化为基于 0 和 1 的逻辑运算?**
**第3层(功能单元)**:把逻辑门组合起来,就能实现更复杂的功能。加法器能做加法,寄存器能存储数据,多路选择器能选择数据。这些是 CPU 的"器官"。
- **第三层:功能单元(数百级**
把基础的逻辑门拼装在一起,就能构建出有特定用途的计算模块。加法器处理算术运算,多路选择器控制数据流向,而寄存器赋予了电路记忆能力。解决的核心问题是:**如何构造出能够执行加法计算和记忆状态的机器?**
**第4层(CPU 核心)**:把功能单元组合起来,加上控制器、总线等,就形成了一个完整的 CPU 核心。它能取指令、解码、执行、写回结果——这就是"计算"的全部过程。
- **第四层:CPU 核心1-128核**
这是整个微架构的指挥中心。当你写下一行代码时,CPU 内部的各个部件正以每秒几十亿次的频率协同工作,执行着取指、解码、执行、写回的整个流程。解决的核心问题是:**如何让各模块协同一致,自动执行指定的程序序列?**
:::
---
## 1. 晶体管:数字世界的开关
让我们从微观世界开始。下面这个组件展示了晶体管的基本原理,你可以试着操作一下,观察电流是如何流动的:
<TransistorDemo />
### 1.1 什么是晶体管?
::: tip 晶体管是什么?
**晶体管(Transistor** 是一种半导体器件,它可以像开关一样控制电流的通断
::: tip 概念引入
在工程学中,**晶体管(Transistor** 是一种改变了人类历史的半导体器件。在数字电路的语境下,我们可以直接把它抽象为一个完美的“开关”
**生活类比**:想象一个水龙头:
为什么我们需要晶体管?想想生活中的水龙头。你用手拧开阀门,水流就涌出。**晶体管其实就是一个纳米级的水龙头**
- **源极 (Source)****漏极 (Drain)** 就如同水管的两端。
- **栅极 (Gate)** 就是那个用来控制水流的阀门。
- **水龙头**:你用手拧开关,控制水流
- **晶体管**:用电压控制开关,控制电流
关键区别是:晶体管不是用手拧,而是用"电"来控制。这意味着一个开关可以控制另一个开关,从而实现"自动控制"。
关键的区别在于:我们不是用手拧开关,而是用**电压信号**。当一种开关能够被另一种开关产生的电信号所控制时,我们就跨过了从“人工干预”到“自动运算”的巨大鸿沟。
:::
**晶体管的三个极:**
| 极 | 名称 | 作用 | 类比 |
| ----------------- | -------- | -------------- | ---------- |
| **源极 (Source)** | 电流入口 | 电流从这里进入 | 水管入口 |
| **漏极 (Drain)** | 电流出口 | 电流从这里流出 | 水管出口 |
| **栅极 (Gate)** | 控制端 | 控制是否导通 | 水龙头开关 |
### 1.2 晶体管如何表示 0 和 1?
计算机只认识 0 和 1,这和晶体管有什么关系
你可能会问:计算机所谓的“只认识 0 和 1”,在物理世界中究竟是什么样子?难道芯片里真的流淌着微小的 0 和 1 吗
::: tip 用电压表示 0 和 1
**核心思想**:用电压的高低来表示 0 和 1。
当然不是。这一切全靠人为的**抽象约定**。我们要摒弃对连续模拟信号的执念,设定两个极端阈值:
- **高电压(如 3.3V**:表示 1
- **低电压( 0V**:表示 0
- 我们把**高电压(如 3.3V 或 1.0V)** 强行定义为逻辑的 **1**True)。
- **低电压(接近 0V** 强行定义为逻辑的 **0**False)。
这就像灯泡的亮和灭:
这就是所谓的数字抽象能力:我们把充满噪音的模拟世界,硬生生地切分成了干净利落的 0 和 1。栅极输入高电压,晶体管导通,相当于开关合上;栅极输入低电压,开关断开。
- 灯亮 = 1
- 灯灭 = 0
### 1.3 晶体管数量的演进
晶体管的作用就是"控制灯泡的亮灭"——给栅极加高电压,源极和漏极导通,"灯泡"亮了(输出 1);给栅极低电压,源极和漏极断开,"灯泡"灭了(输出 0
:::
一个晶体管只能控制通断,显得极其微不足道。但如果把几十亿个这样的开关组合起来呢?观察下面这张体现摩尔定律的表格,了解一下现代芯片的发展
### 1.3 从一个开关到几十亿
| 时代标志 | 处理器芯片 | 晶体管数量 | 制程节点 | 时代意义 |
| -------- | ---------------- | ---------- | -------- | ---------------------- |
| 1971 | Intel 4004 | 2,300 | 10微米 | 微处理器黎明开端 |
| 1993 | Intel Pentium | 310万 | 800纳米 | 个人电脑全面普及 |
| 2006 | Intel Core 2 Duo | 2.91亿 | 65纳米 | 多核架构成为主流 |
| 2020 | Apple M1 | 160亿 | 5纳米 | 移动端架构的反哺革命 |
| 2023 | Apple M3 Max | 920亿 | 3纳米 | 接近原子的物理学极限 |
你可能好奇:一个开关能做什么?答案是:一个开关做不了什么,但几十亿个开关组合起来,就能做任何计算。
**现代 CPU 的晶体管数量:**
| 年份 | CPU | 晶体管数量 | 制程工艺 |
| ---- | ------------- | ---------- | -------- |
| 1971 | Intel 4004 | 2,300 | 10μm |
| 1993 | Intel Pentium | 310万 | 0.8μm |
| 2006 | Intel Core 2 | 2.91亿 | 65nm |
| 2020 | Apple M1 | 160亿 | 5nm |
| 2023 | Apple M3 Max | 920亿 | 3nm |
::: tip 什么是制程工艺?
**制程工艺**(如 5nm、3nm)指的是晶体管的尺寸。数字越小,晶体管越小,同样面积能容纳的晶体管越多。
- **5nm**:大约是 50 个原子的宽度
- **3nm**:大约是 30 个原子的宽度
制程越小,CPU 性能越强、功耗越低。但制造难度也指数级增加。
:::
> **深入思考:什么是 “3nm”?**
> 当我们在新闻里听到 5nm、3nm 时,可以想象它有多微小。一个硅原子的直径大约是 0.2 纳米。所以在 3nm 的制程下,晶体管最关键的结构,只有几十个原子那么宽幅!这意味着我们是在量子力学规律生效的尺度边缘,来打造人类最庞大的算力堡垒。
---
@@ -105,184 +82,165 @@
### 2.1 从晶体管到逻辑门
一个晶体管只是一个开关,但把多个晶体管组合起来,就能实现"逻辑运算"
正如之前所说,单个晶体管只是对电流的简单控制。但当你把多个晶体管按照特定的结构排列时,物理学就变成了数学逻辑。在这个全新的维度上,我们不再谈论繁琐的电压和电流,而是直接谈论纯粹的逻辑“真”(1)与“假”(0)
请通过下面的逻辑门演示,直观地感受一下开关组合的效果:
<LogicGateDemo />
### 2.2 基本逻辑门详解
### 2.2 基本逻辑门介绍
**AND 门(与门)**
在我们的计算机体系结构中,有几种最基础的逻辑门,所有的超级计算机都是由这些积木搭建而成的
- **规则**:两个输入都为 1,输出才为 1
- **生活类比**串联的两个开关,必须都按下灯才亮
- **应用**判断"多个条件是否同时满足"
- **AND 门(与门)**
- **规则**只有当所有输入都为 1 时,输出才为 1。
- **直觉理解**把两个晶体管**串联**。电流要想通过,必须同时打开两道关卡。如同开启银行金库,必须经理和主管同时插入各自的钥匙。
**OR 门(或门)**
- **OR 门(或门)**
- **规则**:只要有一个输入为 1,输出就为 1。
- **直觉理解**:把两个晶体管**并联**。多条并行的通道,只要有一条路通了,电流就能流向彼岸。
- **规则**:任一个输入为 1,输出就为 1
- **生活类比**并联的两个开关,按任意一个灯就亮
- **应用**判断"是否满足任一条件"
- **NOT 门(非门 / 反相器)**
- **规则**输入 1 必定输出 0,输入 0 必定输出 1。
- **直觉理解**这是专门用来翻转状态的门,也是电路设计中经常用于信号整形的关键防线。
**NOT 门(非门)**
- **XOR 门(异或门)**
- **规则**:当两个输入**不相同**时,输出恰好为 1。
- **直觉理解**:你可以把它理解为一个“侦测差异”的精密机器。这是我们在电路中执行二进制加法的杀手锏。
- **规则**:输入和输出相反
- **生活类比**:反相器,开变关、关变开
- **应用**:取反操作
### 2.3 用逻辑门实现加法
**XOR 门(异或门)**
如果刚才介绍的逻辑门只能做简单的条件判断,那计算机到底是如何做数学运算的呢?
- **规则**:两个输入不同时输出 1
- **生活类比**:判断"两个值是否不同"
- **应用**:比较、加法运算
我们先回想一下手算加法的方式:对应位相加,如果超出了限制(十进制是满十进一,二进制是满二进一),就向更高位“进位”。
### 2.3 用逻辑门做加法
在二进制中,只有 0 和 1。对于一位数的加法,可能的情况只有四种:
- `0 + 0 = 0` (本位是 0,不进位)
- `0 + 1 = 1` (本位是 1,不进位)
- `1 + 0 = 1` (本位是 1,不进位)
- `1 + 1 = 10` (本位是 0,进位 1
仔细观察这四种情况,你会发现:
1. **本位的结果**,只有在两个输入**不同**时才为 1,这正是 **XOR 门(异或门)** 的逻辑。
2. **进位的结果**,只有在两个输入**都为 1** 时才为 1,这正是 **AND 门(与门)** 的逻辑。
因此,只要把一个 XOR 门和一个 AND 门组合起来,我们就得到了能计算一位数加法的电路,这也是最基础的**半加器(Half Adder**。
<AdderDemo />
::: tip 💡 加法器是怎么工作的?
**半加器**:处理两个 1 位二进制数相加
- 输入:A、B(各 1 位)
- 输出:和(S)、进位(C
- 公式:S = A XOR BC = A AND B
**全加器**:处理两个 1 位二进制数相加,加上上一位的进位
- 输入:A、B、Cin(进位输入)
- 输出:和(S)、Cout(进位输出)
**多位加法器**:把多个全加器级联起来
- 第 1 位加法器的进位输出,连接到第 2 位加法器的进位输入
- 就像我们手算加法时"逢二进一"
::: tip 核心解析:分解加法器
为了处理真实世界中更复杂的数字,加法器需要像搭积木一样拼装:
1. **半加器(Half Adder**:它可以处理两个一位数相加(即上述 XOR 和 AND 门的组合)。它计算了本位和进位,但没法接收来自更低位的进位。
2. **全加器(Full Adder**:在多位计算中,中间位数除了要把 A 和 B 加起来,还要处理来自低位的进位(Carry In)。把低位进位也加入逻辑后,就是全加器。
3. **行波进位加法器(Ripple Carry Adder**:要想处理 32 位或 64 位的数字,只需要把几十个全加器串联起来。进位信号便像波浪一样从低位一层层涌向高位,从而完成任意大小的加法。
:::
---
## 3. 功能单元:逻辑门的组合
### 3.1 常见功能单元
现在,手里握着逻辑门构成的积木,我们可以向更高的抽象层跃进了。单单计算加法是不够的,我们将成组的逻辑门打包,组装成具有特定功能的模块。这些模块我们统称为**功能单元(Functional Units**。
| 单元 | 功能 | 组成 | 类比 |
| -------------- | -------- | ---------------- | ---------------- |
| **加法器** | 做加法 | 多个全加器级联 | 计算器的加法功能 |
| **多路选择器** | 选择数据 | AND 门 + OR 门 | 多选一开关 |
| **译码器** | 解码指令 | 多个 AND 门 | 翻译器 |
| **寄存器** | 存储数据 | 触发器(锁存器) | 临时笔记本 |
| **计数器** | 计数 | 触发器级联 | 计分牌 |
### 3.1 常见功能模块分类
在设计 CPU 时,有一些经过时间考验的经典预制模块:
| 模块名称 | 承担的核心使命 | 内部的逻辑构造本质 | 现实生活中的绝佳隐喻 |
| -------------- | ------------------------------------ | ------------------------------------ | -------------------- |
| **加法器(Adder)** | 处理各种类型的算术运算引擎 | 海量全加器的高级按位级联 | 不知疲倦的算盘 |
| **多路选择器(MUX)** | 控制数据的流向途径,实现多选一通道 | 巧妙融合 AND 门作为开关、OR 门进行汇总 | 铁路线上的精密道岔 |
| **译码器(Decoder)** | 破解并翻译外部传入的二进制死指令 | 基于输入状态精确点亮特定输出的门阵列 | 破获密电的翻译员 |
| **触发器(Flip-Flop)**| 突破电信号转瞬即逝的限制,记录历史 | 极其微妙的交叉反馈环路构成双稳态模式 | 会保持状态的跷跷板 |
为了直观地感受这些功能单元是如何工作的,你可以操作下面的组件,分别查看**多路选择器**和**译码器**的内部逻辑:
<FunctionalUnitDemo />
请通过下面这款组件实验,亲自窥探其中最令人着迷的部分——**记忆是如何凭空产生的**:
<RegisterDemo />
### 3.2 寄存器:存储 1 位数据
### 3.2 寄存器:数据的存储单元
::: tip 💡 寄存器是怎么存储数据的?
寄存器使用**触发器**电路来存储数据。触发器的特点是:一旦设置了状态,就能保持住,直到下一次改变。
除了计算,计算机还需要能够长期或临时地记住数据。如果在运算过程中丧失了对前一秒的记忆,那任何复杂的计算都无法进行。计算机必须拥有某种手段保留过去的状态,这种能力主要仰仗于一种名为**触发器(Flip-Flop)**的电路结构。
**生活类比**:想象一个跷跷板:
::: tip 深入理解:记忆本质上是一种循环
大多数逻辑电路的信号流向都是向前的(前馈回路)。而要产生持续的“记忆”,早期的先驱们想到了一个绝妙的设计:将输出的电波重新反馈回输入端。
- 推一下左边,左边就沉下去,右边翘起来
- 即使你松手,跷跷板也会保持这个状态
- 只有再推一下,才会改变状态
如同一个有着两个稳定静止点的精巧跷跷板结构。只要不受外界扰动,它凭借其闭环的设计,会永久性稳固在“左高右低(例如这就是记住了 0)”抑或是相反状态(记住了 1)。即便是转瞬即逝的状态改变,也能因闭环相互锁定而被长久“深锁”。
触发器就是这样的"电子跷跷板",能"记住"上一次被设置的状态
当我们将 32 个抑或 64 个这种触发器整齐地编排成一列,施加同一种强劲的时钟频率信号(Clock)来号令它们统一行动时,**寄存器(Register)**便应运而生了。它身居 CPU 系统的心脏位置,被当做极速的“工作草稿纸”,默默捍卫着你每一个即时的关键变量
:::
---
## 4. CPU 架构:从功能单元到处理器
随着各种运算模块和记忆组件设计完毕,现在到了核心的综合阶段。如何将这些模块组合起来,让它们变成能自动执行指令的中央处理器(CPU)?
### 4.1 CPU 的核心组件
CPU 不是单一部件,而是多个功能单元协作工作
如果把 CPU 看作一个分工明确的机器,那么每个单元都有自己不可替代的位置
| 组件 | 做什么 | 类比 |
| ------------ | ------------------------------ | ------------------------ |
| **控制器** | 取指、解码、发出控制信号 | 像指挥员,安排谁何时工作 |
| **ALU** | 加减、与、或、比较等运算 | 像计算器,做算术与逻辑 |
| **寄存器组** | 保存最常用的数据和中间结果 | 像桌面便签,比内存更快 |
| **内部总线** | 在模块间传数据、地址、控制信息 | 像高速通道,把组件连成整体 |
一句话:控制器负责调度,ALU 负责计算,寄存器负责高速暂存,总线负责连接与传输。
- **算术逻辑单元 (ALU)**:负责“干活”的运算单元,专门执行加减乘除和各种逻辑运算。
- **寄存器组 (Register File)**:工作台上的临时抽屉,容量很小但速度极快,用于暂存当前正在计算的紧迫参数。
- **内部总线 (Internal Bus)**:系统里的传送带,负责在各个模块之间搬运数据和信号。
- **控制单元 (Control Unit)**:总指挥。它的使命就是从内存中读取用 0 和 1 组成的指令,解析出应该做什么,并向其他模块传达具体的控制信号,调度它们各司其职。
### 4.2 CPU 是如何执行指令的?
CPU 执行一条指令,需要经过四个阶段
不管写下的高级编程语言有多么复杂,最终都会变成内存中的一条条底层指令。CPU 执行任何指令的过程,本质上都在重复以下典型的四个步骤
| 阶段 | 名称 | 做什么 | 类比 |
| ----- | ----------------- | ---------------- | ------------------ |
| **1** | 取指 (Fetch) | 从内存读取指令 | 从书架上取书 |
| **2** | 解码 (Decode) | 分析指令要做什么 | 阅读书的内容 |
| **3** | 执行 (Execute) | 执行运算 | 按书中的指示行动 |
| **4** | 写回 (Write Back) | 把结果存回寄存器 | 把结果记在笔记本上 |
1. **取指 (Fetch)**:循着当前程序执行的光标地址,探入相对漫长迟缓的缓存之中,把下一套二进制“指令”硬生生抓进核心。
2. **译码 (Decode)**:指挥大脑马上分析:这道命令具体是要我移动内存,还是呼叫加法器拼凑运算?立刻将所需电路彻底连通唤醒。
3. **执行 (Execute)**:指令派单到达诸如 ALU 等业务工厂车间,机器轰鸣,全力以赴进行硬核逻辑翻转。
4. **写回 (Write Back)**:成果凝结时刻,将刚刚得手的答案慎重写至特定的寄存器或反馈回宽阔的内存。
::: tip 💡 指令周期
这四个阶段组成一个**指令周期**。CPU 不断重复这个周期,一条一条执行指令,就实现了"计算"。
点击下方的“时钟脉冲”,观察在这个死循环中,指令是如何一步步被拆解、执行,并涉及哪些硬件模块的:
现代 CPU 使用**流水线技术**,让多个指令的不同阶段并行执行:
<CpuArchitectureDemo />
- 第 1 条指令在执行时
- 第 2 条指令在解码
- 第 3 条指令在取指
::: tip 追求效率的极致:流水线(Pipeline)
如果必须等上一条指令经历了这四个步骤后才开始下一条指令,效率显然太低。
就像工厂流水线,大大提高了效率
就像工厂流水线一样,芯片工程师引入了**指令流水线技术**。这意味着当第一部分电路在对指令 A 进行“执行”时,之前的电路并没有闲着,而是去对指令 B 进行“解码”,甚至是把指令 C 提前“取指”拿了出来。通过这种并行的重叠方式,CPU 的执行效率得到了极大的提升
:::
### 4.3 CPU 性能的关键指标
| 指标 | 含义 | 影响 | 典型值 |
| ---------- | ---------------------- | ---------------------- | -------- |
| **主频** | 每秒执行多少个时钟周期 | 主频越高,执行越快 | 3-5 GHz |
| **核心数** | 独立的处理器数量 | 核心越多,并行能力越强 | 4-64 核 |
| **缓存** | CPU 内部的高速存储 | 缓存越大,访问内存越少 | 8-64 MB |
| **指令集** | CPU 能理解的指令集合 | 决定兼容性和功能 | x86、ARM |
---
## 5. 总结:从沙子到智能
## 5. 总结:跨越抽象层级
每一层都是对下一层的抽象封装,从沙子到可运行软件的完整路径如下
回顾这一路,我们经历了计算机体系结构中最核心的层层抽象。这是将底层物理材料变为通用计算平台的完整路径:
1. **沙子(硅)** — 原材料:地球上最丰富的元素之一,提炼出高纯度硅。
↓ 提纯 → 切割成晶圆
1. **宏观物理:沙子(二氧化硅晶体)**
→ *接受人类冶炼、切片、剧毒气体蚀刻等种种苛刻雕琢后*
2. **微观物理:海量的晶体管开关** (以微电控微电)
→ *经过工程大牛不眠不休的密集拉线,实现了惊人的数字抽象约束*
3. **数字代数:AND / OR / NOT 逻辑门体系**
→ *无情抹杀误差,以完美真值表衍生出基础行为*
4. **微架构模块:功能单元积木集(加法器等组件)**
→ *加入了系统生命节拍与记忆特性,进化为完整功能体*
5. **复杂体系结构:庞大而精妙的 CPU 联合阵列**
→ *面向全世界开发极客,彻底敞开了通往虚拟应用世界的大门*
6. **万千应用王国:算法、系统级软件以及繁花似锦的互联网宇宙**
2. **硅晶圆** — 基底:直径约 30cm 的单晶硅片,表面极其光滑。
↓ 光刻 → 蚀刻 → 掺杂
计算机科学中最令人着迷的部分在于,**每一层封装都完美地隐藏了下一层的复杂细节**。作为一个软件开发者,当你写下 `salary = base + bonus` 时,完全不需要考虑底层电子的漂移以及半加器内电流的走向;同样,芯片硬件设计师也不需要操心这块芯片未来将运行什么软件。
3. **晶体管(开关)** — 数百亿个/芯片:Gate=1 导通,Gate=0 断开,用电压控制电流。
↓ 组合成逻辑电路
正是极端的层级解耦以及高度互不干扰的黑盒封装,合力孕育、铺就了现代科技的狂欢盛世。
4. **逻辑门** — 数十亿个:AND / OR / NOT / XOR,实现基本布尔运算。
↓ 组合成功能模块
::: tip 终极思考
**归根究底,所谓的算力,不过是有限的密闭空间内海量开关重组的变幻;伴随着时钟的节拍,在这片小小的硅片上完成了复杂的运算。**
5. **功能单元** — 数百个:加法器、寄存器、多路选择器……各司其职。
↓ 集成为完整处理器
6. **CPU 核心** — 1128 核:ALU + 控制器 + 寄存器组,执行取指→解码→执行→写回。
↓ 软件编程
7. **软件应用** — 无限可能:操作系统 / AI 模型 / 游戏 / 网页……一切皆指令。
计算机的本质是「开关的组合」:通过一层层抽象封装,最底层的物理材料最终变成能执行任意逻辑的通用计算平台。
::: tip 核心启示
**计算机的本质是"开关的组合"**。
- 一个开关做不了什么
- 但几十亿个开关,按特定方式组合,就能执行任何计算
- 这就是"量变引起质变"的最好例证
理解这一点,你就会明白:
- 为什么计算机只认识 0 和 1
- 为什么编程语言最终都要翻译成机器码
- 为什么算法效率如此重要(因为每一步操作都需要大量晶体管参与)
“量变最终引发质的飞跃”,这句话在计算机体系结构中被不断验证。当我们敲下键盘,注视着屏幕时,可以试着想象:在极其微小的硅基深处,此刻正有百亿级极小的晶体管,在电光火石之间拼尽全力进行着精密的协同。这或许就是最独特的计算机科学之美。
:::
---
## 延伸阅读
- **计算机组成原理**:深入了解 CPU、内存、I/O 的工作原理
- **数字电路**学习逻辑门、触发器、时序电路的设计
- **计算机体系结构**:研究 CPU 的性能优化、流水线、缓存等
- **汇编语言**:直接和 CPU 对话,理解指令执行过程
如果你对底层技术充满好奇,可以尝试在以下几个方向继续探索:
- **经典教材**《计算机组成与设计(软硬件接口)》是深入学习体系结构的一本很好的参考书。
- **数字逻辑仿真**:尝试使用逻辑仿真软件或基础元器件,动手搭建一个简单的 8 位加法器或模拟器。
- **体系结构前沿**:了解多级缓存如何缓解“内存墙”问题、指令乱序执行的原理,以及 GPU 的特殊运算机制等。
- **底层与汇编语言**:尝试学习一些基础汇编语言,理解高级语言最终是如何被转化为机器可以执行的十六进制指令的。
@@ -755,7 +755,7 @@ console.log('4. End')
`setTimeout(0)`不保证立即执行,它至少会被延迟到当前调用栈清空、微任务队列清空之后。
:::
<EventLoopDemo />
<JSEventLoopDemo />
<MacroMicroTaskDemo />
@@ -78,7 +78,46 @@ HTTP/1.1 200 OK
---
## 2. RESTful 设计:让 URL 会说话
## 2. API 设计哲学:RPC / REST / GraphQL / gRPC
在开始具体的 RESTful 设计之前,先了解四种主流的 API 设计风格:
<ApiStyleCompare />
### 2.1 REST vs RESTful:有什么区别?
很多人会混淆这两个概念:
| 概念 | 含义 | 说明 |
| :--- | :--- | :--- |
| **REST** | 一种架构风格 | 由 Roy Fielding 提出的设计理念,包含一组约束条件 |
| **RESTful** | 符合 REST 风格的 | 形容词,表示 API 设计遵循了 REST 原则 |
**类比**
- REST 就像"极简主义"——一种设计理念
- RESTful API 就像"极简风格的房间"——应用了这个理念的具体实现
**REST 的六大约束**
| 约束 | 说明 |
| :--- | :--- |
| **客户端-服务器分离** | 前后端独立开发,接口解耦 |
| **无状态** | 每个请求包含所有必要信息,服务器不保存会话状态 |
| **可缓存** | 响应应标明是否可缓存,提高性能 |
| **统一接口** | 使用标准的 HTTP 方法和状态码 |
| **分层系统** | 客户端无需知道连接的是哪层服务器 |
| **按需代码**(可选) | 服务器可以扩展客户端功能 |
::: tip 💡 为什么 REST 最常用?
1. **学习成本低**:HTTP 协议本身就体现了 REST 思想
2. **生态成熟**:工具、框架、文档丰富
3. **通用性强**:任何语言、任何平台都能调用
4. **易于缓存**GET 请求天然可缓存,CDN 友好
:::
---
## 3. RESTful 设计:让 URL 会说话
**REST**Representational State Transfer)是一种架构风格,核心思想是:
@@ -86,7 +125,7 @@ HTTP/1.1 200 OK
- 用 URL 标识资源
- 用 HTTP 方法操作资源
### 2.1 用仓库来类比
### 3.1 用仓库来类比
| 仓库概念 | REST 对应 | 示例 |
| :--- | :--- | :--- |
@@ -96,21 +135,21 @@ HTTP/1.1 200 OK
**关键原则**:URL 是名词,不是动词。
### 2.2 URL 设计规则
### 3.2 URL 设计规则
| 规则 | 错误示例 | 正确示例 | 说明 |
| :--- | :--- | :--- | :--- |
| **用名词不用动词** | `GET /getUsers`<br>`POST /createOrder` | `GET /users`<br>`POST /orders` | URL 是资源地址HTTP 方法已表达操作 |
| **用复数形式** | `GET /user`<br>`GET /order` | `GET /users`<br>`GET /users/123` | 统一复数,避免 `/user``/users` 混用 |
| **小写+连字符** | `GET /UserProfiles`<br>`GET /user_profiles` | `GET /user-profiles`<br>`GET /order-items` | URL 大小写敏感,统一小写最安全 |
| **避免层级过深** | `GET /users/123/orders/456/items/789` | `GET /users/123/orders`<br>`GET /order-items/789` | 超过 3 层考虑重构,用扁平化路径 |
| **过滤用查询参数** | `GET /products/category/phone/price/5000` | `GET /products?category=phone&price_max=5000` | 过滤、排序、分页都用查询参数 |
| 用名词不用动词 | `/getUsers` | `/users` | URL 表示资源HTTP 方法表示操作 |
| 用复数形式 | `/user` | `/users` | 统一复数风格 |
| 小写+连字符 | `/UserProfiles` | `/user-profiles` | URL 大小写敏感 |
| 避免层级过深 | `/a/b/c/d/e` | `/a/b/c` | 最多 3 层 |
| 过滤用查询参数 | `/products/phone/5000` | `/products?cat=phone` | 过滤条件用 `?` 参数 |
::: tip 💡 URL 大小写敏感
统一用小写 + 连字符(-)是最安全的做法,避免大小写混乱和下划线风格不一致的问题。
:::
### 2.3 HTTP 方法选择
### 3.3 HTTP 方法选择
| 方法 | 用途 | 幂等性 | 安全性 | 典型场景 |
| :--- | :--- | :--- | :--- | :--- |
@@ -131,11 +170,11 @@ HTTP/1.1 200 OK
---
## 3. 状态码:让错误"会说话"
## 4. 状态码:让错误"会说话"
HTTP 状态码是服务器告诉客户端"发生了什么"的标准方式。
### 3.1 状态码分类
### 4.1 状态码分类
| 分类 | 含义 | 典型状态码 |
| :--- | :--- | :--- |
@@ -144,7 +183,7 @@ HTTP 状态码是服务器告诉客户端"发生了什么"的标准方式。
| **4xx** | 客户端错误 | 400 参数错误、401 未认证、404 不存在 |
| **5xx** | 服务端错误 | 500 内部错误、503 服务不可用 |
### 3.2 常用状态码演示
### 4.2 常用状态码演示
👇 **动手试试看**:点击下方按钮,了解常见状态码的含义:
@@ -152,7 +191,7 @@ HTTP 状态码是服务器告诉客户端"发生了什么"的标准方式。
---
## 4. 错误处理:优雅地"拒绝"
## 5. 错误处理:优雅地"拒绝"
好的错误处理能让客户端"看状态码就知道怎么回事",而不是去猜。
@@ -188,7 +227,7 @@ HTTP/1.1 500 Internal Server Error
危险:暴露了代码结构、数据库查询,攻击者可以利用这些信息。
### 4.2 正确的错误处理演示
### 5.2 正确的错误处理演示
👇 **动手试试看**:对比"好的"和"差的"错误响应设计:
@@ -196,9 +235,9 @@ HTTP/1.1 500 Internal Server Error
---
## 5. 版本控制:API 的"向后兼容"
## 6. 版本控制:API 的"向后兼容"
### 5.1 为什么要版本控制?
### 6.1 为什么要版本控制?
场景:你的 App 有 100 万用户,需要修改订单接口。
@@ -210,7 +249,7 @@ HTTP/1.1 500 Internal Server Error
- `/v1/orders` - 旧接口,继续服务旧 App
- `/v2/orders` - 新接口,新功能在这里
### 5.2 版本控制策略
### 6.2 版本控制策略
| 策略 | 示例 | 优点 | 缺点 |
| :--- | :--- | :--- | :--- |
@@ -218,66 +257,251 @@ HTTP/1.1 500 Internal Server Error
| **请求头** | `Accept: vnd.api.v2+json` | URL 干净 | 不便调试 |
| **查询参数** | `/users?version=2` | 简单 | 不够标准 |
### 5.3 版本控制演示
### 6.3 版本演进示例
👇 **动手试试看**:了解 API 版本控制的策略和最佳实践
以用户接口为例,展示 v1 到 v2 的演进
<ApiVersioningDemo />
| 接口 | v1(旧版) | v2(新版) | 变化说明 |
| :--- | :--- | :--- | :--- |
| **获取用户** | `GET /v1/users`<br>返回:`name, email` | `GET /v2/users`<br>返回:`name, email, avatar, phone` | 新增头像、手机号字段 |
| **创建订单** | `POST /v1/orders`<br>接收:`items[]` | `POST /v2/orders`<br>接收:`items[], coupons[]` | 新增优惠券支持 |
| **批量操作** | 无 | `POST /v2/orders/batch` | 新增批量创建接口 |
---
## 6. 响应结构:标准化的"数据契约"
无论成功还是失败,响应结构应该保持一致:
### 6.1 标准响应格式
```json
{
"code": 0,
"message": "success",
"data": { ... },
"request_id": "req-550e8400",
"timestamp": "2024-01-15T09:30:00.000Z"
}
```
| 字段 | 类型 | 说明 |
| :--- | :--- | :--- |
| `code` | number | 业务状态码,0 表示成功 |
| `message` | string | 状态描述 |
| `data` | any | 业务数据 |
| `request_id` | string | 请求唯一标识,用于问题追踪 |
| `timestamp` | string | 响应时间戳 |
### 6.2 分页响应格式
```json
{
"code": 0,
"data": {
"items": [...],
"pagination": {
"page": 1,
"page_size": 20,
"total": 156,
"total_pages": 8
}
}
}
```
::: tip 💡 为什么要 request_id
**request_id** 是问题追踪的关键:
1. 用户反馈:"支付失败,错误 ID 是 abc123"
2. 技术人员直接在日志里搜索 abc123,立即定位问题
3. 分布式系统中,每个服务都记录相同的 request_id,可以把所有相关日志聚合起来
::: tip 💡 版本控制最佳实践
- **保持向后兼容**:v1 接口至少维护 6-12 个月,给客户端升级时间
- **文档同步更新**:每个版本有独立的 API 文档
- **废弃公告**:提前通知 v1 将在何时下线,引导迁移
- **监控使用情况**:统计 v1 调用量,确认可以安全下线后再停止服务
:::
---
## 7. 实战:电商系统 API 设计示例
## 7. 响应结构设计
响应结构是前后端协作的"数据契约",统一格式能大幅降低沟通成本。
<ResponseStructureDemo />
### 7.1 大厂实践参考
::: details Google API 设计指南
参考 [Google API Design Guide](https://cloud.google.com/apis/design/errors)Google 要求所有 API 错误响应必须包含 `google.rpc.Status` 消息结构:
```json
{
"error": {
"code": 429,
"message": "资源不足,请稍后重试",
"status": "RESOURCE_EXHAUSTED",
"details": [
{
"@type": "type.googleapis.com/google.rpc.ErrorInfo",
"reason": "RESOURCE_AVAILABILITY",
"domain": "compute.googleapis.com",
"metadata": {
"zone": "us-east1-a",
"service": "compute"
}
}
]
}
}
```
**核心要求**
- 必须包含 `ErrorInfo` 提供机器可读的错误标识
- `message` 面向开发者,用简洁语言描述问题和解决方案
- `details` 数组可包含 `LocalizedMessage`(本地化消息)、`Help`(帮助链接)等
:::
::: details Microsoft REST API 指南
参考 [Microsoft REST API Guidelines](https://github.com/microsoft/api-guidelines/blob/vNext/Guidelines.md),微软强调响应的一致性:
**错误与故障的分类**
- **错误(Error**:客户端传递无效数据导致,返回 4xx,不影响 API 可用性
- **故障(Fault**:服务端无法正确响应有效请求,返回 5xx,影响 API 可用性
**响应标头规范**
- `Date`:必须返回,使用 RFC 5322 格式(GMT 时区)
- `Content-Type`:必须返回
- `ETag`:支持乐观并发控制的资源必须返回
:::
::: details 阿里巴巴 Java 开发手册
参考 [阿里巴巴 Java 开发手册](https://developer.aliyun.com/special/tech-java),阿里对 API 响应有以下规范:
**统一返回对象**
```java
public class Result<T> {
private Integer code;
private String message;
private T data;
private String requestId;
}
```
**错误码分段设计**
| 范围 | 类型 | 示例 |
| :--- | :--- | :--- |
| 0 | 成功 | 0 |
| 1xxxx | 参数错误 | 10001 缺少必填参数 |
| 2xxxx | 业务错误 | 20001 余额不足 |
| 3xxxx | 认证错误 | 30001 未登录 |
| 5xxxx | 系统错误 | 50001 数据库异常 |
:::
::: details Stripe API 响应设计
参考 [Stripe API Documentation](https://docs.stripe.com/api/errors),Stripe 的错误响应设计非常精细:
```json
{
"error": {
"type": "card_error",
"code": "card_declined",
"message": "Your card was declined.",
"param": "number",
"decline_code": "insufficient_funds",
"doc_url": "https://stripe.com/docs/error-codes/card-declined"
}
}
```
**设计亮点**
- `type` 区分错误类型:`api_error``card_error``invalid_request_error`
- `param` 指出具体哪个参数出错,前端可直接定位表单字段
- `doc_url` 提供文档链接,开发者可深入了解
- `decline_code` 提供更细粒度的错误原因
:::
::: details JSON:API 规范
参考 [JSON:API Specification](https://jsonapi.org/format/),这是一个业界广泛采纳的 JSON API 响应规范:
```json
{
"data": {
"type": "articles",
"id": "1",
"attributes": {
"title": "JSON:API 规范详解"
},
"relationships": {
"author": {
"data": { "type": "users", "id": "9" }
}
}
},
"included": [
{
"type": "users",
"id": "9",
"attributes": {
"name": "张三"
}
}
]
}
```
**核心设计**
- `data` 包含主资源,必须有 `type``id`
- `attributes` 存放资源属性
- `relationships` 描述资源关联
- `included` 避免重复请求,一次性返回关联数据
:::
::: details GitHub REST API 响应设计
参考 [GitHub REST API Documentation](https://docs.github.com/en/rest),GitHub 的响应设计注重开发者体验:
**成功响应**
```json
{
"id": 1296269,
"node_id": "MDEwOlJlcG9zaXRvcnkxMjk2MjY5",
"name": "Hello-World",
"full_name": "octocat/Hello-World",
"owner": {
"login": "octocat",
"id": 1,
"avatar_url": "https://github.com/images/error/octocat_happy.gif"
},
"private": false,
"html_url": "https://github.com/octocat/Hello-World"
}
```
**错误响应**
```json
{
"message": "Bad credentials",
"documentation_url": "https://docs.github.com/rest"
}
```
**设计亮点**
- 响应包含多种 URL 格式(`html_url``url`)方便不同场景使用
- 错误响应包含 `documentation_url` 指向文档
- 使用 `Link` 响应头实现分页导航
:::
::: details Twitter/X API v2 响应设计
参考 [Twitter API v2 Documentation](https://developer.twitter.com/en/docs/twitter-api)Twitter API v2 采用简洁的响应格式:
```json
{
"data": {
"id": "1460323737035677698",
"text": "Hello, Twitter!"
},
"includes": {
"users": [
{
"id": "2244994945",
"name": "Twitter Dev",
"username": "TwitterDev"
}
]
}
}
```
**设计亮点**
- `data` 包含主数据,`includes` 包含关联数据(类似 JSON:API)
- 支持字段选择:`?tweet.fields=created_at,public_metrics`
- 分页使用 `next_token``previous_token`
:::
### 7.2 最佳实践总结
综合以上规范,响应结构设计应遵循以下原则:
1. **一致性优先**:所有接口使用相同的响应结构,前端可统一封装请求层
2. **机器可读**:错误码 + 错误原因(reason)让程序能自动处理
3. **人类友好**:message 描述清晰,包含解决建议
4. **可追踪**request_id 贯穿请求全链路,便于问题定位
5. **国际化支持**:通过 details 扩展本地化消息
### 7.3 data 字段设计规范
`data` 是响应的核心,其设计直接影响前端开发效率。
<DataFieldDesignDemo />
### 7.4 错误响应设计进阶
<ErrorResponseDesignDemo />
::: tip 参考链接
- [Google API Design Guide - Errors](https://cloud.google.com/apis/design/errors)
- [Microsoft REST API Guidelines](https://github.com/microsoft/api-guidelines)
- [阿里巴巴 Java 开发手册](https://developer.aliyun.com/special/tech-java)
- [Heroku HTTP API Design Guide](https://github.com/interagent/http-api-design)
- [Stripe API - Errors](https://docs.stripe.com/api/errors)
- [JSON:API Specification](https://jsonapi.org/format/)
:::
---
## 8. 实战:电商系统 API 设计示例
```
# 用户模块
@@ -300,6 +524,100 @@ GET /v1/products?category=phone&price_max=5000&sort=price_desc&page=1
---
## 9. 用 AI 辅助设计 API
AI 可以帮助你快速生成符合规范的 API 设计。关键在于提供清晰的上下文和约束条件。
### 9.1 提示词模板
```
你是一位资深的后端架构师,精通 RESTful API 设计。请帮我设计一套 API 接口。
## 业务背景
[描述你的业务场景,例如:电商系统、博客平台、任务管理等]
## 功能需求
[列出需要的功能模块,例如:
- 用户管理:注册、登录、个人信息
- 订单管理:创建订单、查询订单、取消订单
- 商品管理:商品列表、商品详情、搜索]
## 设计要求
1. 遵循 RESTful 规范
2. URL 使用名词复数,小写+连字符
3. 正确使用 HTTP 方法(GET/POST/PUT/PATCH/DELETE
4. 统一的响应格式:{ code, message, data, request_id }
5. 合理的状态码使用
6. 版本控制:URL 路径方式(/v1/)
## 输出格式
请按以下格式输出:
### 接口列表
| 方法 | URL | 描述 | 请求体 | 响应体 |
|------|-----|------|--------|--------|
### 请求/响应示例
[关键接口的详细示例]
### 状态码说明
[使用的状态码及其含义]
```
### 9.2 实战示例:电商订单 API
**输入提示词:**
```
你是一位资深的后端架构师,精通 RESTful API 设计。请帮我设计一套电商订单系统的 API 接口。
## 业务背景
一个 B2C 电商平台,用户可以浏览商品、下单购买、查看订单状态。
## 功能需求
- 订单模块:创建订单、查询订单列表、查询订单详情、取消订单、支付订单
- 购物车模块:添加商品、修改数量、删除商品、查看购物车
## 设计要求
1. 遵循 RESTful 规范
2. URL 使用名词复数,小写+连字符
3. 正确使用 HTTP 方法
4. 统一的响应格式
5. 版本控制:/v1/
```
**AI 输出示例:**
| 方法 | URL | 描述 |
| :--- | :--- | :--- |
| `POST` | `/v1/orders` | 创建订单 |
| `GET` | `/v1/orders` | 查询订单列表 |
| `GET` | `/v1/orders/{id}` | 查询订单详情 |
| `PATCH` | `/v1/orders/{id}/status` | 更新订单状态(取消/支付) |
| `GET` | `/v1/users/{id}/cart` | 获取购物车 |
| `POST` | `/v1/users/{id}/cart/items` | 添加商品到购物车 |
| `PATCH` | `/v1/users/{id}/cart/items/{itemId}` | 修改购物车商品数量 |
| `DELETE` | `/v1/users/{id}/cart/items/{itemId}` | 删除购物车商品 |
### 9.3 AI 辅助设计的注意事项
| 注意点 | 说明 |
| :--- | :--- |
| **提供完整上下文** | 业务背景、用户角色、数据关系都要说清楚 |
| **明确约束条件** | 命名规范、版本策略、响应格式等要提前定义 |
| **迭代优化** | 第一次输出可能不完美,追问细节、要求修改 |
| **人工审核** | AI 生成的内容需要人工检查是否符合业务需求 |
| **补充边界情况** | 让 AI 考虑错误处理、权限控制、分页等边界情况 |
::: tip 💡 追问技巧
- "请补充每个接口的错误响应示例"
- "请考虑分页、排序、过滤参数"
- "请添加接口的权限控制说明"
- "请检查是否符合 RESTful 最佳实践"
:::
---
## 名词速查表
| 名词 | 英文 | 解释 |