diff --git a/docs/.vitepress/theme/components/appendix/api-design/ApiRequestDemo.vue b/docs/.vitepress/theme/components/appendix/api-design/ApiRequestDemo.vue index 96d0fdf..ffe2ea6 100644 --- a/docs/.vitepress/theme/components/appendix/api-design/ApiRequestDemo.vue +++ b/docs/.vitepress/theme/components/appendix/api-design/ApiRequestDemo.vue @@ -14,7 +14,9 @@
> - {{ typing }} + {{ typing }}
@@ -23,18 +25,30 @@ v-for="op in ops" :key="op.id" :disabled="running || !op.ok()" - :class="['ar-btn', { 'ar-btn--on': active === op.id, 'ar-btn--dim': !op.ok() }]" + :class="[ + 'ar-btn', + { 'ar-btn--on': active === op.id, 'ar-btn--dim': !op.ok() } + ]" @click="run(op)" > {{ op.cmd }} - +
-
+
💻 客户端 @@ -43,7 +57,9 @@
- {{ requestData.method }} + {{ + requestData.method + }} {{ requestData.url }}
@@ -54,12 +70,18 @@
-
+
HTTP Request
-
+
🖥️ 服务器 @@ -74,12 +96,18 @@
-
+
HTTP Response
-
+
📦 响应 @@ -109,7 +137,9 @@ import { ref, nextTick } from 'vue' const termEl = ref(null) -const lines = ref([{ kind: 'dim', text: '// 点击下方按钮,模拟不同的 API 请求' }]) +const lines = ref([ + { kind: 'dim', text: '// 点击下方按钮,模拟不同的 API 请求' } +]) const typing = ref('') const running = ref(false) const active = ref(null) @@ -120,7 +150,7 @@ const requestData = ref(null) const serverStatus = ref(null) const responseData = ref(null) -const sleep = ms => new Promise(r => setTimeout(r, ms)) +const sleep = (ms) => new Promise((r) => setTimeout(r, ms)) const ops = [ { @@ -132,7 +162,7 @@ const ops = [ { kind: 'grn', text: 'HTTP/1.1 200 OK' }, { kind: 'dim', text: 'Content-Type: application/json' }, { kind: 'dim', text: '' }, - { kind: 'grn', text: '{ "code": 0, "data": { "items": [...] } }' }, + { kind: 'grn', text: '{ "code": 0, "data": { "items": [...] } }' } ], hint: 'GET 请求成功!状态码 200 表示请求正常。服务器返回了用户列表数据。', do: async () => { @@ -163,7 +193,10 @@ const ops = [ { kind: 'grn', text: 'HTTP/1.1 201 Created' }, { kind: 'dim', text: 'Location: /api/users/3' }, { kind: 'dim', text: '' }, - { kind: 'grn', text: '{ "code": 0, "data": { "id": 3, "name": "王五" } }' }, + { + kind: 'grn', + text: '{ "code": 0, "data": { "id": 3, "name": "王五" } }' + } ], hint: 'POST 创建成功!状态码 201 表示资源已创建,响应头 Location 指向新资源地址。', do: async () => { @@ -199,7 +232,7 @@ const ops = [ { kind: 'dim', text: '// 获取不存在的用户' }, { kind: 'red', text: 'HTTP/1.1 404 Not Found' }, { kind: 'dim', text: '' }, - { kind: 'red', text: '{ "code": 10002, "message": "用户不存在" }' }, + { kind: 'red', text: '{ "code": 10002, "message": "用户不存在" }' } ], hint: '404 错误!请求的资源不存在。客户端应该检查请求的 ID 是否正确。', do: async () => { @@ -230,7 +263,7 @@ const ops = [ { kind: 'red', text: 'HTTP/1.1 401 Unauthorized' }, { kind: 'dim', text: 'WWW-Authenticate: Bearer' }, { kind: 'dim', text: '' }, - { kind: 'red', text: '{ "code": 10018, "message": "请先登录" }' }, + { kind: 'red', text: '{ "code": 10018, "message": "请先登录" }' } ], hint: '401 错误!需要身份认证。客户端应该引导用户登录后再重试。', do: async () => { @@ -255,7 +288,7 @@ const ops = [ body: '{\n "code": 10018,\n "message": "请先登录"\n}' } } - }, + } ] async function run(op) { @@ -291,7 +324,9 @@ async function run(op) { await sleep(120) hint.value = op.hint running.value = false - setTimeout(() => { pulseArea.value = null }, 1500) + setTimeout(() => { + pulseArea.value = null + }, 1500) } function scroll() { @@ -342,11 +377,19 @@ function reset() { } @media (max-width: 768px) { - .ar-layout { flex-direction: column; } - .ar-right { width: 100%; border-left: none; border-top: 1px solid var(--vp-c-divider); } + .ar-layout { + flex-direction: column; + } + .ar-right { + width: 100%; + border-left: none; + border-top: 1px solid var(--vp-c-divider); + } } -.ar-terminal { background: #141420; } +.ar-terminal { + background: #141420; +} .term-bar { display: flex; align-items: center; @@ -354,11 +397,26 @@ function reset() { padding: 7px 12px; background: #1e1e2e; } -.dot { width: 11px; height: 11px; border-radius: 50%; } -.dot.r { background: #ff5f57; } -.dot.y { background: #febc2e; } -.dot.g { background: #28c840; } -.term-title { margin-left: 8px; font-size: 0.72rem; color: #666; font-family: monospace; } +.dot { + width: 11px; + height: 11px; + border-radius: 50%; +} +.dot.r { + background: #ff5f57; +} +.dot.y { + background: #febc2e; +} +.dot.g { + background: #28c840; +} +.term-title { + margin-left: 8px; + font-size: 0.72rem; + color: #666; + font-family: monospace; +} .term-body { min-height: 120px; @@ -371,15 +429,41 @@ function reset() { line-height: 1.65; color: #cdd6f4; } -.t-line { display: flex; min-width: min-content; } -.t-ps { color: #89b4fa; flex-shrink: 0; } -.t-cmd { color: #cdd6f4; } -.t-dim { color: #585b70; } -.t-grn { color: #a6e3a1; } -.t-red { color: #f38ba8; } -.t-typing { color: #cdd6f4; } -.t-cur { animation: blink 1s step-end infinite; } -@keyframes blink { 0%, 100% { opacity: 1; } 50% { opacity: 0; } } +.t-line { + display: flex; + min-width: min-content; +} +.t-ps { + color: #89b4fa; + flex-shrink: 0; +} +.t-cmd { + color: #cdd6f4; +} +.t-dim { + color: #585b70; +} +.t-grn { + color: #a6e3a1; +} +.t-red { + color: #f38ba8; +} +.t-typing { + color: #cdd6f4; +} +.t-cur { + animation: blink 1s step-end infinite; +} +@keyframes blink { + 0%, + 100% { + opacity: 1; + } + 50% { + opacity: 0; + } +} .ar-btns { display: flex; @@ -397,18 +481,38 @@ function reset() { cursor: pointer; transition: border-color 0.2s; } -.ar-btn code { font-size: 0.7rem; color: #7f849c; font-family: monospace; white-space: nowrap; } -.ar-btn:hover:not(:disabled) { border-color: var(--vp-c-brand); } -.ar-btn--on { border-color: var(--vp-c-brand) !important; } -.ar-btn--on code { color: var(--vp-c-brand); } -.ar-btn--dim { opacity: 0.3; cursor: not-allowed; } +.ar-btn code { + font-size: 0.7rem; + color: #7f849c; + font-family: monospace; + white-space: nowrap; +} +.ar-btn:hover:not(:disabled) { + border-color: var(--vp-c-brand); +} +.ar-btn--on { + border-color: var(--vp-c-brand) !important; +} +.ar-btn--on code { + color: var(--vp-c-brand); +} +.ar-btn--dim { + opacity: 0.3; + cursor: not-allowed; +} .ar-btn--reset { background: transparent; border-color: #313244; margin-left: auto; } -.ar-btn--reset code { display: none; } -.ar-btn--reset::after { content: '重置'; font-size: 0.7rem; color: #585b70; } +.ar-btn--reset code { + display: none; +} +.ar-btn--reset::after { + content: '重置'; + font-size: 0.7rem; + color: #585b70; +} .ar-flow { display: flex; @@ -426,15 +530,23 @@ function reset() { display: flex; flex-direction: column; min-height: 60px; - transition: border-color 0.25s, box-shadow 0.25s; + transition: + border-color 0.25s, + box-shadow 0.25s; } .flow-col.flow-highlight { border-color: var(--vp-c-brand); box-shadow: 0 0 0 3px color-mix(in srgb, var(--vp-c-brand) 14%, transparent); } -.flow-client { border-left: 4px solid #89b4fa; } -.flow-server { border-left: 4px solid #f9e2af; } -.flow-response { border-left: 4px solid #a6e3a1; } +.flow-client { + border-left: 4px solid #89b4fa; +} +.flow-server { + border-left: 4px solid #f9e2af; +} +.flow-response { + border-left: 4px solid #a6e3a1; +} .flow-header { padding: 6px 8px; @@ -444,9 +556,19 @@ function reset() { align-items: center; gap: 6px; } -.flow-icon { font-size: 0.9rem; } -.flow-title { font-weight: 700; font-size: 0.8rem; color: var(--vp-c-text-1); } -.flow-desc { font-size: 0.7rem; color: var(--vp-c-text-3); margin-left: auto; } +.flow-icon { + font-size: 0.9rem; +} +.flow-title { + font-weight: 700; + font-size: 0.8rem; + color: var(--vp-c-text-1); +} +.flow-desc { + font-size: 0.7rem; + color: var(--vp-c-text-3); + margin-left: auto; +} .flow-body { padding: 8px 10px; @@ -459,18 +581,36 @@ function reset() { font-style: italic; } -.req-preview, .res-preview { font-size: 0.72rem; } -.req-line { display: flex; align-items: center; gap: 6px; margin-bottom: 6px; } +.req-preview, +.res-preview { + font-size: 0.72rem; +} +.req-line { + display: flex; + align-items: center; + gap: 6px; + margin-bottom: 6px; +} .req-method { padding: 2px 6px; border-radius: 3px; font-weight: 700; font-size: 0.68rem; } -.req-method.GET { background: #22c55e22; color: #22c55e; } -.req-method.POST { background: #3b82f622; color: #3b82f6; } -.req-url { font-family: monospace; color: var(--vp-c-text-1); } -.req-body pre, .res-body pre { +.req-method.GET { + background: #22c55e22; + color: #22c55e; +} +.req-method.POST { + background: #3b82f622; + color: #3b82f6; +} +.req-url { + font-family: monospace; + color: var(--vp-c-text-1); +} +.req-body pre, +.res-body pre { margin: 0; padding: 6px; background: var(--vp-c-bg-soft); @@ -486,8 +626,12 @@ function reset() { gap: 8px; font-size: 0.8rem; } -.status-icon { font-size: 1rem; } -.status-text { color: var(--vp-c-text-2); } +.status-icon { + font-size: 1rem; +} +.status-text { + color: var(--vp-c-text-2); +} .res-status { display: inline-block; @@ -497,8 +641,14 @@ function reset() { font-size: 0.7rem; margin-bottom: 6px; } -.res-status.success { background: #22c55e22; color: #22c55e; } -.res-status.error { background: #ef444422; color: #ef4444; } +.res-status.success { + background: #22c55e22; + color: #22c55e; +} +.res-status.error { + background: #ef444422; + color: #ef4444; +} .flow-arrow { display: flex; @@ -509,7 +659,9 @@ function reset() { opacity: 0.3; transition: opacity 0.3s; } -.flow-arrow.arrow-lit { opacity: 1; } +.flow-arrow.arrow-lit { + opacity: 1; +} .arrow-label { font-size: 0.65rem; font-family: monospace; diff --git a/docs/.vitepress/theme/components/appendix/api-design/ApiVersioningDemo.vue b/docs/.vitepress/theme/components/appendix/api-design/ApiVersioningDemo.vue index fad64fc..4e22389 100644 --- a/docs/.vitepress/theme/components/appendix/api-design/ApiVersioningDemo.vue +++ b/docs/.vitepress/theme/components/appendix/api-design/ApiVersioningDemo.vue @@ -22,12 +22,17 @@ v-for="op in ops" :key="op.id" :disabled="running || !op.ok()" - :class="['av-btn', { 'av-btn--on': active === op.id, 'av-btn--dim': !op.ok() }]" + :class="[ + 'av-btn', + { 'av-btn--on': active === op.id, 'av-btn--dim': !op.ok() } + ]" @click="run(op)" > {{ op.cmd }} - +
@@ -90,7 +95,7 @@ const active = ref(null) const activeVersion = ref('') const hint = ref('点击按钮,了解 API 版本控制的策略和最佳实践。') -const sleep = ms => new Promise(r => setTimeout(r, ms)) +const sleep = (ms) => new Promise((r) => setTimeout(r, ms)) const ops = [ { @@ -108,10 +113,12 @@ const ops = [ { kind: 'dim', text: '' }, { kind: 'grn', text: '✅ 正确做法:' }, { kind: 'grn', text: ' /v1/orders - 旧接口,继续服务旧 App' }, - { kind: 'grn', text: ' /v2/orders - 新接口,新功能在这里' }, + { kind: 'grn', text: ' /v2/orders - 新接口,新功能在这里' } ], hint: '版本控制让新旧客户端都能正常工作。旧 App 用户可以慢慢升级,不会突然崩溃。', - do: () => { activeVersion.value = '' } + do: () => { + activeVersion.value = '' + } }, { id: 'url', @@ -125,10 +132,12 @@ const ops = [ { kind: 'grn', text: 'GET /v3/users' }, { kind: 'dim', text: '' }, { kind: 'dim', text: '优点:直观、易缓存、浏览器友好' }, - { kind: 'dim', text: '缺点:URL 变长' }, + { kind: 'dim', text: '缺点:URL 变长' } ], hint: 'URL 路径版本是最常用的方式。GitHub、Twitter、Stripe 都用这种方式。', - do: () => { activeVersion.value = 'v1' } + do: () => { + activeVersion.value = 'v1' + } }, { id: 'header', @@ -145,10 +154,12 @@ const ops = [ { kind: 'grn', text: 'X-API-Version: 2' }, { kind: 'dim', text: '' }, { kind: 'dim', text: '优点:URL 干净' }, - { kind: 'dim', text: '缺点:不便调试、缓存复杂' }, + { kind: 'dim', text: '缺点:不便调试、缓存复杂' } ], hint: 'Header 版本让 URL 更干净,但调试时需要额外设置 Header,不如 URL 版本直观。', - do: () => { activeVersion.value = 'v2' } + do: () => { + activeVersion.value = 'v2' + } }, { id: 'query', @@ -161,10 +172,12 @@ const ops = [ { kind: 'grn', text: 'GET /users?version=2' }, { kind: 'dim', text: '' }, { kind: 'dim', text: '优点:简单、向后兼容' }, - { kind: 'dim', text: '缺点:容易被忽略、不是 RESTful 标准' }, + { kind: 'dim', text: '缺点:容易被忽略、不是 RESTful 标准' } ], hint: '查询参数版本简单但不够"正规"。适合内部 API 或快速迭代的项目。', - do: () => { activeVersion.value = '' } + do: () => { + activeVersion.value = '' + } }, { id: 'best', @@ -177,11 +190,13 @@ const ops = [ { kind: 'grn', text: '2. 新功能放新版本,旧版本保持稳定' }, { kind: 'grn', text: '3. 设置废弃时间线(如 v1 将在 2025-06 废弃)' }, { kind: 'grn', text: '4. 响应头标注当前版本和废弃信息' }, - { kind: 'grn', text: '5. 文档明确标注每个版本的变更' }, + { kind: 'grn', text: '5. 文档明确标注每个版本的变更' } ], hint: '版本控制不是"以后再说"的事,从第一天就应该规划好。废弃旧版本要给用户足够的迁移时间。', - do: () => { activeVersion.value = 'v2' } - }, + do: () => { + activeVersion.value = 'v2' + } + } ] async function run(op) { @@ -239,7 +254,9 @@ function reset() { font-size: 0.85rem; } -.av-terminal { background: #141420; } +.av-terminal { + background: #141420; +} .term-bar { display: flex; align-items: center; @@ -247,11 +264,26 @@ function reset() { padding: 7px 12px; background: #1e1e2e; } -.dot { width: 11px; height: 11px; border-radius: 50%; } -.dot.r { background: #ff5f57; } -.dot.y { background: #febc2e; } -.dot.g { background: #28c840; } -.term-title { margin-left: 8px; font-size: 0.72rem; color: #666; font-family: monospace; } +.dot { + width: 11px; + height: 11px; + border-radius: 50%; +} +.dot.r { + background: #ff5f57; +} +.dot.y { + background: #febc2e; +} +.dot.g { + background: #28c840; +} +.term-title { + margin-left: 8px; + font-size: 0.72rem; + color: #666; + font-family: monospace; +} .term-body { min-height: 100px; @@ -264,16 +296,44 @@ function reset() { line-height: 1.6; color: #cdd6f4; } -.t-line { display: flex; min-width: min-content; } -.t-ps { color: #a6e3a1; flex-shrink: 0; } -.t-cmd { color: #cdd6f4; } -.t-dim { color: #585b70; } -.t-grn { color: #a6e3a1; } -.t-red { color: #f38ba8; } -.t-yel { color: #f9e2af; } -.t-typing { color: #cdd6f4; } -.t-cur { animation: blink 1s step-end infinite; } -@keyframes blink { 0%, 100% { opacity: 1; } 50% { opacity: 0; } } +.t-line { + display: flex; + min-width: min-content; +} +.t-ps { + color: #a6e3a1; + flex-shrink: 0; +} +.t-cmd { + color: #cdd6f4; +} +.t-dim { + color: #585b70; +} +.t-grn { + color: #a6e3a1; +} +.t-red { + color: #f38ba8; +} +.t-yel { + color: #f9e2af; +} +.t-typing { + color: #cdd6f4; +} +.t-cur { + animation: blink 1s step-end infinite; +} +@keyframes blink { + 0%, + 100% { + opacity: 1; + } + 50% { + opacity: 0; + } +} .av-btns { display: flex; @@ -291,18 +351,38 @@ function reset() { cursor: pointer; transition: border-color 0.2s; } -.av-btn code { font-size: 0.68rem; color: #7f849c; font-family: monospace; white-space: nowrap; } -.av-btn:hover:not(:disabled) { border-color: var(--vp-c-brand); } -.av-btn--on { border-color: var(--vp-c-brand) !important; } -.av-btn--on code { color: var(--vp-c-brand); } -.av-btn--dim { opacity: 0.3; cursor: not-allowed; } +.av-btn code { + font-size: 0.68rem; + color: #7f849c; + font-family: monospace; + white-space: nowrap; +} +.av-btn:hover:not(:disabled) { + border-color: var(--vp-c-brand); +} +.av-btn--on { + border-color: var(--vp-c-brand) !important; +} +.av-btn--on code { + color: var(--vp-c-brand); +} +.av-btn--dim { + opacity: 0.3; + cursor: not-allowed; +} .av-btn--reset { background: transparent; border-color: #313244; margin-left: auto; } -.av-btn--reset code { display: none; } -.av-btn--reset::after { content: '重置'; font-size: 0.7rem; color: #585b70; } +.av-btn--reset code { + display: none; +} +.av-btn--reset::after { + content: '重置'; + font-size: 0.7rem; + color: #585b70; +} .av-versions { display: flex; diff --git a/docs/.vitepress/theme/components/appendix/api-design/DataFieldDesignDemo.vue b/docs/.vitepress/theme/components/appendix/api-design/DataFieldDesignDemo.vue index 1016674..2b6015e 100644 --- a/docs/.vitepress/theme/components/appendix/api-design/DataFieldDesignDemo.vue +++ b/docs/.vitepress/theme/components/appendix/api-design/DataFieldDesignDemo.vue @@ -22,17 +22,20 @@
单对象
-
{
+            
+{
   "code": 0,
   "data": {
     "id": 123,
     "name": "张三"
   }
-}
+}
列表
-
{
+            
+{
   "code": 0,
   "data": {
     "items": [...],
@@ -41,34 +44,27 @@
       "total": 100
     }
   }
-}
+}
-
列表数据包裹在 items 数组中,分页信息放在 pagination 对象
+
+ 列表数据包裹在 items 数组中,分页信息放在 pagination 对象 +

字段命名规范

-
+
{{ rule.icon }} {{ rule.name }}
{{ rule.good }} - vs - {{ rule.bad }} + vs + {{ rule.bad }}
{{ rule.desc }}
@@ -78,11 +74,13 @@

时间格式设计

-
{
+          
+{
   "created_at": "2024-01-15T09:30:00.000Z",
   "updated_at": "2024-01-15T10:00:00.000Z",
   "expired_at": "2025-01-15T00:00:00.000Z"
-}
+}
@@ -99,7 +97,9 @@
命名 - xxx_at 表示时间点,xxx_duration 表示时长 + xxx_at 表示时间点,xxx_duration 表示时长
@@ -109,18 +109,22 @@
✅ 推荐
-
{
+            
+{
   "name": "张三",
   "nickname": null,
   "avatar": null
-}
+}
字段存在但无值时返回 null
❌ 不推荐
-
{
+            
+{
   "name": "张三"
-}
+}
省略字段,前端需判断是否存在
@@ -152,7 +156,9 @@
💡 - 参考 ISO 8601 时间标准,字段命名保持 snake_case 风格 + 参考 ISO 8601 时间标准,字段命名保持 snake_case 风格
@@ -172,11 +178,41 @@ const tabs = [ ] 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: '明确是数值类型' } + { + 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 = [ @@ -220,7 +256,7 @@ const relations = [ ] const currentRelation = computed(() => { - return relations.find(r => r.id === rId.value) || relations[0] + return relations.find((r) => r.id === rId.value) || relations[0] }) diff --git a/docs/.vitepress/theme/components/appendix/api-design/ErrorHandlingDemo.vue b/docs/.vitepress/theme/components/appendix/api-design/ErrorHandlingDemo.vue index 4e6ba9b..ecb15c2 100644 --- a/docs/.vitepress/theme/components/appendix/api-design/ErrorHandlingDemo.vue +++ b/docs/.vitepress/theme/components/appendix/api-design/ErrorHandlingDemo.vue @@ -22,18 +22,25 @@ v-for="op in ops" :key="op.id" :disabled="running || !op.ok()" - :class="['eh-btn', { 'eh-btn--on': active === op.id, 'eh-btn--dim': !op.ok() }]" + :class="[ + 'eh-btn', + { 'eh-btn--on': active === op.id, 'eh-btn--dim': !op.ok() } + ]" @click="run(op)" > {{ op.cmd }} - +
响应结构 - {{ responseStatus }} + {{ + responseStatus + }}
{{ responseData }}
@@ -57,7 +64,7 @@ const hint = ref('点击按钮,对比"好的"和"差的"错误响应设计。' const responseData = ref('') const responseStatus = ref('') -const sleep = ms => new Promise(r => setTimeout(r, ms)) +const sleep = (ms) => new Promise((r) => setTimeout(r, ms)) const ops = [ { @@ -68,7 +75,7 @@ const ops = [ { kind: 'dim', text: '// HTTP 200 但业务失败' }, { kind: 'yel', text: 'HTTP/1.1 200 OK' }, { kind: 'dim', text: '' }, - { kind: 'yel', text: '{ "error": "出错了" }' }, + { kind: 'yel', text: '{ "error": "出错了" }' } ], hint: '问题:HTTP 状态码说"成功",但业务说"出错"。缓存层会缓存这个"成功"响应,监控系统也发现不了问题。', do: () => { @@ -86,7 +93,7 @@ const ops = [ { kind: 'dim', text: '// 错误信息没有帮助' }, { kind: 'red', text: 'HTTP/1.1 400 Bad Request' }, { kind: 'dim', text: '' }, - { kind: 'red', text: '{ "message": "参数错误" }' }, + { kind: 'red', text: '{ "message": "参数错误" }' } ], hint: '问题:客户端不知道哪个参数错了、为什么错。用户只能看到"参数错误",无法修正。', do: () => { @@ -104,9 +111,12 @@ const ops = [ { kind: 'dim', text: '// 500 错误暴露堆栈' }, { kind: 'red', text: 'HTTP/1.1 500 Internal Server Error' }, { kind: 'dim', text: '' }, - { kind: 'red', text: '{ "error": "TypeError: Cannot read property..." }' }, + { + kind: 'red', + text: '{ "error": "TypeError: Cannot read property..." }' + }, { kind: 'red', text: '{ "stack": "at UserService.login..." }' }, - { kind: 'red', text: '{ "sql": "SELECT * FROM users WHERE..." }' }, + { kind: 'red', text: '{ "sql": "SELECT * FROM users WHERE..." }' } ], hint: '危险!暴露了代码结构、数据库查询。攻击者可以利用这些信息进行攻击。', do: () => { @@ -126,7 +136,7 @@ const ops = [ { kind: 'dim', text: '// HTTP 状态码准确表达错误类型' }, { kind: 'grn', text: 'HTTP/1.1 404 Not Found' }, { kind: 'dim', text: '' }, - { kind: 'grn', text: '{ "code": 10002, "message": "用户不存在" }' }, + { kind: 'grn', text: '{ "code": 10002, "message": "用户不存在" }' } ], hint: '正确!404 表示资源不存在,客户端一看就知道问题所在。', do: () => { @@ -147,7 +157,7 @@ const ops = [ { kind: 'grn', text: 'HTTP/1.1 422 Unprocessable Entity' }, { kind: 'dim', text: '' }, { kind: 'grn', text: '{ "code": 20003, "message": "密码强度不足" }' }, - { kind: 'grn', text: '{ "errors": [{ "field": "password", ... }] }' }, + { kind: 'grn', text: '{ "errors": [{ "field": "password", ... }] }' } ], hint: '正确!提供了错误码、字段级别的错误详情,前端可以精确提示用户。', do: () => { @@ -175,7 +185,7 @@ const ops = [ { kind: 'grn', text: 'HTTP/1.1 500 Internal Server Error' }, { kind: 'dim', text: '' }, { kind: 'grn', text: '{ "code": 10000, "message": "服务器错误" }' }, - { kind: 'grn', text: '{ "error_id": "err-a1b2c3d4" }' }, + { kind: 'grn', text: '{ "error_id": "err-a1b2c3d4" }' } ], hint: '正确!只返回错误 ID,详细日志记录在服务器。用户反馈错误 ID,技术人员可以快速定位。', do: () => { @@ -188,7 +198,7 @@ const ops = [ "help_url": "https://docs.example.com/errors/10000" }` } - }, + } ] async function run(op) { @@ -249,7 +259,9 @@ function reset() { font-size: 0.85rem; } -.eh-terminal { background: #141420; } +.eh-terminal { + background: #141420; +} .term-bar { display: flex; align-items: center; @@ -257,11 +269,26 @@ function reset() { padding: 7px 12px; background: #1e1e2e; } -.dot { width: 11px; height: 11px; border-radius: 50%; } -.dot.r { background: #ff5f57; } -.dot.y { background: #febc2e; } -.dot.g { background: #28c840; } -.term-title { margin-left: 8px; font-size: 0.72rem; color: #666; font-family: monospace; } +.dot { + width: 11px; + height: 11px; + border-radius: 50%; +} +.dot.r { + background: #ff5f57; +} +.dot.y { + background: #febc2e; +} +.dot.g { + background: #28c840; +} +.term-title { + margin-left: 8px; + font-size: 0.72rem; + color: #666; + font-family: monospace; +} .term-body { min-height: 100px; @@ -274,16 +301,44 @@ function reset() { line-height: 1.6; color: #cdd6f4; } -.t-line { display: flex; min-width: min-content; } -.t-ps { color: #89b4fa; flex-shrink: 0; } -.t-cmd { color: #cdd6f4; } -.t-dim { color: #585b70; } -.t-grn { color: #a6e3a1; } -.t-red { color: #f38ba8; } -.t-yel { color: #f9e2af; } -.t-typing { color: #cdd6f4; } -.t-cur { animation: blink 1s step-end infinite; } -@keyframes blink { 0%, 100% { opacity: 1; } 50% { opacity: 0; } } +.t-line { + display: flex; + min-width: min-content; +} +.t-ps { + color: #89b4fa; + flex-shrink: 0; +} +.t-cmd { + color: #cdd6f4; +} +.t-dim { + color: #585b70; +} +.t-grn { + color: #a6e3a1; +} +.t-red { + color: #f38ba8; +} +.t-yel { + color: #f9e2af; +} +.t-typing { + color: #cdd6f4; +} +.t-cur { + animation: blink 1s step-end infinite; +} +@keyframes blink { + 0%, + 100% { + opacity: 1; + } + 50% { + opacity: 0; + } +} .eh-btns { display: flex; @@ -301,18 +356,38 @@ function reset() { cursor: pointer; transition: border-color 0.2s; } -.eh-btn code { font-size: 0.68rem; color: #7f849c; font-family: monospace; white-space: nowrap; } -.eh-btn:hover:not(:disabled) { border-color: var(--vp-c-brand); } -.eh-btn--on { border-color: var(--vp-c-brand) !important; } -.eh-btn--on code { color: var(--vp-c-brand); } -.eh-btn--dim { opacity: 0.3; cursor: not-allowed; } +.eh-btn code { + font-size: 0.68rem; + color: #7f849c; + font-family: monospace; + white-space: nowrap; +} +.eh-btn:hover:not(:disabled) { + border-color: var(--vp-c-brand); +} +.eh-btn--on { + border-color: var(--vp-c-brand) !important; +} +.eh-btn--on code { + color: var(--vp-c-brand); +} +.eh-btn--dim { + opacity: 0.3; + cursor: not-allowed; +} .eh-btn--reset { background: transparent; border-color: #313244; margin-left: auto; } -.eh-btn--reset code { display: none; } -.eh-btn--reset::after { content: '重置'; font-size: 0.7rem; color: #585b70; } +.eh-btn--reset code { + display: none; +} +.eh-btn--reset::after { + content: '重置'; + font-size: 0.7rem; + color: #585b70; +} .eh-response { border-top: 1px solid var(--vp-c-divider); @@ -326,7 +401,11 @@ function reset() { border-bottom: 1px solid var(--vp-c-divider); background: var(--vp-c-bg-alt); } -.res-label { font-weight: 700; font-size: 0.8rem; color: var(--vp-c-text-1); } +.res-label { + font-weight: 700; + font-size: 0.8rem; + color: var(--vp-c-text-1); +} .res-status { font-family: monospace; font-size: 0.72rem; @@ -334,11 +413,26 @@ function reset() { padding: 2px 8px; border-radius: 4px; } -.res-status\.200\ \(错误\) { background: #f9e2af22; color: #d97706; } -.res-status\.400 { background: #f59e0b22; color: #d97706; } -.res-status\.404 { background: #3b82f622; color: #3b82f6; } -.res-status\.422 { background: #8b5cf622; color: #8b5cf6; } -.res-status\.500 { background: #ef444422; color: #ef4444; } +.res-status\.200\ \(错误\) { + background: #f9e2af22; + color: #d97706; +} +.res-status\.400 { + background: #f59e0b22; + color: #d97706; +} +.res-status\.404 { + background: #3b82f622; + color: #3b82f6; +} +.res-status\.422 { + background: #8b5cf622; + color: #8b5cf6; +} +.res-status\.500 { + background: #ef444422; + color: #ef4444; +} .res-body { padding: 12px; diff --git a/docs/.vitepress/theme/components/appendix/api-design/ErrorResponseDesignDemo.vue b/docs/.vitepress/theme/components/appendix/api-design/ErrorResponseDesignDemo.vue index e8f6ce2..a7b6560 100644 --- a/docs/.vitepress/theme/components/appendix/api-design/ErrorResponseDesignDemo.vue +++ b/docs/.vitepress/theme/components/appendix/api-design/ErrorResponseDesignDemo.vue @@ -19,7 +19,8 @@

参数校验错误

-
{
+        
+{
   "code": 10001,
   "message": "参数校验失败",
   "data": {
@@ -36,7 +37,8 @@
       }
     ]
   }
-}
+}
field @@ -55,7 +57,8 @@

业务错误

-
{
+        
+{
   "code": 20001,
   "message": "余额不足",
   "data": {
@@ -64,7 +67,8 @@
     "shortfall": 49.00,
     "suggestion": "请充值后重试"
   }
-}
+}
✓ 返回当前状态数据,便于前端展示
✓ 提供 suggestion 给出解决建议
@@ -75,11 +79,7 @@

错误码分层设计

-
+
{{ layer.range }}
{{ layer.name }}
@@ -88,7 +88,9 @@
{{ layer.desc }}
-
错误码从外到内:系统 → 服务 → 业务 → 认证 → 参数
+
+ 错误码从外到内:系统 → 服务 → 业务 → 认证 → 参数 +
@@ -164,7 +166,9 @@
💡 - 错误信息要"机器可读 + 人类友好",便于前端统一处理 + 错误信息要"机器可读 + 人类友好",便于前端统一处理
@@ -184,11 +188,36 @@ const tabs = [ ] 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: '客户端输入问题' } + { + 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 = [ @@ -234,7 +263,7 @@ const examples = [ ] const currentExample = computed(() => { - return examples.find(e => e.id === exId.value) || examples[0] + return examples.find((e) => e.id === exId.value) || examples[0] }) @@ -434,7 +463,7 @@ const currentExample = computed(() => { .http-compare { flex-direction: column; } - + .http-arrow { display: none; } diff --git a/docs/.vitepress/theme/components/appendix/api-design/ResponseStructureDemo.vue b/docs/.vitepress/theme/components/appendix/api-design/ResponseStructureDemo.vue index 6268a5a..bb06bd1 100644 --- a/docs/.vitepress/theme/components/appendix/api-design/ResponseStructureDemo.vue +++ b/docs/.vitepress/theme/components/appendix/api-design/ResponseStructureDemo.vue @@ -29,7 +29,8 @@ { "result": { "user": {...} } } // 接口 C -{ "user": {...} } +{ "user": {...} }
前端需要针对每个接口单独处理,代码冗余,容易出错
@@ -42,7 +43,8 @@ "message": "success", "data": { ... }, "request_id": "req-xxx" -} +}
@@ -139,7 +141,8 @@ "total": 156, "total_pages": 8, "has_next": true -} +}
@@ -147,7 +150,9 @@
💡 - request_id 用于问题追踪,建议使用 UUID v4 或雪花算法生成 + request_id 用于问题追踪,建议使用 UUID v4 或雪花算法生成
diff --git a/docs/.vitepress/theme/components/appendix/api-design/RestfulApiFlow.vue b/docs/.vitepress/theme/components/appendix/api-design/RestfulApiFlow.vue index 07ef39a..72b6bb0 100644 --- a/docs/.vitepress/theme/components/appendix/api-design/RestfulApiFlow.vue +++ b/docs/.vitepress/theme/components/appendix/api-design/RestfulApiFlow.vue @@ -7,11 +7,11 @@ 💻 Client (Browser/App)
- +
- - +
@@ -37,7 +42,12 @@ 错误示例
-
+
{{ item.url }} {{ item.reason }}
@@ -50,7 +60,12 @@ 正确示例
-
+
{{ item.url }} {{ item.reason }}
@@ -66,7 +81,9 @@ import { ref, nextTick } from 'vue' const termEl = ref(null) -const lines = ref([{ kind: 'dim', text: '# 对比 RESTful URL 的正确与错误写法' }]) +const lines = ref([ + { kind: 'dim', text: '# 对比 RESTful URL 的正确与错误写法' } +]) const typing = ref('') const running = ref(false) const active = ref(null) @@ -77,8 +94,16 @@ const badExamples = ref([ { url: 'GET /user', reason: '单数形式', active: false }, { url: 'GET /UserProfiles', reason: '大写字母', active: false }, { url: 'GET /user_profiles', reason: '下划线连接', active: false }, - { url: 'GET /users/123/orders/456/items/789', reason: '层级过深', active: false }, - { url: 'GET /products/category/phone/price/5000', reason: '过滤条件放路径', active: false }, + { + url: 'GET /users/123/orders/456/items/789', + reason: '层级过深', + active: false + }, + { + url: 'GET /products/category/phone/price/5000', + reason: '过滤条件放路径', + active: false + } ]) const goodExamples = ref([ @@ -87,10 +112,14 @@ const goodExamples = ref([ { url: 'GET /user-profiles', reason: '小写 + 连字符', active: false }, { url: 'GET /user-profiles', reason: '连字符连接', active: false }, { url: 'GET /users/123/orders', reason: '最多 3 层', active: false }, - { url: 'GET /products?category=phone&price_max=5000', reason: '过滤用查询参数', active: false }, + { + url: 'GET /products?category=phone&price_max=5000', + reason: '过滤用查询参数', + active: false + } ]) -const sleep = ms => new Promise(r => setTimeout(r, ms)) +const sleep = (ms) => new Promise((r) => setTimeout(r, ms)) const ops = [ { @@ -104,7 +133,7 @@ const ops = [ { kind: 'red', text: '❌ POST /createOrder' }, { kind: 'grn', text: '✅ GET /users' }, { kind: 'grn', text: '✅ GET /users/123' }, - { kind: 'grn', text: '✅ POST /orders' }, + { kind: 'grn', text: '✅ POST /orders' } ], hint: 'URL 是资源的"地址",HTTP 方法已经表达了"操作"。不要在 URL 里重复说"做什么"。', do: () => { @@ -122,7 +151,7 @@ const ops = [ { kind: 'red', text: '❌ GET /order' }, { kind: 'grn', text: '✅ GET /users' }, { kind: 'grn', text: '✅ GET /orders' }, - { kind: 'grn', text: '✅ GET /users/123 (获取单个)' }, + { kind: 'grn', text: '✅ GET /users/123 (获取单个)' } ], hint: '统一用复数,避免 /user 和 /users 混用。获取单个资源时用 /users/123。', do: () => { @@ -139,7 +168,7 @@ const ops = [ { kind: 'red', text: '❌ GET /UserProfiles' }, { kind: 'red', text: '❌ GET /user_profiles' }, { kind: 'grn', text: '✅ GET /user-profiles' }, - { kind: 'grn', text: '✅ GET /order-items' }, + { kind: 'grn', text: '✅ GET /order-items' } ], hint: 'URL 大小写敏感,统一用小写 + 连字符(-)是最安全的做法。', do: () => { @@ -158,7 +187,7 @@ const ops = [ { kind: 'red', text: '❌ /users/123/orders/456/items/789/status' }, { kind: 'grn', text: '✅ /users/123/orders (用户订单)' }, { kind: 'grn', text: '✅ /orders/456/items (订单商品)' }, - { kind: 'grn', text: '✅ /order-items/789 (直接访问)' }, + { kind: 'grn', text: '✅ /order-items/789 (直接访问)' } ], hint: '超过 3 层考虑重构。可以用扁平化路径或查询参数替代深层嵌套。', do: () => { @@ -175,14 +204,14 @@ const ops = [ { kind: 'red', text: '❌ /products/category/phone/price/5000' }, { kind: 'grn', text: '✅ /products?category=phone&price_max=5000' }, { kind: 'grn', text: '✅ /products?status=active&sort=created_desc' }, - { kind: 'grn', text: '✅ /products?category=phone,electronics' }, + { kind: 'grn', text: '✅ /products?category=phone,electronics' } ], hint: '查询参数可以灵活组合,路径则固定不变。过滤、排序、分页都用查询参数。', do: () => { badExamples.value[5].active = true goodExamples.value[5].active = true } - }, + } ] async function run(op) { @@ -192,8 +221,8 @@ async function run(op) { hint.value = '' typing.value = '' - badExamples.value.forEach(e => e.active = false) - goodExamples.value.forEach(e => e.active = false) + badExamples.value.forEach((e) => (e.active = false)) + goodExamples.value.forEach((e) => (e.active = false)) for (const ch of op.cmd) { typing.value += ch @@ -225,8 +254,8 @@ function scroll() { function reset() { lines.value = [{ kind: 'dim', text: '# 对比 RESTful URL 的正确与错误写法' }] - badExamples.value.forEach(e => e.active = false) - goodExamples.value.forEach(e => e.active = false) + badExamples.value.forEach((e) => (e.active = false)) + goodExamples.value.forEach((e) => (e.active = false)) active.value = null hint.value = '点击命令按钮,查看不同场景下的 URL 设计对比。' typing.value = '' @@ -244,7 +273,9 @@ function reset() { font-size: 0.85rem; } -.ru-terminal { background: #141420; } +.ru-terminal { + background: #141420; +} .term-bar { display: flex; align-items: center; @@ -252,11 +283,26 @@ function reset() { padding: 7px 12px; background: #1e1e2e; } -.dot { width: 11px; height: 11px; border-radius: 50%; } -.dot.r { background: #ff5f57; } -.dot.y { background: #febc2e; } -.dot.g { background: #28c840; } -.term-title { margin-left: 8px; font-size: 0.72rem; color: #666; font-family: monospace; } +.dot { + width: 11px; + height: 11px; + border-radius: 50%; +} +.dot.r { + background: #ff5f57; +} +.dot.y { + background: #febc2e; +} +.dot.g { + background: #28c840; +} +.term-title { + margin-left: 8px; + font-size: 0.72rem; + color: #666; + font-family: monospace; +} .term-body { min-height: 100px; @@ -269,15 +315,41 @@ function reset() { line-height: 1.6; color: #cdd6f4; } -.t-line { display: flex; min-width: min-content; } -.t-ps { color: #a6e3a1; flex-shrink: 0; } -.t-cmd { color: #cdd6f4; } -.t-dim { color: #585b70; } -.t-grn { color: #a6e3a1; } -.t-red { color: #f38ba8; } -.t-typing { color: #cdd6f4; } -.t-cur { animation: blink 1s step-end infinite; } -@keyframes blink { 0%, 100% { opacity: 1; } 50% { opacity: 0; } } +.t-line { + display: flex; + min-width: min-content; +} +.t-ps { + color: #a6e3a1; + flex-shrink: 0; +} +.t-cmd { + color: #cdd6f4; +} +.t-dim { + color: #585b70; +} +.t-grn { + color: #a6e3a1; +} +.t-red { + color: #f38ba8; +} +.t-typing { + color: #cdd6f4; +} +.t-cur { + animation: blink 1s step-end infinite; +} +@keyframes blink { + 0%, + 100% { + opacity: 1; + } + 50% { + opacity: 0; + } +} .ru-btns { display: flex; @@ -295,18 +367,38 @@ function reset() { cursor: pointer; transition: border-color 0.2s; } -.ru-btn code { font-size: 0.68rem; color: #7f849c; font-family: monospace; white-space: nowrap; } -.ru-btn:hover:not(:disabled) { border-color: var(--vp-c-brand); } -.ru-btn--on { border-color: var(--vp-c-brand) !important; } -.ru-btn--on code { color: var(--vp-c-brand); } -.ru-btn--dim { opacity: 0.3; cursor: not-allowed; } +.ru-btn code { + font-size: 0.68rem; + color: #7f849c; + font-family: monospace; + white-space: nowrap; +} +.ru-btn:hover:not(:disabled) { + border-color: var(--vp-c-brand); +} +.ru-btn--on { + border-color: var(--vp-c-brand) !important; +} +.ru-btn--on code { + color: var(--vp-c-brand); +} +.ru-btn--dim { + opacity: 0.3; + cursor: not-allowed; +} .ru-btn--reset { background: transparent; border-color: #313244; margin-left: auto; } -.ru-btn--reset code { display: none; } -.ru-btn--reset::after { content: '重置'; font-size: 0.7rem; color: #585b70; } +.ru-btn--reset code { + display: none; +} +.ru-btn--reset::after { + content: '重置'; + font-size: 0.7rem; + color: #585b70; +} .ru-compare { display: grid; @@ -332,8 +424,14 @@ function reset() { gap: 6px; margin-bottom: 10px; } -.compare-icon { font-size: 1rem; } -.compare-title { font-weight: 700; font-size: 0.85rem; color: var(--vp-c-text-1); } +.compare-icon { + font-size: 1rem; +} +.compare-title { + font-weight: 700; + font-size: 0.85rem; + color: var(--vp-c-text-1); +} .compare-body { display: flex; @@ -346,7 +444,9 @@ function reset() { border-radius: 4px; background: var(--vp-c-bg); border: 1px solid transparent; - transition: border-color 0.2s, background 0.2s; + transition: + border-color 0.2s, + background 0.2s; } .url-row.highlight { border-color: var(--vp-c-brand); diff --git a/docs/.vitepress/theme/components/appendix/api-design/StatusCodeDemo.vue b/docs/.vitepress/theme/components/appendix/api-design/StatusCodeDemo.vue index 1bb067d..55f16cd 100644 --- a/docs/.vitepress/theme/components/appendix/api-design/StatusCodeDemo.vue +++ b/docs/.vitepress/theme/components/appendix/api-design/StatusCodeDemo.vue @@ -22,12 +22,17 @@ v-for="op in ops" :key="op.id" :disabled="running || !op.ok()" - :class="['sc-btn', { 'sc-btn--on': active === op.id, 'sc-btn--dim': !op.ok() }]" + :class="[ + 'sc-btn', + { 'sc-btn--on': active === op.id, 'sc-btn--dim': !op.ok() } + ]" @click="run(op)" > {{ op.cmd }} - +
@@ -37,7 +42,12 @@ 2xx 成功
-
+
{{ c.code }} {{ c.name }} {{ c.desc }} @@ -51,7 +61,12 @@ 4xx 客户端错误
-
+
{{ c.code }} {{ c.name }} {{ c.desc }} @@ -65,7 +80,12 @@ 5xx 服务端错误
-
+
{{ c.code }} {{ c.name }} {{ c.desc }} @@ -92,7 +112,7 @@ const hint = ref('点击命令按钮,了解常见的 HTTP 状态码。') const successCodes = ref([ { code: 200, name: 'OK', desc: '请求成功' }, { code: 201, name: 'Created', desc: '创建成功' }, - { code: 204, name: 'No Content', desc: '成功但无返回内容' }, + { code: 204, name: 'No Content', desc: '成功但无返回内容' } ]) const clientCodes = ref([ @@ -101,16 +121,16 @@ const clientCodes = ref([ { code: 403, name: 'Forbidden', desc: '无权限' }, { code: 404, name: 'Not Found', desc: '资源不存在' }, { code: 422, name: 'Unprocessable', desc: '语义错误' }, - { code: 429, name: 'Too Many', desc: '请求过多' }, + { code: 429, name: 'Too Many', desc: '请求过多' } ]) const serverCodes = ref([ { code: 500, name: 'Server Error', desc: '服务器内部错误' }, { code: 502, name: 'Bad Gateway', desc: '网关错误' }, - { code: 503, name: 'Unavailable', desc: '服务不可用' }, + { code: 503, name: 'Unavailable', desc: '服务不可用' } ]) -const sleep = ms => new Promise(r => setTimeout(r, ms)) +const sleep = (ms) => new Promise((r) => setTimeout(r, ms)) const ops = [ { @@ -122,10 +142,12 @@ const ops = [ { kind: 'grn', text: 'HTTP/1.1 200 OK' }, { kind: 'dim', text: 'Content-Type: application/json' }, { kind: 'dim', text: '' }, - { kind: 'grn', text: '{ "code": 0, "data": { ... } }' }, + { kind: 'grn', text: '{ "code": 0, "data": { ... } }' } ], hint: '200 表示请求成功处理。GET 查询、PUT/PATCH 更新成功时常用。', - do: () => { activeCode.value = 200 } + do: () => { + activeCode.value = 200 + } }, { id: '201', @@ -136,10 +158,12 @@ const ops = [ { kind: 'grn', text: 'HTTP/1.1 201 Created' }, { kind: 'dim', text: 'Location: /api/users/123' }, { kind: 'dim', text: '' }, - { kind: 'grn', text: '{ "code": 0, "data": { "id": 123 } }' }, + { kind: 'grn', text: '{ "code": 0, "data": { "id": 123 } }' } ], hint: '201 表示资源创建成功。响应头 Location 指向新资源的地址。', - do: () => { activeCode.value = 201 } + do: () => { + activeCode.value = 201 + } }, { id: '400', @@ -149,10 +173,12 @@ const ops = [ { kind: 'dim', text: '// 客户端请求有问题' }, { kind: 'red', text: 'HTTP/1.1 400 Bad Request' }, { kind: 'dim', text: '' }, - { kind: 'red', text: '{ "code": 10001, "message": "参数格式错误" }' }, + { kind: 'red', text: '{ "code": 10001, "message": "参数格式错误" }' } ], hint: '400 表示请求语法错误。比如 JSON 格式不对、缺少必填参数。', - do: () => { activeCode.value = 400 } + do: () => { + activeCode.value = 400 + } }, { id: '401', @@ -163,10 +189,12 @@ const ops = [ { kind: 'red', text: 'HTTP/1.1 401 Unauthorized' }, { kind: 'dim', text: 'WWW-Authenticate: Bearer' }, { kind: 'dim', text: '' }, - { kind: 'red', text: '{ "code": 10018, "message": "请先登录" }' }, + { kind: 'red', text: '{ "code": 10018, "message": "请先登录" }' } ], hint: '401 表示未认证。Token 过期、未登录时返回,客户端应引导用户登录。', - do: () => { activeCode.value = 401 } + do: () => { + activeCode.value = 401 + } }, { id: '403', @@ -176,10 +204,12 @@ const ops = [ { kind: 'dim', text: '// 已登录但无权限' }, { kind: 'red', text: 'HTTP/1.1 403 Forbidden' }, { kind: 'dim', text: '' }, - { kind: 'red', text: '{ "code": 10021, "message": "需要管理员权限" }' }, + { kind: 'red', text: '{ "code": 10021, "message": "需要管理员权限" }' } ], hint: '403 表示已认证但无权限。普通用户访问管理员接口时返回。', - do: () => { activeCode.value = 403 } + do: () => { + activeCode.value = 403 + } }, { id: '404', @@ -189,10 +219,12 @@ const ops = [ { kind: 'dim', text: '// 资源不存在' }, { kind: 'red', text: 'HTTP/1.1 404 Not Found' }, { kind: 'dim', text: '' }, - { kind: 'red', text: '{ "code": 10002, "message": "用户不存在" }' }, + { kind: 'red', text: '{ "code": 10002, "message": "用户不存在" }' } ], hint: '404 表示请求的资源不存在。URL 错误或资源已被删除。', - do: () => { activeCode.value = 404 } + do: () => { + activeCode.value = 404 + } }, { id: '500', @@ -202,11 +234,16 @@ const ops = [ { kind: 'dim', text: '// 服务器内部错误' }, { kind: 'red', text: 'HTTP/1.1 500 Internal Server Error' }, { kind: 'dim', text: '' }, - { kind: 'red', text: '{ "code": 10000, "message": "服务器错误,请联系管理员" }' }, + { + kind: 'red', + text: '{ "code": 10000, "message": "服务器错误,请联系管理员" }' + } ], hint: '500 表示服务器内部错误。代码 bug、数据库连接失败等,不要暴露堆栈信息!', - do: () => { activeCode.value = 500 } - }, + do: () => { + activeCode.value = 500 + } + } ] async function run(op) { @@ -265,7 +302,9 @@ function reset() { font-size: 0.85rem; } -.sc-terminal { background: #141420; } +.sc-terminal { + background: #141420; +} .term-bar { display: flex; align-items: center; @@ -273,11 +312,26 @@ function reset() { padding: 7px 12px; background: #1e1e2e; } -.dot { width: 11px; height: 11px; border-radius: 50%; } -.dot.r { background: #ff5f57; } -.dot.y { background: #febc2e; } -.dot.g { background: #28c840; } -.term-title { margin-left: 8px; font-size: 0.72rem; color: #666; font-family: monospace; } +.dot { + width: 11px; + height: 11px; + border-radius: 50%; +} +.dot.r { + background: #ff5f57; +} +.dot.y { + background: #febc2e; +} +.dot.g { + background: #28c840; +} +.term-title { + margin-left: 8px; + font-size: 0.72rem; + color: #666; + font-family: monospace; +} .term-body { min-height: 90px; @@ -290,15 +344,41 @@ function reset() { line-height: 1.6; color: #cdd6f4; } -.t-line { display: flex; min-width: min-content; } -.t-ps { color: #89b4fa; flex-shrink: 0; } -.t-cmd { color: #cdd6f4; } -.t-dim { color: #585b70; } -.t-grn { color: #a6e3a1; } -.t-red { color: #f38ba8; } -.t-typing { color: #cdd6f4; } -.t-cur { animation: blink 1s step-end infinite; } -@keyframes blink { 0%, 100% { opacity: 1; } 50% { opacity: 0; } } +.t-line { + display: flex; + min-width: min-content; +} +.t-ps { + color: #89b4fa; + flex-shrink: 0; +} +.t-cmd { + color: #cdd6f4; +} +.t-dim { + color: #585b70; +} +.t-grn { + color: #a6e3a1; +} +.t-red { + color: #f38ba8; +} +.t-typing { + color: #cdd6f4; +} +.t-cur { + animation: blink 1s step-end infinite; +} +@keyframes blink { + 0%, + 100% { + opacity: 1; + } + 50% { + opacity: 0; + } +} .sc-btns { display: flex; @@ -316,18 +396,38 @@ function reset() { cursor: pointer; transition: border-color 0.2s; } -.sc-btn code { font-size: 0.68rem; color: #7f849c; font-family: monospace; white-space: nowrap; } -.sc-btn:hover:not(:disabled) { border-color: var(--vp-c-brand); } -.sc-btn--on { border-color: var(--vp-c-brand) !important; } -.sc-btn--on code { color: var(--vp-c-brand); } -.sc-btn--dim { opacity: 0.3; cursor: not-allowed; } +.sc-btn code { + font-size: 0.68rem; + color: #7f849c; + font-family: monospace; + white-space: nowrap; +} +.sc-btn:hover:not(:disabled) { + border-color: var(--vp-c-brand); +} +.sc-btn--on { + border-color: var(--vp-c-brand) !important; +} +.sc-btn--on code { + color: var(--vp-c-brand); +} +.sc-btn--dim { + opacity: 0.3; + cursor: not-allowed; +} .sc-btn--reset { background: transparent; border-color: #313244; margin-left: auto; } -.sc-btn--reset code { display: none; } -.sc-btn--reset::after { content: '重置'; font-size: 0.7rem; color: #585b70; } +.sc-btn--reset code { + display: none; +} +.sc-btn--reset::after { + content: '重置'; + font-size: 0.7rem; + color: #585b70; +} .sc-codes { display: grid; @@ -351,12 +451,25 @@ function reset() { font-weight: 700; font-size: 0.8rem; } -.section-header.success { background: color-mix(in srgb, #22c55e 8%, var(--vp-c-bg-alt)); color: #22c55e; } -.section-header.client { background: color-mix(in srgb, #f59e0b 8%, var(--vp-c-bg-alt)); color: #d97706; } -.section-header.server { background: color-mix(in srgb, #ef4444 8%, var(--vp-c-bg-alt)); color: #ef4444; } +.section-header.success { + background: color-mix(in srgb, #22c55e 8%, var(--vp-c-bg-alt)); + color: #22c55e; +} +.section-header.client { + background: color-mix(in srgb, #f59e0b 8%, var(--vp-c-bg-alt)); + color: #d97706; +} +.section-header.server { + background: color-mix(in srgb, #ef4444 8%, var(--vp-c-bg-alt)); + color: #ef4444; +} -.section-icon { font-size: 0.9rem; } -.section-title { font-size: 0.75rem; } +.section-icon { + font-size: 0.9rem; +} +.section-title { + font-size: 0.75rem; +} .section-body { padding: 8px; @@ -386,7 +499,9 @@ function reset() { font-size: 0.75rem; min-width: 28px; } -.code-item.active .code-num { color: var(--vp-c-brand); } +.code-item.active .code-num { + color: var(--vp-c-brand); +} .code-name { font-size: 0.72rem; diff --git a/docs/.vitepress/theme/components/appendix/api-intro/ApiConceptDemo.vue b/docs/.vitepress/theme/components/appendix/api-intro/ApiConceptDemo.vue index 071c0d6..1bab8f2 100644 --- a/docs/.vitepress/theme/components/appendix/api-intro/ApiConceptDemo.vue +++ b/docs/.vitepress/theme/components/appendix/api-intro/ApiConceptDemo.vue @@ -1,87 +1,90 @@ @@ -99,130 +102,140 @@ function sendRequest() { response.value = null setTimeout(() => { - const startTime = Date.now() - if (endpoint.value === '/users') { - // 限制最多返回3条,避免结果太长 const actualLimit = Math.min(limit.value, 3) const users = [] for (let i = 1; i <= actualLimit; i++) { users.push({ id: i, - name: `用户${(page.value - 1) * limit.value + i}`, - age: 20 + i + name: `用户${(page.value - 1) * limit.value + i}` }) } - response.value = { status: 200, statusText: 'OK', - time: Date.now() - startTime, - data: { - users, - total: 100, - page: page.value, - limit: limit.value, - note: limit.value > 3 ? `仅显示前3条,共${limit.value}条` : null - } + data: { users, total: 100, page: page.value } } } else { response.value = { status: 404, statusText: 'Not Found', - time: Date.now() - startTime, data: { error: '找不到这个接口' } } } - loading.value = false - }, 300 + Math.random() * 200) + }, 300) } diff --git a/docs/.vitepress/theme/components/appendix/api-intro/ApiDocumentDemo.vue b/docs/.vitepress/theme/components/appendix/api-intro/ApiDocumentDemo.vue index 4b26948..82c9a7a 100644 --- a/docs/.vitepress/theme/components/appendix/api-intro/ApiDocumentDemo.vue +++ b/docs/.vitepress/theme/components/appendix/api-intro/ApiDocumentDemo.vue @@ -1,269 +1,241 @@ - + diff --git a/docs/.vitepress/theme/components/appendix/api-intro/ApiMethodDemo.vue b/docs/.vitepress/theme/components/appendix/api-intro/ApiMethodDemo.vue index 2306972..98d148c 100644 --- a/docs/.vitepress/theme/components/appendix/api-intro/ApiMethodDemo.vue +++ b/docs/.vitepress/theme/components/appendix/api-intro/ApiMethodDemo.vue @@ -1,422 +1,242 @@ diff --git a/docs/.vitepress/theme/components/appendix/api-intro/ApiPlayground.vue b/docs/.vitepress/theme/components/appendix/api-intro/ApiPlayground.vue index 2d8a750..d19fe43 100644 --- a/docs/.vitepress/theme/components/appendix/api-intro/ApiPlayground.vue +++ b/docs/.vitepress/theme/components/appendix/api-intro/ApiPlayground.vue @@ -1,57 +1,40 @@ + - + diff --git a/docs/.vitepress/theme/components/appendix/computer-fundamentals/DataStructureOverviewDemo.vue b/docs/.vitepress/theme/components/appendix/computer-fundamentals/DataStructureOverviewDemo.vue index 21bcc6b..38b4d13 100644 --- a/docs/.vitepress/theme/components/appendix/computer-fundamentals/DataStructureOverviewDemo.vue +++ b/docs/.vitepress/theme/components/appendix/computer-fundamentals/DataStructureOverviewDemo.vue @@ -152,11 +152,7 @@ const categories = [ icon: '🗂️', desc: '通过关键词快速查找', examples: ['哈希表', '字典', '集合'], - features: [ - '通过键值对存储数据', - '查找速度极快', - '数据之间没有顺序关系' - ], + features: ['通过键值对存储数据', '查找速度极快', '数据之间没有顺序关系'], scenarios: [ { icon: '📖', @@ -189,11 +185,7 @@ const categories = [ icon: '🌳', desc: '层级关系,像家谱', examples: ['二叉树', 'B 树', '堆'], - features: [ - '一对多的层级关系', - '有明确的根节点', - '适合表示分类和层级' - ], + features: ['一对多的层级关系', '有明确的根节点', '适合表示分类和层级'], scenarios: [ { icon: '📁', @@ -226,11 +218,7 @@ const categories = [ icon: '🕸️', desc: '复杂关系网络', examples: ['有向图', '无向图', '网络图'], - features: [ - '多对多的复杂关系', - '节点之间可以任意连接', - '可以表示复杂网络' - ], + features: ['多对多的复杂关系', '节点之间可以任意连接', '可以表示复杂网络'], scenarios: [ { icon: '🗺️', @@ -259,7 +247,9 @@ const categories = [ } ] -const currentCategory = computed(() => categories.find(c => c.id === activeCategory.value)) +const currentCategory = computed(() => + categories.find((c) => c.id === activeCategory.value) +) diff --git a/docs/.vitepress/theme/components/appendix/computer-fundamentals/EncodingStorageTransmissionDemo.vue b/docs/.vitepress/theme/components/appendix/computer-fundamentals/EncodingStorageTransmissionDemo.vue index 3da45b1..87c752b 100644 --- a/docs/.vitepress/theme/components/appendix/computer-fundamentals/EncodingStorageTransmissionDemo.vue +++ b/docs/.vitepress/theme/components/appendix/computer-fundamentals/EncodingStorageTransmissionDemo.vue @@ -35,7 +35,9 @@
编码后
-
{{ currentScenario.encoding.output }}
+
+ {{ currentScenario.encoding.output }} +
@@ -62,11 +64,15 @@
位置: - {{ currentScenario.storage.location }} + {{ + currentScenario.storage.location + }}
大小: - {{ currentScenario.storage.size }} + {{ + currentScenario.storage.size + }}
@@ -83,7 +89,12 @@
数据包
-
+
{{ layer.name }}: {{ layer.value }}
@@ -93,11 +104,15 @@
协议: - {{ currentScenario.transmission.protocol }} + {{ + currentScenario.transmission.protocol + }}
路径: - {{ currentScenario.transmission.path }} + {{ + currentScenario.transmission.path + }}
@@ -107,11 +122,15 @@
- {{ currentScenario.relationships.encodingToStorage }} + {{ + currentScenario.relationships.encodingToStorage + }}
- {{ currentScenario.relationships.storageToTransmission }} + {{ + currentScenario.relationships.storageToTransmission + }}
@@ -121,7 +140,11 @@
协作要点
-
+
{{ point.icon }}
{{ point.title }}
@@ -347,8 +370,14 @@ const currentScenario = computed(() => scenarioData[activeScenario.value]) margin-bottom: 1.5rem; } -.demo-header .title { font-weight: 700; font-size: 1.1rem; } -.demo-header .subtitle { color: var(--vp-c-text-2); font-size: 0.9rem; } +.demo-header .title { + font-weight: 700; + font-size: 1.1rem; +} +.demo-header .subtitle { + color: var(--vp-c-text-2); + font-size: 0.9rem; +} .scenario-selector { margin-bottom: 2rem; @@ -420,8 +449,13 @@ const currentScenario = computed(() => scenarioData[activeScenario.value]) border-bottom: 1px solid var(--vp-c-divider); } -.stage-icon { font-size: 1.3rem; } -.stage-title { font-weight: 600; font-size: 0.95rem; } +.stage-icon { + font-size: 1.3rem; +} +.stage-title { + font-weight: 600; + font-size: 0.95rem; +} .stage-content { display: flex; diff --git a/docs/.vitepress/theme/components/appendix/computer-fundamentals/FilesystemDemo.vue b/docs/.vitepress/theme/components/appendix/computer-fundamentals/FilesystemDemo.vue index d0a68a9..04d27a3 100644 --- a/docs/.vitepress/theme/components/appendix/computer-fundamentals/FilesystemDemo.vue +++ b/docs/.vitepress/theme/components/appendix/computer-fundamentals/FilesystemDemo.vue @@ -1,34 +1,32 @@ @@ -160,7 +154,8 @@ const isBlockActive = (block) => { } } -.logical-view, .physical-view { +.logical-view, +.physical-view { flex: 1; background: var(--vp-c-bg-alt); border-radius: 10px; @@ -278,20 +273,38 @@ const isBlockActive = (block) => { 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.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); + 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); } +.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; @@ -308,7 +321,13 @@ const isBlockActive = (block) => { } @keyframes fadeIn { - from { opacity: 0; transform: translateX(-10px); } - to { opacity: 1; transform: translateX(0); } + from { + opacity: 0; + transform: translateX(-10px); + } + to { + opacity: 1; + transform: translateX(0); + } } diff --git a/docs/.vitepress/theme/components/appendix/computer-fundamentals/FunctionalUnitDemo.vue b/docs/.vitepress/theme/components/appendix/computer-fundamentals/FunctionalUnitDemo.vue index 77f9194..afb91cd 100644 --- a/docs/.vitepress/theme/components/appendix/computer-fundamentals/FunctionalUnitDemo.vue +++ b/docs/.vitepress/theme/components/appendix/computer-fundamentals/FunctionalUnitDemo.vue @@ -20,7 +20,8 @@
- 多路选择器 (MUX):像铁路道岔一样,根据"选择信号"决定让哪一路数据通过。 + 多路选择器 (MUX):像铁路道岔一样,根据"选择信号"决定让哪一路数据通过。
@@ -80,7 +81,8 @@
- 译码器 (Decoder):将二进制输入转换为特定输出线的激活信号(例如 2位输入可以激活 + 译码器 (Decoder):将二进制输入转换为特定输出线的激活信号(例如 2位输入可以激活 4根输出线中的一根)。
diff --git a/docs/.vitepress/theme/components/appendix/computer-fundamentals/GraphStructureDemo.vue b/docs/.vitepress/theme/components/appendix/computer-fundamentals/GraphStructureDemo.vue index 6be7d35..ac8ab4d 100644 --- a/docs/.vitepress/theme/components/appendix/computer-fundamentals/GraphStructureDemo.vue +++ b/docs/.vitepress/theme/components/appendix/computer-fundamentals/GraphStructureDemo.vue @@ -69,7 +69,11 @@ :cx="node.x" :cy="node.y" r="20" - :fill="selectedNode === index ? 'var(--vp-c-brand)' : 'var(--vp-c-brand-soft)'" + :fill=" + selectedNode === index + ? 'var(--vp-c-brand)' + : 'var(--vp-c-brand-soft)' + " stroke="var(--vp-c-brand)" stroke-width="2" /> @@ -154,7 +158,7 @@ const edges = ref([ ]) const averageDegree = computed(() => { - return (edges.value.length * 2 / nodes.length).toFixed(1) + return ((edges.value.length * 2) / nodes.length).toFixed(1) }) @@ -174,8 +178,14 @@ const averageDegree = computed(() => { margin-bottom: 1.5rem; } -.demo-header .title { font-weight: 700; font-size: 1.1rem; } -.demo-header .subtitle { color: var(--vp-c-text-2); font-size: 0.9rem; } +.demo-header .title { + font-weight: 700; + font-size: 1.1rem; +} +.demo-header .subtitle { + color: var(--vp-c-text-2); + font-size: 0.9rem; +} .graph-types { margin-bottom: 2rem; diff --git a/docs/.vitepress/theme/components/appendix/computer-fundamentals/GreedyThinkingDemo.vue b/docs/.vitepress/theme/components/appendix/computer-fundamentals/GreedyThinkingDemo.vue index 17b62a5..4c38373 100644 --- a/docs/.vitepress/theme/components/appendix/computer-fundamentals/GreedyThinkingDemo.vue +++ b/docs/.vitepress/theme/components/appendix/computer-fundamentals/GreedyThinkingDemo.vue @@ -8,7 +8,7 @@
- 贪心算法在每一步选择中都采取当前状态下最优的选择
+ 贪心算法在每一步选择中都采取当前状态下最优的选择
希望通过一系列局部最优选择达到全局最优
@@ -36,7 +36,11 @@ 需要找零:{{ changeAmount }}
-
+
{{ step.coin }}
× {{ step.count }} = {{ step.value }}元
@@ -46,7 +50,7 @@
- ✓ 贪心策略:每次选择面值最大的硬币
+ ✓ 贪心策略:每次选择面值最大的硬币
✓ 适用于人民币、美元等货币系统
@@ -59,9 +63,14 @@
-
{{ activity.start }} - {{ activity.end }}
+
+ {{ activity.start }} - {{ activity.end }} +
{{ activity.name }}
@@ -183,17 +192,51 @@ const changeSteps = [ { coin: '1元', count: 2, value: 2 } ] -const totalCoins = computed(() => changeSteps.reduce((sum, step) => sum + step.count, 0)) +const totalCoins = computed(() => + changeSteps.reduce((sum, step) => sum + step.count, 0) +) const activities = [ - { start: '9:00', end: '10:00', name: '活动1', selected: true, conflicting: false }, - { start: '9:30', end: '11:30', name: '活动2', selected: false, conflicting: true }, - { start: '10:00', end: '11:00', name: '活动3', selected: true, conflicting: false }, - { start: '10:30', end: '12:00', name: '活动4', selected: false, conflicting: true }, - { start: '11:00', end: '12:00', name: '活动5', selected: true, conflicting: false } + { + start: '9:00', + end: '10:00', + name: '活动1', + selected: true, + conflicting: false + }, + { + start: '9:30', + end: '11:30', + name: '活动2', + selected: false, + conflicting: true + }, + { + start: '10:00', + end: '11:00', + name: '活动3', + selected: true, + conflicting: false + }, + { + start: '10:30', + end: '12:00', + name: '活动4', + selected: false, + conflicting: true + }, + { + start: '11:00', + end: '12:00', + name: '活动5', + selected: true, + conflicting: false + } ] -const selectedCount = computed(() => activities.filter(a => a.selected).length) +const selectedCount = computed( + () => activities.filter((a) => a.selected).length +) diff --git a/docs/.vitepress/theme/components/appendix/computer-fundamentals/HashTableDemo.vue b/docs/.vitepress/theme/components/appendix/computer-fundamentals/HashTableDemo.vue index 31d7acf..5c23903 100644 --- a/docs/.vitepress/theme/components/appendix/computer-fundamentals/HashTableDemo.vue +++ b/docs/.vitepress/theme/components/appendix/computer-fundamentals/HashTableDemo.vue @@ -28,9 +28,7 @@ placeholder="值 (如: 苹果)" class="hash-input" /> - +
@@ -60,7 +58,10 @@
{{ index }}
{{ slot || '空' }}
@@ -121,7 +122,18 @@ const newKey = ref('') const newValue = ref('') const exampleKey = ref('apple') -const hashTable = ref([null, null, null, null, null, null, null, null, null, null]) +const hashTable = ref([ + null, + null, + null, + null, + null, + null, + null, + null, + null, + null +]) // 初始化一些数据 const initData = () => { @@ -130,7 +142,7 @@ const initData = () => { { key: 'banana', value: '香蕉' }, { key: 'orange', value: '橙子' } ] - data.forEach(item => { + data.forEach((item) => { const index = simpleHash(item.key) hashTable.value[index] = `${item.key}: ${item.value}` }) @@ -174,8 +186,14 @@ initData() margin-bottom: 1.5rem; } -.demo-header .title { font-weight: 700; font-size: 1.1rem; } -.demo-header .subtitle { color: var(--vp-c-text-2); font-size: 0.9rem; } +.demo-header .title { + font-weight: 700; + font-size: 1.1rem; +} +.demo-header .subtitle { + color: var(--vp-c-text-2); + font-size: 0.9rem; +} .analogy-box { display: flex; diff --git a/docs/.vitepress/theme/components/appendix/computer-fundamentals/LanguageEvolutionDemo.vue b/docs/.vitepress/theme/components/appendix/computer-fundamentals/LanguageEvolutionDemo.vue index 32f4817..77752b6 100644 --- a/docs/.vitepress/theme/components/appendix/computer-fundamentals/LanguageEvolutionDemo.vue +++ b/docs/.vitepress/theme/components/appendix/computer-fundamentals/LanguageEvolutionDemo.vue @@ -55,13 +55,17 @@
✓ 优点
    -
  • {{ pro }}
  • +
  • + {{ pro }} +
✗ 缺点
    -
  • {{ con }}
  • +
  • + {{ con }} +
@@ -99,11 +103,7 @@
现代编程语言生态
-
+
{{ lang.name }}
{{ lang.year }}
@@ -134,11 +134,7 @@ const eras = [ icon: '0️⃣', position: '5%', example: '10110000 11000000\n(add two numbers)', - features: [ - '直接用二进制代码', - '机器可以直接执行', - '完全依赖硬件' - ], + features: ['直接用二进制代码', '机器可以直接执行', '完全依赖硬件'], pros: ['执行速度最快', '直接控制硬件'], cons: ['极难编写', '容易出错', '不可移植'] }, @@ -149,11 +145,7 @@ const eras = [ icon: '🔧', position: '25%', example: 'MOV AX, 5\nADD AX, 3\n(add 5 and 3)', - features: [ - '用助记符代替二进制', - '需要汇编器翻译', - '仍然依赖硬件' - ], + features: ['用助记符代替二进制', '需要汇编器翻译', '仍然依赖硬件'], pros: ['比机器语言好懂', '效率仍然很高'], cons: ['代码冗长', '不可移植', '需要了解硬件'] }, @@ -164,11 +156,7 @@ const eras = [ icon: '📋', position: '50%', example: 'int add(int a, int b) {\n return a + b;\n}', - features: [ - '函数、变量等抽象', - '结构化编程', - '可移植性好' - ], + features: ['函数、变量等抽象', '结构化编程', '可移植性好'], pros: ['易读易写', '可移植', '效率较高'], cons: ['大型项目难以维护', '代码重用性差'] }, @@ -179,11 +167,7 @@ const eras = [ icon: '🎯', position: '75%', example: 'class Calculator {\n add(a, b) { return a + b; }\n}', - features: [ - '类、对象、封装、继承', - '模块化设计', - '代码复用性强' - ], + features: ['类、对象、封装、继承', '模块化设计', '代码复用性强'], pros: ['适合大型项目', '易维护', '可扩展'], cons: ['学习曲线陡', '代码量较大'] }, @@ -194,11 +178,7 @@ const eras = [ icon: '🚀', position: '95%', example: 'const add = (a, b) => a + b;\n(add arrow function)', - features: [ - '简洁优雅的语法', - '多范式支持', - '强大的标准库' - ], + features: ['简洁优雅的语法', '多范式支持', '强大的标准库'], pros: ['开发效率高', '生态丰富', '社区活跃'], cons: ['抽象层多', '性能可能不如底层语言'] } @@ -232,8 +212,14 @@ const currentEra = computed(() => eras[activeEra.value]) margin-bottom: 1.5rem; } -.demo-header .title { font-weight: 700; font-size: 1.1rem; } -.demo-header .subtitle { color: var(--vp-c-text-2); font-size: 0.9rem; } +.demo-header .title { + font-weight: 700; + font-size: 1.1rem; +} +.demo-header .subtitle { + color: var(--vp-c-text-2); + font-size: 0.9rem; +} .evolution-timeline { background: var(--vp-c-bg); @@ -321,7 +307,8 @@ const currentEra = computed(() => eras[activeEra.value]) gap: 1.5rem; } -.content-section {} +.content-section { +} .section-title { font-weight: 600; diff --git a/docs/.vitepress/theme/components/appendix/computer-fundamentals/LanguageScenarioDemo.vue b/docs/.vitepress/theme/components/appendix/computer-fundamentals/LanguageScenarioDemo.vue index f360ca2..7d3dc99 100644 --- a/docs/.vitepress/theme/components/appendix/computer-fundamentals/LanguageScenarioDemo.vue +++ b/docs/.vitepress/theme/components/appendix/computer-fundamentals/LanguageScenarioDemo.vue @@ -120,9 +120,7 @@
现代语言
-
- 越来越接近
人类思维 -
+
越来越接近
人类思维
@@ -139,7 +137,8 @@ const scenarios = [ icon: '🌐', title: '开发网站', desc: '创建交互式网页', - fullDesc: '你需要创建一个在线购物网站,用户可以浏览商品、加入购物车、下单支付', + fullDesc: + '你需要创建一个在线购物网站,用户可以浏览商品、加入购物车、下单支付', reasons: [ 'HTML 定义网页结构', 'CSS 实现美观样式', @@ -211,7 +210,9 @@ const scenarios = [ } ] -const currentScenario = computed(() => scenarios.find(s => s.id === activeScenario.value)) +const currentScenario = computed(() => + scenarios.find((s) => s.id === activeScenario.value) +) diff --git a/docs/.vitepress/theme/components/appendix/computer-fundamentals/NetworkLayers.vue b/docs/.vitepress/theme/components/appendix/computer-fundamentals/NetworkLayers.vue index 90ac0c9..1a2c36b 100644 --- a/docs/.vitepress/theme/components/appendix/computer-fundamentals/NetworkLayers.vue +++ b/docs/.vitepress/theme/components/appendix/computer-fundamentals/NetworkLayers.vue @@ -7,8 +7,8 @@
-
-
+
{{ layer.device }}
-
+
{{ currentLayer.name }} {{ currentLayer.analogy }} @@ -45,14 +39,9 @@ {{ currentLayer.desc }}
-
- 核心任务 -
+
核心任务
    -
  • +
  • {{ task }}
@@ -64,35 +53,25 @@
-
- 数据封装过程 -
+
数据封装过程
-
+
{{ step.layer }}
- {{ step.header }} + {{ step.header }} {{ step.payload }}
-
- ↓ 发送 -
+
↓ 发送
- 核心思想:分层设计让网络协议模块化,每层只关心自己的职责。数据从应用层向下传递时,每层都会添加自己的"信封"(头部),接收时再逐层拆开。 + 核心思想:分层设计让网络协议模块化,每层只关心自己的职责。数据从应用层向下传递时,每层都会添加自己的"信封"(头部),接收时再逐层拆开。
@@ -118,7 +97,12 @@ const layers = [ device: '', analogy: '包裹分拣组', desc: '负责端到端的数据传输,确保数据的可靠性或实时性。', - tasks: ['建立和管理端到端连接', '分段和重组数据', '流量控制和拥塞控制', '端口寻址'], + tasks: [ + '建立和管理端到端连接', + '分段和重组数据', + '流量控制和拥塞控制', + '端口寻址' + ], unit: '段(Segment)' }, { @@ -177,8 +161,15 @@ const encapsulation = [ 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-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: grid; diff --git a/docs/.vitepress/theme/components/appendix/computer-fundamentals/NetworkOverviewDemo.vue b/docs/.vitepress/theme/components/appendix/computer-fundamentals/NetworkOverviewDemo.vue index 30a6960..14932fb 100644 --- a/docs/.vitepress/theme/components/appendix/computer-fundamentals/NetworkOverviewDemo.vue +++ b/docs/.vitepress/theme/components/appendix/computer-fundamentals/NetworkOverviewDemo.vue @@ -201,8 +201,14 @@ const protocolLayers = [ margin-bottom: 1.5rem; } -.demo-header .title { font-weight: 700; font-size: 1.1rem; } -.demo-header .subtitle { color: var(--vp-c-text-2); font-size: 0.9rem; } +.demo-header .title { + font-weight: 700; + font-size: 1.1rem; +} +.demo-header .subtitle { + color: var(--vp-c-text-2); + font-size: 0.9rem; +} .network-scene { margin-bottom: 2rem; @@ -253,8 +259,14 @@ const protocolLayers = [ border-radius: 6px; } -.app-icon { font-size: 1.5rem; } -.app-name { font-size: 0.8rem; font-weight: 600; color: var(--vp-c-brand); } +.app-icon { + font-size: 1.5rem; +} +.app-name { + font-size: 0.8rem; + font-weight: 600; + color: var(--vp-c-brand); +} .network-path { flex: 1; @@ -316,8 +328,15 @@ const protocolLayers = [ } @keyframes flowMove { - 0%, 100% { transform: translateX(-20px); opacity: 0; } - 50% { transform: translateX(20px); opacity: 1; } + 0%, + 100% { + transform: translateX(-20px); + opacity: 0; + } + 50% { + transform: translateX(20px); + opacity: 1; + } } .flow-packet { diff --git a/docs/.vitepress/theme/components/appendix/computer-fundamentals/NetworkPrincipleDemo.vue b/docs/.vitepress/theme/components/appendix/computer-fundamentals/NetworkPrincipleDemo.vue index bce4611..c631265 100644 --- a/docs/.vitepress/theme/components/appendix/computer-fundamentals/NetworkPrincipleDemo.vue +++ b/docs/.vitepress/theme/components/appendix/computer-fundamentals/NetworkPrincipleDemo.vue @@ -106,8 +106,14 @@ const principles = [ margin-bottom: 1.5rem; } -.demo-header .title { font-weight: 700; font-size: 1.1rem; } -.demo-header .subtitle { color: var(--vp-c-text-2); font-size: 0.9rem; } +.demo-header .title { + font-weight: 700; + font-size: 1.1rem; +} +.demo-header .subtitle { + color: var(--vp-c-text-2); + font-size: 0.9rem; +} .principle-cards { display: grid; diff --git a/docs/.vitepress/theme/components/appendix/computer-fundamentals/OSSystemOverviewDemo.vue b/docs/.vitepress/theme/components/appendix/computer-fundamentals/OSSystemOverviewDemo.vue index f835c0d..10a978c 100644 --- a/docs/.vitepress/theme/components/appendix/computer-fundamentals/OSSystemOverviewDemo.vue +++ b/docs/.vitepress/theme/components/appendix/computer-fundamentals/OSSystemOverviewDemo.vue @@ -8,9 +8,7 @@
-
- 应用程序层 -
+
应用程序层
-
- 操作系统内核 -
+
操作系统内核
-
- 进程管理、内存管理、文件系统、设备管理 -
+
进程管理、内存管理、文件系统、设备管理
-
- 硬件层 -
+
硬件层
💻 CPU @@ -70,27 +62,18 @@
-
- 应用程序请求资源(内存、CPU、文件) -
+
应用程序请求资源(内存、CPU、文件)
-
- 操作系统内核统一分配和调度 -
+
操作系统内核统一分配和调度
-
- 硬件执行实际操作 -
+
硬件执行实际操作
-
@@ -132,13 +115,13 @@ const kernelComponents = [ ] const activeAppName = computed(() => { - const app = applications.find(a => a.id === activeApp.value) + const app = applications.find((a) => a.id === activeApp.value) return app?.name || '' }) const getActiveAppDesc = () => { - const component = kernelComponents.find(c => c.id === activeComponent.value) - const app = applications.find(a => a.id === activeApp.value) + const component = kernelComponents.find((c) => c.id === activeComponent.value) + const app = applications.find((a) => a.id === activeApp.value) if (!app || !component) return '请选择应用程序和内核组件' return `${app.name} 需要使用 ${component.name} 的功能` @@ -161,8 +144,14 @@ const getActiveAppDesc = () => { margin-bottom: 1.5rem; } -.demo-header .title { font-weight: 700; font-size: 1.1rem; } -.demo-header .subtitle { color: var(--vp-c-text-2); font-size: 0.9rem; } +.demo-header .title { + font-weight: 700; + font-size: 1.1rem; +} +.demo-header .subtitle { + color: var(--vp-c-text-2); + font-size: 0.9rem; +} .os-layers { display: flex; @@ -213,7 +202,8 @@ const getActiveAppDesc = () => { transition: all 0.3s; } -.app-icon:hover, .app-icon.active { +.app-icon:hover, +.app-icon.active { transform: scale(1.1); background: rgba(255, 255, 255, 0.2); } @@ -227,7 +217,8 @@ const getActiveAppDesc = () => { transition: all 0.3s; } -.kernel-component:hover, .kernel-component.active { +.kernel-component:hover, +.kernel-component.active { background: rgba(255, 255, 255, 0.3); font-weight: 600; } diff --git a/docs/.vitepress/theme/components/appendix/computer-fundamentals/PhysicalLayerDemo.vue b/docs/.vitepress/theme/components/appendix/computer-fundamentals/PhysicalLayerDemo.vue index d734bf0..08a01e9 100644 --- a/docs/.vitepress/theme/components/appendix/computer-fundamentals/PhysicalLayerDemo.vue +++ b/docs/.vitepress/theme/components/appendix/computer-fundamentals/PhysicalLayerDemo.vue @@ -30,32 +30,55 @@
- + - 1 - 0 - 1 - 1 - 0 + + 1 + + + 0 + + + 1 + + + 1 + + + 0 + - + + 关 + - + + 关 +
@@ -114,7 +137,11 @@
典型应用场景
-
+
{{ app.icon }} {{ app.text }}
@@ -138,7 +165,8 @@ const mediaData = { copper: { signalName: '电信号(电压高低)', signalDesc: '用高低电压表示 0 和 1', - wavePath: 'M 50 75 L 100 75 L 100 25 L 150 25 L 150 125 L 200 125 L 200 25 L 250 25 L 250 25 L 300 25 L 300 125 L 350 125 L 350 25 L 400 25', + wavePath: + 'M 50 75 L 100 75 L 100 25 L 150 25 L 150 125 L 200 125 L 200 25 L 250 25 L 250 25 L 300 25 L 300 125 L 350 125 L 350 25 L 400 25', speed: '最高 10 Gbps', distance: '100 米', immunity: '较差(易受电磁干扰)', @@ -152,7 +180,8 @@ const mediaData = { fiber: { signalName: '光信号(光的开关)', signalDesc: '用光脉冲表示 0 和 1', - wavePath: 'M 50 75 L 100 75 L 100 25 L 150 25 L 150 125 L 200 125 L 200 25 L 250 25 L 250 25 L 300 25 L 300 125 L 350 125 L 350 25 L 400 25', + wavePath: + 'M 50 75 L 100 75 L 100 25 L 150 25 L 150 125 L 200 125 L 200 25 L 250 25 L 250 25 L 300 25 L 300 125 L 350 125 L 350 25 L 400 25', speed: '最高 100+ Tbps', distance: '几十公里', immunity: '极强(不受电磁干扰)', @@ -198,8 +227,14 @@ const currentMedia = computed(() => mediaData[activeMedia.value]) margin-bottom: 1.5rem; } -.demo-header .title { font-weight: 700; font-size: 1.1rem; } -.demo-header .subtitle { color: var(--vp-c-text-2); font-size: 0.9rem; } +.demo-header .title { + font-weight: 700; + font-size: 1.1rem; +} +.demo-header .subtitle { + color: var(--vp-c-text-2); + font-size: 0.9rem; +} .media-selector { margin-bottom: 2rem; @@ -283,8 +318,12 @@ const currentMedia = computed(() => mediaData[activeMedia.value]) } @keyframes drawSignal { - 0% { stroke-dashoffset: 1000; } - 100% { stroke-dashoffset: 0; } + 0% { + stroke-dashoffset: 1000; + } + 100% { + stroke-dashoffset: 0; + } } .signal-legend { diff --git a/docs/.vitepress/theme/components/appendix/computer-fundamentals/ProcessDemo.vue b/docs/.vitepress/theme/components/appendix/computer-fundamentals/ProcessDemo.vue index a8b28e5..675ac4f 100644 --- a/docs/.vitepress/theme/components/appendix/computer-fundamentals/ProcessDemo.vue +++ b/docs/.vitepress/theme/components/appendix/computer-fundamentals/ProcessDemo.vue @@ -1,13 +1,21 @@ @@ -77,7 +94,9 @@ const processes = ref([ { id: 3, name: '游戏渲染', icon: '🎮', progress: 0 } ]) -const activeProcess = computed(() => processes.value.find(p => p.id === activeProcessId.value)) +const activeProcess = computed(() => + processes.value.find((p) => p.id === activeProcessId.value) +) const setSpeed = (s) => { speed.value = s @@ -88,17 +107,17 @@ const setSpeed = (s) => { } const startLoop = () => { - const switchTime = speed.value === 'slow' ? 1200 : 80; // 慢动作 1.2s,快动作极快 - + 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) + const curr = processes.value.find((p) => p.id === activeProcessId.value) if (curr) { - curr.progress += (speed.value === 'slow' ? 15 : 4) + curr.progress += speed.value === 'slow' ? 15 : 4 if (curr.progress >= 100) curr.progress = 0 } @@ -106,7 +125,6 @@ const startLoop = () => { let nextId = activeProcessId.value + 1 if (nextId > 3) nextId = 1 activeProcessId.value = nextId - }, switchTime) } @@ -203,7 +221,7 @@ onUnmounted(() => { flex-direction: column; justify-content: center; align-items: center; - box-shadow: 0 4px 12px rgba(0,0,0,0.05); + box-shadow: 0 4px 12px rgba(0, 0, 0, 0.05); transition: all 0.3s; position: relative; } @@ -276,7 +294,9 @@ onUnmounted(() => { .process-card.active::before { content: ''; position: absolute; - top: 0; left: 0; right: 0; + top: 0; + left: 0; + right: 0; height: 4px; background: var(--vp-c-brand-1); } diff --git a/docs/.vitepress/theme/components/appendix/computer-fundamentals/ProcessMemoryFilesystemDemo.vue b/docs/.vitepress/theme/components/appendix/computer-fundamentals/ProcessMemoryFilesystemDemo.vue index d56665b..46fc17c 100644 --- a/docs/.vitepress/theme/components/appendix/computer-fundamentals/ProcessMemoryFilesystemDemo.vue +++ b/docs/.vitepress/theme/components/appendix/computer-fundamentals/ProcessMemoryFilesystemDemo.vue @@ -7,9 +7,7 @@
-
- 场景选择: -
+
场景选择:
@@ -207,19 +207,21 @@ const sceneData = { } } -const currentSceneData = computed(() => sceneData[activeScene.value] || sceneData.launch) +const currentSceneData = computed( + () => sceneData[activeScene.value] || sceneData.launch +) const getProcessName = (id) => { - const proc = processes.value.find(p => p.id === id) + const proc = processes.value.find((p) => p.id === id) return proc?.name || '系统' } const getIcon = (type) => { const icons = { - 'json': '📋', - 'db': '🗄️', - 'folder': '📁', - 'audio': '🎵' + json: '📋', + db: '🗄️', + folder: '📁', + audio: '🎵' } return icons[type] || '📄' } @@ -241,8 +243,14 @@ const getIcon = (type) => { margin-bottom: 1.5rem; } -.demo-header .title { font-weight: 700; font-size: 1.1rem; } -.demo-header .subtitle { color: var(--vp-c-text-2); font-size: 0.9rem; } +.demo-header .title { + font-weight: 700; + font-size: 1.1rem; +} +.demo-header .subtitle { + color: var(--vp-c-text-2); + font-size: 0.9rem; +} .scene-buttons { display: flex; @@ -300,8 +308,12 @@ const getIcon = (type) => { font-size: 0.9rem; } -.zone-icon { font-size: 1.2rem; } -.zone-name { font-size: 0.85rem; } +.zone-icon { + font-size: 1.2rem; +} +.zone-name { + font-size: 0.85rem; +} .process-list { display: flex; @@ -324,7 +336,9 @@ const getIcon = (type) => { background: var(--vp-c-brand-soft); } -.proc-name, .proc-pid, .proc-state { +.proc-name, +.proc-pid, +.proc-state { flex: 1; } @@ -389,8 +403,13 @@ const getIcon = (type) => { background: var(--vp-c-brand-soft); } -.file-name { flex: 1; } -.file-size { color: var(--vp-c-text-2); font-size: 0.7rem; } +.file-name { + flex: 1; +} +.file-size { + color: var(--vp-c-text-2); + font-size: 0.7rem; +} .explanation { background: var(--vp-c-bg); diff --git a/docs/.vitepress/theme/components/appendix/computer-fundamentals/ProgrammingLanguageComparisonDemo.vue b/docs/.vitepress/theme/components/appendix/computer-fundamentals/ProgrammingLanguageComparisonDemo.vue index a744aeb..258acc4 100644 --- a/docs/.vitepress/theme/components/appendix/computer-fundamentals/ProgrammingLanguageComparisonDemo.vue +++ b/docs/.vitepress/theme/components/appendix/computer-fundamentals/ProgrammingLanguageComparisonDemo.vue @@ -65,13 +65,17 @@
✓ 优点
    -
  • {{ pro }}
  • +
  • + {{ pro }} +
✗ 缺点
    -
  • {{ con }}
  • +
  • + {{ con }} +
@@ -92,7 +96,11 @@ - + {{ lang.icon }} {{ lang.name }} {{ lang.difficulty }} {{ lang.efficiency }} @@ -197,7 +205,9 @@ const languages = [ } ] -const currentLang = computed(() => languages.find(l => l.name === activeLang.value)) +const currentLang = computed(() => + languages.find((l) => l.name === activeLang.value) +) diff --git a/docs/.vitepress/theme/components/appendix/computer-fundamentals/StorageHierarchyDemo.vue b/docs/.vitepress/theme/components/appendix/computer-fundamentals/StorageHierarchyDemo.vue index 9bc9764..fc05108 100644 --- a/docs/.vitepress/theme/components/appendix/computer-fundamentals/StorageHierarchyDemo.vue +++ b/docs/.vitepress/theme/components/appendix/computer-fundamentals/StorageHierarchyDemo.vue @@ -106,8 +106,7 @@
- + diff --git a/docs/.vitepress/theme/components/appendix/computer-fundamentals/TcpUdpComparison.vue b/docs/.vitepress/theme/components/appendix/computer-fundamentals/TcpUdpComparison.vue index 520bafb..3a5fa94 100644 --- a/docs/.vitepress/theme/components/appendix/computer-fundamentals/TcpUdpComparison.vue +++ b/docs/.vitepress/theme/components/appendix/computer-fundamentals/TcpUdpComparison.vue @@ -7,14 +7,14 @@
- -
-
+
{{ currentProtocol.name }} {{ currentProtocol.fullName }}
- +
-
- 核心机制 -
+
核心机制
-
- 适用场景 -
+
适用场景
{{ use }} + >{{ use }}
-
- 传输过程演示 -
+
传输过程演示
-
- 发送方 -
+
发送方
-
{{ packet.seq }}
- +
-
- 网络通道 -
-
+
网络通道
+
{{ isCongested ? '拥堵' : '正常' }}
- - +
- +
-
- 接收方 -
+
接收方
-
@@ -133,17 +111,11 @@
- +
-
- 传输日志 -
+
传输日志
-
+
{{ log }}
@@ -151,9 +123,7 @@
-
- 特性对比 -
+
特性对比
@@ -163,10 +133,7 @@ - + @@ -183,7 +150,9 @@
- 核心思想:TCP 像挂号信,确保送达但较慢;UDP 像平信,快速但不保证送达。选择哪种协议取决于应用场景:需要可靠性选 TCP,需要实时性选 UDP。 + 核心思想:TCP 像挂号信,确保送达但较慢;UDP + 像平信,快速但不保证送达。选择哪种协议取决于应用场景:需要可靠性选 + TCP,需要实时性选 UDP。
@@ -267,25 +236,25 @@ const toggleCongestion = () => { const runDemo = async () => { receivedPackets.value = [] logs.value = ['开始传输演示...'] - + for (let i = 0; i < packets.value.length; i++) { packets.value[i].sent = false packets.value[i].acked = false packets.value[i].lost = false } - + const isTcp = activeTab.value === 'tcp' - + for (let i = 0; i < packets.value.length; i++) { const packet = packets.value[i] packet.sent = true - + if (isCongested.value && Math.random() > 0.5) { packet.lost = true logs.value.push(`包 ${packet.seq} 丢失!`) - + if (isTcp) { - await new Promise(r => setTimeout(r, 500)) + await new Promise((r) => setTimeout(r, 500)) logs.value.push(`TCP 重传包 ${packet.seq}...`) packet.lost = false receivedPackets.value.push(packet.seq) @@ -297,12 +266,14 @@ const runDemo = async () => { packet.acked = true logs.value.push(`包 ${packet.seq} 送达`) } - - await new Promise(r => setTimeout(r, 300)) + + await new Promise((r) => setTimeout(r, 300)) } - + if (isTcp) { - logs.value.push(`TCP 完成: 收到 ${receivedPackets.value.length} 个包,顺序: ${receivedPackets.value.join(', ')}`) + logs.value.push( + `TCP 完成: 收到 ${receivedPackets.value.length} 个包,顺序: ${receivedPackets.value.join(', ')}` + ) } else { logs.value.push(`UDP 完成: 收到 ${receivedPackets.value.length} 个包`) } @@ -325,8 +296,15 @@ const runDemo = async () => { 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-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; +} .comparison-tabs { display: flex; @@ -353,7 +331,9 @@ const runDemo = async () => { background: var(--vp-c-brand-soft); } -.tab-icon { font-size: 1.1rem; } +.tab-icon { + font-size: 1.1rem; +} .protocol-detail { background: var(--vp-c-bg); @@ -395,15 +375,24 @@ const runDemo = async () => { border-radius: 4px; } -.feature-icon { font-size: 1rem; } -.feature-name { font-size: 0.75rem; color: var(--vp-c-text-2); } -.feature-value { font-size: 0.8rem; font-weight: bold; } +.feature-icon { + font-size: 1rem; +} +.feature-name { + font-size: 0.75rem; + color: var(--vp-c-text-2); +} +.feature-value { + font-size: 0.8rem; + font-weight: bold; +} .mechanism-section { margin-bottom: 0.75rem; } -.mechanism-title, .use-title { +.mechanism-title, +.use-title { font-weight: bold; font-size: 0.85rem; margin-bottom: 0.5rem; @@ -465,7 +454,8 @@ const runDemo = async () => { margin-bottom: 0.75rem; } -.sender, .receiver { +.sender, +.receiver { flex: 1; padding: 0.5rem; background: var(--vp-c-bg-alt); @@ -478,7 +468,8 @@ const runDemo = async () => { margin-bottom: 0.25rem; } -.packets, .received-packets { +.packets, +.received-packets { display: flex; gap: 0.25rem; flex-wrap: wrap; @@ -604,7 +595,8 @@ table { font-size: 0.8rem; } -th, td { +th, +td { border: 1px solid var(--vp-c-divider); padding: 0.4rem; text-align: center; @@ -635,5 +627,4 @@ th { display: flex; gap: 0.25rem; } - diff --git a/docs/.vitepress/theme/components/appendix/computer-fundamentals/TransistorDemo.vue b/docs/.vitepress/theme/components/appendix/computer-fundamentals/TransistorDemo.vue index 4c1cd54..9b67d30 100644 --- a/docs/.vitepress/theme/components/appendix/computer-fundamentals/TransistorDemo.vue +++ b/docs/.vitepress/theme/components/appendix/computer-fundamentals/TransistorDemo.vue @@ -5,7 +5,7 @@
- 源极
Source
+ 源极
Source
@@ -23,13 +23,15 @@
-
{{ gateOn ? '导通 → 输出 1' : '断开 → 输出 0' }}
+
+ {{ gateOn ? '导通 → 输出 1' : '断开 → 输出 0' }} +
- 漏极
Drain
+ 漏极
Drain
@@ -179,14 +181,28 @@ const gateOn = ref(false) animation: flow 1.2s linear infinite; } -.electron.e2 { animation-delay: 0.4s; } -.electron.e3 { animation-delay: 0.8s; } +.electron.e2 { + animation-delay: 0.4s; +} +.electron.e3 { + animation-delay: 0.8s; +} @keyframes flow { - 0% { left: -8%; opacity: 0; } - 10% { opacity: 1; } - 90% { opacity: 1; } - 100% { left: 108%; opacity: 0; } + 0% { + left: -8%; + opacity: 0; + } + 10% { + opacity: 1; + } + 90% { + opacity: 1; + } + 100% { + left: 108%; + opacity: 0; + } } .channel-status { @@ -208,7 +224,11 @@ const gateOn = ref(false) } @media (max-width: 480px) { - .pin-wire { width: 1.5rem; } - .channel-area { min-width: 5rem; } + .pin-wire { + width: 1.5rem; + } + .channel-area { + min-width: 5rem; + } } diff --git a/docs/.vitepress/theme/components/appendix/computer-fundamentals/TransmissionDemo.vue b/docs/.vitepress/theme/components/appendix/computer-fundamentals/TransmissionDemo.vue index 0d7a80b..0791d92 100644 --- a/docs/.vitepress/theme/components/appendix/computer-fundamentals/TransmissionDemo.vue +++ b/docs/.vitepress/theme/components/appendix/computer-fundamentals/TransmissionDemo.vue @@ -12,36 +12,20 @@ :class="{ active: activeType === 'serial' }" @click="activeType = 'serial'" > -
- ➡️ -
-
- 串行传输 -
-
- 一位一位依次传输 -
-
- USB、SATA、PCIe -
+
➡️
+
串行传输
+
一位一位依次传输
+
USB、SATA、PCIe
-
- ⬇️⬇️⬇️⬇️ -
-
- 并行传输 -
-
- 多位同时传输 -
-
- 旧式打印机接口、IDE -
+
⬇️⬇️⬇️⬇️
+
并行传输
+
多位同时传输
+
旧式打印机接口、IDE
@@ -51,47 +35,35 @@
-
- 发送端 -
+
发送端
{{ bit }} + :class="{ + sending: sendingBit === i && activeType === 'serial' + }" + >{{ bit }}
-
-
- 单通道 -
+
+
单通道
+ >●
-
-
-
- 通道{{ i }} -
+
+
+
通道{{ i }}
@@ -99,30 +71,19 @@
-
- 接收端 -
+
接收端
- {{ bit }} + {{ + bit + }}
- +
-
- 串行 vs 并行对比 -
+
串行 vs 并行对比
{{ row.feature }}
@@ -158,7 +119,8 @@
- 核心思想:现代高速传输多采用串行方式。虽然并行"看起来"更快(一次传多位),但串行可以跑更高频率,抗干扰更强,实际速度反而更快。 + 核心思想:现代高速传输多采用串行方式。虽然并行"看起来"更快(一次传多位),但串行可以跑更高频率,抗干扰更强,实际速度反而更快。
@@ -207,8 +169,15 @@ const startTransmission = () => { 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-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; @@ -279,7 +248,8 @@ const startTransmission = () => { margin-bottom: 0.75rem; } -.sender, .receiver { +.sender, +.receiver { text-align: center; } @@ -382,7 +352,8 @@ table { font-size: 0.8rem; } -th, td { +th, +td { border: 1px solid var(--vp-c-divider); padding: 0.4rem; text-align: center; @@ -402,5 +373,4 @@ th { display: flex; gap: 0.25rem; } - diff --git a/docs/.vitepress/theme/components/appendix/computer-fundamentals/TransportLayerDemo.vue b/docs/.vitepress/theme/components/appendix/computer-fundamentals/TransportLayerDemo.vue index dd085e9..c666c38 100644 --- a/docs/.vitepress/theme/components/appendix/computer-fundamentals/TransportLayerDemo.vue +++ b/docs/.vitepress/theme/components/appendix/computer-fundamentals/TransportLayerDemo.vue @@ -107,7 +107,8 @@
端口号:应用程序的标识
- 端口号就像公寓房间号,IP 地址是公寓楼地址,合起来才能找到具体的应用程序 + 端口号就像公寓房间号,IP + 地址是公寓楼地址,合起来才能找到具体的应用程序
protocolData[activeProtocol.value]) margin-bottom: 1.5rem; } -.demo-header .title { font-weight: 700; font-size: 1.1rem; } -.demo-header .subtitle { color: var(--vp-c-text-2); font-size: 0.9rem; } +.demo-header .title { + font-weight: 700; + font-size: 1.1rem; +} +.demo-header .subtitle { + color: var(--vp-c-text-2); + font-size: 0.9rem; +} .protocol-tabs { display: flex; @@ -422,9 +429,17 @@ const currentProtocol = computed(() => protocolData[activeProtocol.value]) } @keyframes slideRight { - 0% { transform: translateX(-100%); opacity: 0; } - 50% { opacity: 1; } - 100% { transform: translateX(100%); opacity: 0; } + 0% { + transform: translateX(-100%); + opacity: 0; + } + 50% { + opacity: 1; + } + 100% { + transform: translateX(100%); + opacity: 0; + } } .side-desc { diff --git a/docs/.vitepress/theme/components/appendix/computer-fundamentals/TreeStructureDemo.vue b/docs/.vitepress/theme/components/appendix/computer-fundamentals/TreeStructureDemo.vue index d348b5c..7d07409 100644 --- a/docs/.vitepress/theme/components/appendix/computer-fundamentals/TreeStructureDemo.vue +++ b/docs/.vitepress/theme/components/appendix/computer-fundamentals/TreeStructureDemo.vue @@ -24,20 +24,43 @@
- - - - {{ node.value }} + + + {{ node.value }} +
@@ -83,14 +106,9 @@
HTML 结构
-<html> - <body> - <div class="container"> - <h1>标题</h1> - <p>段落</p> - </div> - </body> -</html> + <html> <body> <div class="container"> + <h1>标题</h1> <p>段落</p> </div> + </body> </html>
@@ -235,8 +253,14 @@ const binaryTreeLines = [ margin-bottom: 1.5rem; } -.demo-header .title { font-weight: 700; font-size: 1.1rem; } -.demo-header .subtitle { color: var(--vp-c-text-2); font-size: 0.9rem; } +.demo-header .title { + font-weight: 700; + font-size: 1.1rem; +} +.demo-header .subtitle { + color: var(--vp-c-text-2); + font-size: 0.9rem; +} .tree-selector { margin-bottom: 2rem; diff --git a/docs/zh-cn/appendix/1-computer-fundamentals/transistor-to-cpu.md b/docs/zh-cn/appendix/1-computer-fundamentals/transistor-to-cpu.md index d56d6d2..3faacae 100644 --- a/docs/zh-cn/appendix/1-computer-fundamentals/transistor-to-cpu.md +++ b/docs/zh-cn/appendix/1-computer-fundamentals/transistor-to-cpu.md @@ -126,6 +126,10 @@ 因此,只要把一个 XOR 门和一个 AND 门组合起来,我们就得到了能计算一位数加法的电路,这也是最基础的**半加器(Half Adder)**。 + + +再进一步:我们可以把多个半加器和全加器级联组合起来,完成多个位数的加法: + ::: tip 核心解析:分解加法器 diff --git a/docs/zh-cn/appendix/4-server-and-backend/api-intro.md b/docs/zh-cn/appendix/4-server-and-backend/api-intro.md index cf6e73a..276ee80 100644 --- a/docs/zh-cn/appendix/4-server-and-backend/api-intro.md +++ b/docs/zh-cn/appendix/4-server-and-backend/api-intro.md @@ -1,60 +1,66 @@ # API 入门 + +::: tip 🎯 学习目标 +阅读完本节后,你将能够: +- 理解 API 的本质概念和设计哲学 +- 区分不同类型的 API(函数 API、操作系统 API、Web API) +- 掌握 HTTP 方法的语义和使用场景 +- 学会阅读和使用 API 文档 +- 理解 HTTP 调用与 SDK 调用的区别 +::: + +--- + +## 1. 从一个按钮开始 + -👆 看见了吗?点一下按钮,230 毫秒后,一句格言就回来了。 +👆 看见了吗?点击按钮,几百毫秒后,服务器返回了当前时间。 -这个过程,就是 **API 调用**。 +这个过程,就是一次完整的 **API 调用**。虽然简单,但它包含了 API 交互的所有核心要素: + +| 阶段 | 发生了什么 | +|------|-----------| +| **请求** | 客户端向服务器发送 "给我当前时间" 的请求 | +| **处理** | 服务器接收请求,查询系统时间 | +| **响应** | 服务器将时间数据返回给客户端 | + +这就像你去便利店买水——你说"来瓶可乐",店员给你一瓶可乐。你问一句,他给一笔。 + +**API(Application Programming Interface,应用程序编程接口)**,本质上就是一种"对话约定":一方提出请求,另一方给出响应。 --- -## 说白了,API 就是"问一句,拿一笔" +## 2. API 的三种形态 -你可能觉得"调用 API"是很高大上的操作。 +很多人一提到 API,就觉得这是很高深的东西。其实,你写代码的第一天就在用 API 了。 -其实吧,就像你去便利店买水——你说"来瓶可乐",店员给你一瓶可乐。你问一句,他给一笔。 - -API 也是一样: - -1. **你问**(发送请求) -2. **别人答**(服务器处理) -3. **你拿到**(返回结果) - -只不过这次,你问的不是店员,而是一台远在千里之外的电脑。 - ---- - -## 但 API 远不止"网络接口" - -一提到 API,很多人觉得这是很高深的东西,离自己很远。 - -其实吧,你写代码的第一天就在用 API 了,只是你不知道而已。 - -### 函数,就是最基础的 API +### 2.1 函数 API:最基础的形态 -```python -length = len("hello") -``` +当你调用 `len("hello")` 时,你就在使用 Python 提供的一个 API。你不需要知道 `len()` 内部是怎么数字符串长度的——是用 C 实现的还是用 Python 实现的?是遍历计数还是直接读取长度字段?这些细节都被隐藏了。 -`len()` 这个东西,你觉得它是什么? +**API 的核心价值:把复杂的东西藏起来,只给你一个简单的用法。** -它是个函数,没错。但同时,它也是 Python 给你留的一个 API。 - -什么意思呢?你不需要知道 `len()` 内部是怎么数字符串长度的,你不需要知道它是用 C 写的还是用 Python 写的,你只需要知道一件事——**把字符串给我,我告诉你多长**。 - -这就叫 API:**我把复杂的东西藏起来,只给你一个简单的用法**。 - -再看一个: +再看一个更贴近实际的例子: ```python -my_list = [] -my_list.append("item") +# 你写这行代码 +response = requests.get("https://api.example.com/users") + +# 背后发生了什么? +# 1. DNS 解析:把域名转成 IP 地址 +# 2. TCP 连接:建立网络通道 +# 3. TLS 握手:加密通信 +# 4. HTTP 请求:发送数据包 +# 5. 等待响应:接收服务器返回 +# 6. 解析数据:把字节流转成 Python 对象 ``` -`append()` 也是 API。背后可能是内存分配、指针移动、容量扩容...但你不用关心这些。你只需要说"给我加上去",它就帮你搞定。 +如果你要自己处理这 6 步,每次请求都要写几百行代码。但 `requests.get()` 把这些都封装好了,你只需要一行代码。 -### 操作系统 API:让你的程序能"碰"硬件 +### 2.2 操作系统 API:让程序能"碰"硬件 当你在电脑上打开一个文件: @@ -63,27 +69,17 @@ with open("file.txt", "r") as f: content = f.read() ``` -这行代码背后,Python 其实在调用**操作系统的 API**。 +这行代码背后,Python 调用了**操作系统的 API**。 -Windows 有 Win32 API,Linux 有 POSIX API,macOS 有 Cocoa API。这些 API 是干嘛的?让程序能真正操作硬件——读写硬盘、显示窗口、播放声音。 +| 操作系统 | API 名称 | 作用 | +|---------|---------|------| +| Windows | Win32 API | 文件操作、窗口管理、进程控制 | +| Linux | POSIX API | 系统调用、进程通信、设备访问 | +| macOS | Cocoa API | 图形界面、文件系统、网络 | -没有操作系统 API,你的 Python 代码就是一堆文字,根本动不了硬盘里的文件。 +没有操作系统 API,你的 Python 代码就是一堆文字,根本动不了硬盘里的文件、显示不了窗口、连不上网络。 -### 第三方库的 API:站在巨人的肩膀上 - -当你用 NumPy 做矩阵运算: - -```python -import numpy as np -matrix = np.array([[1, 2], [3, 4]]) -result = np.dot(matrix, matrix) -``` - -`np.dot()` 就是 NumPy 给你留的 API。背后可能是经过优化的 C++ 代码、多线程计算、SIMD 指令...但你只需要知道一件事——**给我两个矩阵,我还你一个乘积**。 - -这就是 API 的魅力:**把别人的能力,变成你的能力**。 - -### Web API:跨越网络的"超能力" +### 2.3 Web API:跨越网络的"超能力" 最后,才是大多数人熟知的 Web API: @@ -93,136 +89,180 @@ import requests response = requests.post( "https://api.deepseek.com/v1/chat/completions", headers={"Authorization": "Bearer sk-xxx"}, - json={"model": "deepseek-chat", "messages": [{"role": "user", "content": "你好"}]} + json={ + "model": "deepseek-chat", + "messages": [{"role": "user", "content": "你好"}] + } ) ``` -这行代码做了什么? +这行代码做了什么?它让你的程序穿越互联网,调用了千里之外 DeepSeek 服务器上的 AI 模型。就像你打了个越洋电话,让大洋彼岸的厨师帮你做了一道菜。 -它让你的程序穿越互联网,调用了千里之外 DeepSeek 服务器上的 AI 模型。就像你打了个越洋电话,让大洋彼岸的厨师帮你做了一道菜。 - -**Web API 的神奇之处就在于:它让"调用别人的超级电脑"变得像调用本地函数一样简单。** +**Web API 的神奇之处:让"调用别人的超级电脑"变得像调用本地函数一样简单。** --- -## 无论哪种 API,结构都一样 +## 3. API 的统一结构 -说了这么多,你可能发现了——不管哪种 API,它们的结构都是一样的。 - -就像插头和插座,怎么变都离不开三样东西: - -| 要素 | 函数 API 的例子 | Web API 的例子 | -|------|----------------|---------------| -| **地址/名称** | `len()` | `https://api.example.com/users` | -| **输入/参数** | `"hello"` | `{"name": "张三"}` | -| **输出/返回** | `5` | `{"id": 1}` | +无论哪种 API,结构都一样。就像插头和插座,怎么变都离不开三样东西: +| 要素 | 函数 API | Web API | +|------|---------|---------| +| **地址/名称** | `len()` | `https://api.example.com/users` | +| **输入/参数** | `"hello"` | `{"name": "张三"}` | +| **输出/返回** | `5` | `{"id": 1, "name": "张三"}` | + +理解了这个统一结构,你就掌握了 API 的本质。无论是调用一个 Python 函数,还是请求一个远程服务器,思路都是一样的: + +1. **找到入口**(函数名或 URL) +2. **传入参数**(按要求的格式) +3. **处理返回**(按约定的结构解析) + --- -## 调用服务器:你是在"问"还是在"做"? +## 4. HTTP 方法:你是在"问"还是在"做"? -好,现在你知道调用 API 需要地址和参数。 - -但还有个问题没说:**你跟服务器说话的方式,不止一种。** - -什么意思? +调用 Web API 时,你需要告诉服务器你想做什么。这就是 HTTP 方法的由来。 想象你去一家餐厅: -| 场景 | 现实中你会怎么说? | 对应的 API 方式 | +| 场景 | 现实中你会怎么说? | 对应的 HTTP 方法 | |------|-------------------|-----------------| | 你想知道今天有什么菜 | "服务员,菜单给我看看" | **GET** - 纯"问",不改数据 | -| 你想点一份宫保鸡丁 | "给我来份宫保鸡丁" | **POST** - "做"件事 | +| 你想点一份宫保鸡丁 | "给我来份宫保鸡丁" | **POST** - "做"件事,创建数据 | | 你想换一道菜 | "把宫保鸡丁改成糖醋里脊" | **PUT** - 替换数据 | +| 你想改口味 | "宫保鸡丁不要放花生" | **PATCH** - 部分修改 | | 你不想要了 | "算了,那道菜不要了" | **DELETE** - 删除数据 | -这就是 HTTP 方法的来历:**不同的动词,对应不同的操作。** - -但最常用的就两个:**GET 和 POST**。其他的先不用管。 - +::: warning 关于幂等性 +**幂等性**是一个重要概念:多次执行结果是否相同? + +- **GET**:查询 10 次和查询 1 次,结果一样 → 幂等 +- **DELETE**:删除 10 次和删除 1 次,结果一样 → 幂等 +- **POST**:下单 10 次,可能创建 10 个订单 → 不幂等 + +实际开发中,POST 操作通常需要用唯一 ID 来防止重复处理。 +::: + --- -## HTTP vs SDK:自己跑腿还是让管家代办? +## 5. HTTP vs SDK:自己跑腿还是让管家代办? -既然教程的重点是 AI 编程,我们就重点讲讲 Web API——这是你和 AI 模型打交道的主要方式。 - -你在教程里经常会看到两种调用方式:**HTTP** 和 **SDK**。很多人会被绕晕,其实很简单,就是**"自己跑腿"**和**"让管家代办"**的区别。 - -### HTTP API:自己跑腿 - -这是最原始的方式。就像你自己去餐厅,从头开始点菜。 - -所有编程语言都能用,甚至你在浏览器地址栏里敲一行字也是一种 HTTP 请求。 - -### SDK:让管家代办 - -SDK (Software Development Kit) 就像是餐厅派给你的专属管家。 - -你不需要自己填单子、贴邮票。你只需要跟管家说"来份宫保鸡丁",管家会自己在后台帮你填单子、发请求、处理报错。 +在 AI 编程教程中,你会经常看到两种调用方式:**HTTP** 和 **SDK**。它们的区别就像"自己跑腿"和"让管家代办"。 -> 能用 SDK 就用 SDK,把麻烦事留给别人,把时间留给自己。 +### 5.1 HTTP API:自己跑腿 -## 怎么看 API 文档? +这是最原始的方式。你需要: -文档就像说明书和菜单的结合体。你不需要从头读到尾,只需要学会查字典。 +1. **找到网址**(Base URL + Endpoint) +2. **准备请求头**(Authorization、Content-Type) +3. **构造请求体**(JSON 格式的参数) +4. **发送请求**(处理网络错误、超时) +5. **解析响应**(把 JSON 转成可用的数据) + +所有编程语言都能用,但你需要处理很多细节。 + +### 5.2 SDK:让管家代办 + +SDK(Software Development Kit)就像是 API 提供方派给你的专属管家: + +```python +# HTTP 方式:自己处理所有细节 +import requests +response = requests.post( + "https://api.deepseek.com/v1/chat/completions", + headers={"Authorization": "Bearer sk-xxx"}, + json={"model": "deepseek-chat", "messages": [...]} +) +result = response.json()["choices"][0]["message"]["content"] + +# SDK 方式:管家帮你处理 +from openai import OpenAI +client = OpenAI(api_key="sk-xxx") +response = client.chat.completions.create( + model="deepseek-chat", + messages=[...] +) +result = response.choices[0].message.content +``` + +SDK 自动处理了:鉴权、请求格式、错误处理、重试逻辑、响应解析。 + +::: tip 建议 +**能用 SDK 就用 SDK**,把麻烦事留给库,把时间留给自己。 +::: + +--- + +## 6. 如何阅读 API 文档? + +API 文档就像说明书和菜单的结合体。你不需要从头读到尾,只需要学会"查字典"。 打开任何一个 API 文档(比如 OpenAI 或 DeepSeek),你只需要找这几样东西: -1. **Base URL**:根地址(餐厅在哪?) -2. **Authentication**:怎么证明你是会员?(通常是 `Authorization: Bearer sk-...`) -3. **Endpoints**:具体的接口列表 - - `/v1/chat/completions` -> 对话(最常用的) - - `/v1/images/generations` -> 画图 -4. **Parameters**:必填项有哪些? - -### 常见的"餐厅黑话"(状态码) +### 6.1 文档阅读清单 -服务员(API)回复你的时候,通常会先喊一个数字代码: +| 项目 | 说明 | 示例 | +|------|------|------| +| **Base URL** | API 的根地址 | `https://api.deepseek.com` | +| **Authentication** | 如何证明身份 | `Authorization: Bearer sk-xxx` | +| **Endpoints** | 具体的接口列表 | `/v1/chat/completions` | +| **Parameters** | 必填/可选参数 | `model`(必填)、`temperature`(可选) | +| **Response** | 返回数据结构 | `{"choices": [...]}` | -| 状态码 | 含义 | -|--------|------| -| **200 OK** | 成功了 | -| **400 Bad Request** | 你填错了 | -| **401 Unauthorized** | 没权限 | -| **404 Not Found** | 地址错了 | -| **429 Too Many Requests** | 你点太快了 | -| **500 Internal Server Error** | 对方服务器崩了 | +### 6.2 常见状态码 + +服务器回复时,会先返回一个状态码,告诉你请求是否成功: + +| 状态码 | 含义 | 常见原因 | +|--------|------|---------| +| **200 OK** | 成功 | 请求正常处理 | +| **201 Created** | 创建成功 | POST 请求成功创建资源 | +| **400 Bad Request** | 请求格式错误 | 参数缺失或格式不对 | +| **401 Unauthorized** | 未认证 | 没有提供有效的 API Key | +| **403 Forbidden** | 无权限 | API Key 没有访问该资源的权限 | +| **404 Not Found** | 不存在 | 请求的地址或资源不存在 | +| **429 Too Many Requests** | 请求过多 | 超过了速率限制 | +| **500 Internal Server Error** | 服务器错误 | 服务端出了问题 | --- -## 练手场:弄坏它也没关系 +## 7. 动手练习 光说不练假把式。这里有个模拟 API,你可以随便填参数、随便改地址,看看会发生什么。 -试着触发一下 401(假装没带钱)或者 404(瞎填地址)。 - +试着触发以下场景: +- ✅ **成功请求**:填入正确的 Endpoint 和 API Key +- ❌ **401 错误**:不填 API Key,看看服务器怎么拒绝你 +- ❌ **404 错误**:填一个不存在的地址 + --- -## 总结 +## 8. 小结 -别把 API 想得太复杂。在 AI 编程的时代,你只需要记住这几件事: - -1. **API 就是传声筒**,帮你把话传给 AI 模型 -2. **你早就用过 API**了,从 `len()` 到 `open()` +::: info 核心要点 +1. **API 就是传声筒**,帮你把话传给另一段代码或远程服务器 +2. **你早就用过 API 了**,从 `len()` 到 `open()` 都是 API 3. **Web API 是超能力**,让你调用千里之外的超级电脑 -4. **SDK 是好管家**,能用管家就别自己跑腿 -5. **看文档找三样**:地址、密钥、参数 +4. **SDK 是好管家**,能用 SDK 就别自己跑腿 +5. **看文档找三样**:地址、鉴权、参数 +::: -这就够了。剩下的,交给 IDE 去写吧。 +在 AI 编程的时代,你只需要记住这几个核心概念。剩下的细节,IDE 和 AI 助手会帮你处理。 --- -## 名词速查表 (Glossary) +## 名词速查表 | 名词 | 全称 | 解释 | |------|------|------| @@ -242,3 +282,4 @@ SDK (Software Development Kit) 就像是餐厅派给你的专属管家。 | **Header** | - | HTTP 头,包含元信息 | | **Payload** | - | 请求或响应的实际数据 | | **Rate Limit** | - | 速率限制 | +| **Idempotent** | - | 幂等,多次执行结果相同 |