[{"data":1,"prerenderedAt":1560},["ShallowReactive",2],{"articles":3},[4,948,982,1179,1406],{"id":5,"title":6,"body":7,"date":933,"description":934,"extension":935,"featured":936,"meta":937,"navigation":938,"path":939,"seo":940,"stem":941,"tags":942,"tip":946,"__hash__":947},"articles/articles/gpt-teachers-workspace-exploit.md","无限爽毛K12?GPT For Teachers工作区漏洞的小记",{"type":8,"value":9,"toc":920},"minimark",[10,14,18,21,33,46,49,52,57,64,175,194,205,209,212,447,450,476,480,483,670,683,688,691,706,711,722,726,729,751,754,815,818,821,829,832,863,866,869,880,883,902,905,916],[11,12,13],"h2",{"id":13},"背景",[15,16,17],"p",{},"最近网上冲浪的时候发现了一个强行加入 K12 空间的脚本。因好奇其实现原理，遂进行分析，发现它利用了 ChatGPT 平台的一系列安全缺陷来实现「无限K12」的效果。",[11,19,20],{"id":20},"脚本概述",[15,22,23,24,28,29,32],{},"该脚本是一个浏览器 Userscript（通过 Tampermonkey 等扩展注入），运行在 ",[25,26,27],"code",{},"https://chatgpt.com/*"," 页面上（",[25,30,31],{},"@run-at document-idle","）。安装后会自动在页面右上角注入一个浮层面板，提供以下功能：",[34,35,36,40,43],"ul",{},[37,38,39],"li",{},"自动获取当前登录账号的 Session Token（AT）",[37,41,42],{},"向指定的多个 Workspace 发送「加入申请」（request）",[37,44,45],{},"自动接受已发出的邀请（accept）",[15,47,48],{},"默认配置中硬编码了十几个 Workspace ID，批量操作为母号（管理员）和子号（被加入的账号）之间建立关联。",[11,50,51],{"id":51},"核心漏洞分析",[53,54,56],"h3",{"id":55},"_1-ataccess-token窃取","1. AT（Access Token）窃取",[15,58,59,60,63],{},"脚本的核心入口是 ",[25,61,62],{},"fetchSession()"," 函数：",[65,66,71],"pre",{"className":67,"code":68,"language":69,"meta":70,"style":70},"language-javascript shiki shiki-themes github-light github-dark","async function fetchSession() {\n  const res = await fetch(\"/api/auth/session\", {\n    headers: { accept: \"*/*\" },\n    credentials: \"include\",\n  });\n  return await res.json();\n}\n","javascript","",[25,72,73,93,122,134,146,152,169],{"__ignoreMap":70},[74,75,78,82,85,89],"span",{"class":76,"line":77},"line",1,[74,79,81],{"class":80},"szBVR","async",[74,83,84],{"class":80}," function",[74,86,88],{"class":87},"sScJk"," fetchSession",[74,90,92],{"class":91},"sVt8B","() {\n",[74,94,96,99,103,106,109,112,115,119],{"class":76,"line":95},2,[74,97,98],{"class":80},"  const",[74,100,102],{"class":101},"sj4cs"," res",[74,104,105],{"class":80}," =",[74,107,108],{"class":80}," await",[74,110,111],{"class":87}," fetch",[74,113,114],{"class":91},"(",[74,116,118],{"class":117},"sZZnC","\"/api/auth/session\"",[74,120,121],{"class":91},", {\n",[74,123,125,128,131],{"class":76,"line":124},3,[74,126,127],{"class":91},"    headers: { accept: ",[74,129,130],{"class":117},"\"*/*\"",[74,132,133],{"class":91}," },\n",[74,135,137,140,143],{"class":76,"line":136},4,[74,138,139],{"class":91},"    credentials: ",[74,141,142],{"class":117},"\"include\"",[74,144,145],{"class":91},",\n",[74,147,149],{"class":76,"line":148},5,[74,150,151],{"class":91},"  });\n",[74,153,155,158,160,163,166],{"class":76,"line":154},6,[74,156,157],{"class":80},"  return",[74,159,108],{"class":80},[74,161,162],{"class":91}," res.",[74,164,165],{"class":87},"json",[74,167,168],{"class":91},"();\n",[74,170,172],{"class":76,"line":171},7,[74,173,174],{"class":91},"}\n",[15,176,177,178,181,182,185,186,189,190,193],{},"这里利用了 ChatGPT 的 ",[25,179,180],{},"/api/auth/session"," 端点——该接口返回当前用户的完整会话信息，包括 ",[25,183,184],{},"accessToken","（AT）。由于浏览器 Same-Origin Policy 允许同源页面发起带 ",[25,187,188],{},"credentials: \"include\""," 的请求，任何注入到 ",[25,191,192],{},"chatgpt.com"," 的 JavaScript 代码都可以无感知地获取到当前登录用户的 Token。",[15,195,196,200,201,204],{},[197,198,199],"strong",{},"漏洞本质："," 服务端没有在 Token 传输层做足够的保护（如 ",[25,202,203],{},"httpOnly"," Cookie 绑定、Token 指纹绑定等），使得前端 JS 可以直接读取到高权限的 Access Token。",[53,206,208],{"id":207},"_2-jwt-结构解析","2. JWT 结构解析",[15,210,211],{},"获取到 AT 后，脚本进一步解析 JWT 载荷：",[65,213,215],{"className":67,"code":214,"language":69,"meta":70,"style":70},"function decodeJwt(at) {\n  const payload = at.split(\".\")[1];\n  const json = JSON.parse(atob(payload.replace(/-/g, \"+\").replace(/_/g, \"/\")));\n  const auth = json[\"https://api.openai.com/auth\"] || {};\n  const prof = json[\"https://api.openai.com/profile\"] || {};\n  return {\n    account_id: auth.chatgpt_account_id || \"\",\n    email: prof.email || \"\",\n    plan_type: auth.chatgpt_plan_type || \"\",\n    exp: json.exp || 0,\n  };\n}\n",[25,216,217,234,263,336,360,380,387,399,411,423,436,442],{"__ignoreMap":70},[74,218,219,222,225,227,231],{"class":76,"line":77},[74,220,221],{"class":80},"function",[74,223,224],{"class":87}," decodeJwt",[74,226,114],{"class":91},[74,228,230],{"class":229},"s4XuR","at",[74,232,233],{"class":91},") {\n",[74,235,236,238,241,243,246,249,251,254,257,260],{"class":76,"line":95},[74,237,98],{"class":80},[74,239,240],{"class":101}," payload",[74,242,105],{"class":80},[74,244,245],{"class":91}," at.",[74,247,248],{"class":87},"split",[74,250,114],{"class":91},[74,252,253],{"class":117},"\".\"",[74,255,256],{"class":91},")[",[74,258,259],{"class":101},"1",[74,261,262],{"class":91},"];\n",[74,264,265,267,270,272,275,278,281,283,286,289,292,294,297,301,303,306,309,312,315,317,319,321,324,326,328,330,333],{"class":76,"line":124},[74,266,98],{"class":80},[74,268,269],{"class":101}," json",[74,271,105],{"class":80},[74,273,274],{"class":101}," JSON",[74,276,277],{"class":91},".",[74,279,280],{"class":87},"parse",[74,282,114],{"class":91},[74,284,285],{"class":87},"atob",[74,287,288],{"class":91},"(payload.",[74,290,291],{"class":87},"replace",[74,293,114],{"class":91},[74,295,296],{"class":117},"/",[74,298,300],{"class":299},"sA_wV","-",[74,302,296],{"class":117},[74,304,305],{"class":80},"g",[74,307,308],{"class":91},", ",[74,310,311],{"class":117},"\"+\"",[74,313,314],{"class":91},").",[74,316,291],{"class":87},[74,318,114],{"class":91},[74,320,296],{"class":117},[74,322,323],{"class":299},"_",[74,325,296],{"class":117},[74,327,305],{"class":80},[74,329,308],{"class":91},[74,331,332],{"class":117},"\"/\"",[74,334,335],{"class":91},")));\n",[74,337,338,340,343,345,348,351,354,357],{"class":76,"line":136},[74,339,98],{"class":80},[74,341,342],{"class":101}," auth",[74,344,105],{"class":80},[74,346,347],{"class":91}," json[",[74,349,350],{"class":117},"\"https://api.openai.com/auth\"",[74,352,353],{"class":91},"] ",[74,355,356],{"class":80},"||",[74,358,359],{"class":91}," {};\n",[74,361,362,364,367,369,371,374,376,378],{"class":76,"line":148},[74,363,98],{"class":80},[74,365,366],{"class":101}," prof",[74,368,105],{"class":80},[74,370,347],{"class":91},[74,372,373],{"class":117},"\"https://api.openai.com/profile\"",[74,375,353],{"class":91},[74,377,356],{"class":80},[74,379,359],{"class":91},[74,381,382,384],{"class":76,"line":154},[74,383,157],{"class":80},[74,385,386],{"class":91}," {\n",[74,388,389,392,394,397],{"class":76,"line":171},[74,390,391],{"class":91},"    account_id: auth.chatgpt_account_id ",[74,393,356],{"class":80},[74,395,396],{"class":117}," \"\"",[74,398,145],{"class":91},[74,400,402,405,407,409],{"class":76,"line":401},8,[74,403,404],{"class":91},"    email: prof.email ",[74,406,356],{"class":80},[74,408,396],{"class":117},[74,410,145],{"class":91},[74,412,414,417,419,421],{"class":76,"line":413},9,[74,415,416],{"class":91},"    plan_type: auth.chatgpt_plan_type ",[74,418,356],{"class":80},[74,420,396],{"class":117},[74,422,145],{"class":91},[74,424,426,429,431,434],{"class":76,"line":425},10,[74,427,428],{"class":91},"    exp: json.exp ",[74,430,356],{"class":80},[74,432,433],{"class":101}," 0",[74,435,145],{"class":91},[74,437,439],{"class":76,"line":438},11,[74,440,441],{"class":91},"  };\n",[74,443,445],{"class":76,"line":444},12,[74,446,174],{"class":91},[15,448,449],{},"这说明 AT 是一个标准的 JWT，载荷中包含：",[34,451,452,458,464,470],{},[37,453,454,457],{},[25,455,456],{},"https://api.openai.com/auth.chatgpt_account_id","：账号唯一标识",[37,459,460,463],{},[25,461,462],{},"https://api.openai.com/profile.email","：用户邮箱",[37,465,466,469],{},[25,467,468],{},"chatgpt_plan_type","：订阅类型",[37,471,472,475],{},[25,473,474],{},"exp","：过期时间",[53,477,479],{"id":478},"_3-越权加入工作区","3. 越权加入工作区",[15,481,482],{},"有了 AT 后，脚本直接调用 Workspace 管理 API：",[65,484,486],{"className":67,"code":485,"language":69,"meta":70,"style":70},"async function sendOne(wsId, route, attempt = 0) {\n  const url = `/backend-api/accounts/${wsId}/invites/${route}`;\n  const headers = {\n    authorization: `Bearer ${STATE.at}`,\n    \"oai-device-id\": STATE.deviceId,\n    \"oai-language\": navigator.language || \"en-US\",\n  };\n  const res = await fetch(url, {\n    method: \"POST\",\n    headers,\n    body: null,\n    credentials: \"include\",\n  });\n  // ...\n}\n",[25,487,488,518,543,554,573,586,601,605,620,630,635,645,653,658,665],{"__ignoreMap":70},[74,489,490,492,494,497,499,502,504,507,509,512,514,516],{"class":76,"line":77},[74,491,81],{"class":80},[74,493,84],{"class":80},[74,495,496],{"class":87}," sendOne",[74,498,114],{"class":91},[74,500,501],{"class":229},"wsId",[74,503,308],{"class":91},[74,505,506],{"class":229},"route",[74,508,308],{"class":91},[74,510,511],{"class":229},"attempt",[74,513,105],{"class":80},[74,515,433],{"class":101},[74,517,233],{"class":91},[74,519,520,522,525,527,530,532,535,537,540],{"class":76,"line":95},[74,521,98],{"class":80},[74,523,524],{"class":101}," url",[74,526,105],{"class":80},[74,528,529],{"class":117}," `/backend-api/accounts/${",[74,531,501],{"class":91},[74,533,534],{"class":117},"}/invites/${",[74,536,506],{"class":91},[74,538,539],{"class":117},"}`",[74,541,542],{"class":91},";\n",[74,544,545,547,550,552],{"class":76,"line":124},[74,546,98],{"class":80},[74,548,549],{"class":101}," headers",[74,551,105],{"class":80},[74,553,386],{"class":91},[74,555,556,559,562,565,567,569,571],{"class":76,"line":136},[74,557,558],{"class":91},"    authorization: ",[74,560,561],{"class":117},"`Bearer ${",[74,563,564],{"class":101},"STATE",[74,566,277],{"class":117},[74,568,230],{"class":91},[74,570,539],{"class":117},[74,572,145],{"class":91},[74,574,575,578,581,583],{"class":76,"line":148},[74,576,577],{"class":117},"    \"oai-device-id\"",[74,579,580],{"class":91},": ",[74,582,564],{"class":101},[74,584,585],{"class":91},".deviceId,\n",[74,587,588,591,594,596,599],{"class":76,"line":154},[74,589,590],{"class":117},"    \"oai-language\"",[74,592,593],{"class":91},": navigator.language ",[74,595,356],{"class":80},[74,597,598],{"class":117}," \"en-US\"",[74,600,145],{"class":91},[74,602,603],{"class":76,"line":171},[74,604,441],{"class":91},[74,606,607,609,611,613,615,617],{"class":76,"line":401},[74,608,98],{"class":80},[74,610,102],{"class":101},[74,612,105],{"class":80},[74,614,108],{"class":80},[74,616,111],{"class":87},[74,618,619],{"class":91},"(url, {\n",[74,621,622,625,628],{"class":76,"line":413},[74,623,624],{"class":91},"    method: ",[74,626,627],{"class":117},"\"POST\"",[74,629,145],{"class":91},[74,631,632],{"class":76,"line":425},[74,633,634],{"class":91},"    headers,\n",[74,636,637,640,643],{"class":76,"line":438},[74,638,639],{"class":91},"    body: ",[74,641,642],{"class":101},"null",[74,644,145],{"class":91},[74,646,647,649,651],{"class":76,"line":444},[74,648,139],{"class":91},[74,650,142],{"class":117},[74,652,145],{"class":91},[74,654,656],{"class":76,"line":655},13,[74,657,151],{"class":91},[74,659,661],{"class":76,"line":660},14,[74,662,664],{"class":663},"sJ8bj","  // ...\n",[74,666,668],{"class":76,"line":667},15,[74,669,174],{"class":91},[15,671,672,674,675,678,679,682],{},[25,673,506],{}," 参数可以是 ",[25,676,677],{},"\"request\"","（申请加入）或 ",[25,680,681],{},"\"accept\"","（接受邀请）。这里的关键问题在于：",[15,684,685],{},[197,686,687],{},"漏洞一：AT 直接用于跨账号操作",[15,689,690],{},"Bearer Token 直接作为身份凭证，服务端只验证 Token 本身的有效性（是否过期、签名是否正确），而没有校验操作意图的合法性。这意味着：",[34,692,693,696,703],{},[37,694,695],{},"子号的 AT 可以用于向任意 Workspace 发起加入请求",[37,697,698,699,702],{},"母号的 AT 可以通过 ",[25,700,701],{},"accept"," 接口直接接受邀请，无需二次确认",[37,704,705],{},"整个流程没有任何 CSRF Token、确认步骤或人机验证",[15,707,708],{},[197,709,710],{},"漏洞二：无速率限制与设备指纹绕过",[15,712,713,714,717,718,721],{},"脚本使用了 ",[25,715,716],{},"crypto.randomUUID()"," 生成固定的 ",[25,719,720],{},"oai-device-id","，并在请求头中发送。这种客户端生成的设备标识符显然无法作为有效的风控依据，攻击者可以随意伪造。",[53,723,725],{"id":724},"_4-token-自动刷新机制","4. Token 自动刷新机制",[15,727,728],{},"脚本还实现了自动刷新机制，保证 AT 持续有效：",[65,730,732],{"className":67,"code":731,"language":69,"meta":70,"style":70},"setInterval(refreshSession, CONFIG.sessionPollMs); // 每20秒刷新一次\n",[25,733,734],{"__ignoreMap":70},[74,735,736,739,742,745,748],{"class":76,"line":77},[74,737,738],{"class":87},"setInterval",[74,740,741],{"class":91},"(refreshSession, ",[74,743,744],{"class":101},"CONFIG",[74,746,747],{"class":91},".sessionPollMs); ",[74,749,750],{"class":663},"// 每20秒刷新一次\n",[15,752,753],{},"当检测到 401/403 响应时，脚本会主动清空 AT 并重新获取：",[65,755,757],{"className":67,"code":756,"language":69,"meta":70,"style":70},"if (res.status === 401 || res.status === 403) {\n  STATE.at = \"\";\n  await refreshSession();\n}\n",[25,758,759,786,801,811],{"__ignoreMap":70},[74,760,761,764,767,770,773,776,779,781,784],{"class":76,"line":77},[74,762,763],{"class":80},"if",[74,765,766],{"class":91}," (res.status ",[74,768,769],{"class":80},"===",[74,771,772],{"class":101}," 401",[74,774,775],{"class":80}," ||",[74,777,778],{"class":91}," res.status ",[74,780,769],{"class":80},[74,782,783],{"class":101}," 403",[74,785,233],{"class":91},[74,787,788,791,794,797,799],{"class":76,"line":95},[74,789,790],{"class":101},"  STATE",[74,792,793],{"class":91},".at ",[74,795,796],{"class":80},"=",[74,798,396],{"class":117},[74,800,542],{"class":91},[74,802,803,806,809],{"class":76,"line":124},[74,804,805],{"class":80},"  await",[74,807,808],{"class":87}," refreshSession",[74,810,168],{"class":91},[74,812,813],{"class":76,"line":136},[74,814,174],{"class":91},[15,816,817],{},"这意味着即使原始 Token 过期或被吊销，只要用户保持登录状态，脚本就能持续获取新的有效 Token 来维持攻击。",[11,819,820],{"id":820},"攻击链总结",[65,822,827],{"className":823,"code":825,"language":826},[824],"language-text","用户在 chatgpt.com 登录\n       ↓\nTampermonkey 注入 Userscript\n       ↓\nfetch(\"/api/auth/session\") → 窃取 AT\n       ↓\nJWT 解析 → 提取 account_id、email、plan_type\n       ↓\nPOST /backend-api/accounts/{workspaceId}/invites/request\n       ↓\n向多个目标 Workspace 批量发送加入申请\n       ↓\nPOST /backend-api/accounts/{workspaceId}/invites/accept\n       ↓\n自动接受邀请，子号进入母号的工作区\n       ↓\n每 20 秒刷新 Session，维持持久访问\n","text",[25,828,825],{"__ignoreMap":70},[11,830,831],{"id":831},"影响与危害",[833,834,835,841,847,853],"ol",{},[37,836,837,840],{},[197,838,839],{},"账号劫持风险","：任何运行该脚本的页面都可以窃取用户的 AT，用于冒充用户身份执行任意 API 操作",[37,842,843,846],{},[197,844,845],{},"工作区权限滥用","：子号可以在不知情的情况下被批量加入工作区，母号可以无审核地扩张团队规模",[37,848,849,852],{},[197,850,851],{},"批量注册/滥用","：结合自动化注册工具，可以低成本地批量创建账号并加入工作区，绕过付费限制",[37,854,855,858,859,862],{},[197,856,857],{},"供应链攻击面","：Tampermonkey 脚本的 ",[25,860,861],{},"@grant none"," 模式意味着脚本运行在页面主上下文中，拥有与页面 JS 同等的权限",[11,864,865],{"id":865},"修复建议",[15,867,868],{},"从空间管理者角度：",[34,870,871,874,877],{},[37,872,873],{},"定期审查空间成员列表，移除不认识或不活跃的账号",[37,875,876],{},"废弃被泄漏的空间",[37,878,879],{},"关闭不需要的 Codex 权限",[15,881,882],{},"从平台角度：",[34,884,885,893,896,899],{},[37,886,887,889,890,892],{},[25,888,180],{}," 返回的 AT 应限制为短期有效，或改用 ",[25,891,203],{}," Cookie 方案",[37,894,895],{},"Workspace 操作 API 应增加人机验证和意图确认",[37,897,898],{},"引入设备指纹绑定，使窃取的 Token 在其他设备上无法使用",[37,900,901],{},"增加 API 速率限制和异常行为检测",[15,903,904],{},"从用户角度：",[34,906,907,910,913],{},[37,908,909],{},"不要安装来源不明的 Tampermonkey 脚本",[37,911,912],{},"定期检查 ChatGPT 账号的登录活动和工作区成员列表",[37,914,915],{},"使用浏览器容器隔离不同站点的登录状态",[917,918,919],"style",{},"html pre.shiki code .szBVR, html code.shiki .szBVR{--shiki-default:#D73A49;--shiki-dark:#F97583}html pre.shiki code .sScJk, html code.shiki .sScJk{--shiki-default:#6F42C1;--shiki-dark:#B392F0}html pre.shiki code .sVt8B, html code.shiki .sVt8B{--shiki-default:#24292E;--shiki-dark:#E1E4E8}html pre.shiki code .sj4cs, html code.shiki .sj4cs{--shiki-default:#005CC5;--shiki-dark:#79B8FF}html pre.shiki code .sZZnC, html code.shiki .sZZnC{--shiki-default:#032F62;--shiki-dark:#9ECBFF}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html pre.shiki code .s4XuR, html code.shiki .s4XuR{--shiki-default:#E36209;--shiki-dark:#FFAB70}html pre.shiki code .sA_wV, html code.shiki .sA_wV{--shiki-default:#032F62;--shiki-dark:#DBEDFF}html pre.shiki code .sJ8bj, html code.shiki .sJ8bj{--shiki-default:#6A737D;--shiki-dark:#6A737D}",{"title":70,"searchDepth":95,"depth":95,"links":921},[922,923,924,930,931,932],{"id":13,"depth":95,"text":13},{"id":20,"depth":95,"text":20},{"id":51,"depth":95,"text":51,"children":925},[926,927,928,929],{"id":55,"depth":124,"text":56},{"id":207,"depth":124,"text":208},{"id":478,"depth":124,"text":479},{"id":724,"depth":124,"text":725},{"id":820,"depth":95,"text":820},{"id":831,"depth":95,"text":831},{"id":865,"depth":95,"text":865},"2026-07-05","对某 ChatGPT Workspace 自动化加入脚本的核心漏洞利用逻辑进行技术分析——AT 窃取、越权加入工作区","md",false,{},true,"/articles/gpt-teachers-workspace-exploit",{"title":6,"description":934},"articles/gpt-teachers-workspace-exploit",[943,944,945],"安全","ChatGPT","漏洞分析","本文由 LongCat-2.0 生成。","rQx5gfR1-2MMc_e3wikxJjUWMH2EwuSVDmrZZZLv_-E",{"id":949,"title":950,"body":951,"date":971,"description":972,"extension":935,"featured":936,"meta":973,"navigation":938,"path":974,"seo":975,"stem":976,"tags":977,"tip":980,"__hash__":981},"articles/articles/aiceo-warning.md","被中转坑了70块钱气晕了",{"type":8,"value":952,"toc":969},[953,959,962],[15,954,955,956],{},"避雷这个垃圾中转站 ",[197,957,958],{},"aiceo.dev",[15,960,961],{},"警钟长鸣啊。我的70块钱 T T",[15,963,964],{},[965,966],"img",{"alt":967,"src":968},"截图","https://free.boltp.com/2026/06/30/6a42c263b9a0b.webp",{"title":70,"searchDepth":95,"depth":95,"links":970},[],"2026-06-30","避雷这个垃圾中转站 aiceo.dev，警钟长鸣啊。我的70块钱 T T",{},"/articles/aiceo-warning",{"title":950,"description":972},"articles/aiceo-warning",[978,979],"吐槽","避雷",null,"vQlFRIlWAptB4TBYCBEHTQLehGKrAk-7DcvCJpW3uFw",{"id":983,"title":984,"body":985,"date":1168,"description":1169,"extension":935,"featured":938,"meta":1170,"navigation":938,"path":1171,"seo":1172,"stem":1173,"tags":1174,"tip":980,"__hash__":1178},"articles/articles/getting-started.md","开始使用 Nuxt Content 构建博客",{"type":8,"value":986,"toc":1158},[987,991,994,998,1030,1033,1037,1055,1059,1066,1103,1107,1113,1149,1152,1155],[11,988,990],{"id":989},"什么是-nuxt-content","什么是 Nuxt Content？",[15,992,993],{},"Nuxt Content 是一个基于文件的内容管理模块，专为 Nuxt 设计。它让你可以直接使用 Markdown、JSON 或 YAML 文件来管理网站内容。",[11,995,997],{"id":996},"为什么选择-nuxt-content","为什么选择 Nuxt Content？",[34,999,1000,1006,1012,1018,1024],{},[37,1001,1002,1005],{},[197,1003,1004],{},"零数据库依赖","：内容以文件形式存储在 Git 仓库中",[37,1007,1008,1011],{},[197,1009,1010],{},"Markdown 原生支持","：写文章就像写 Markdown 一样简单",[37,1013,1014,1017],{},[197,1015,1016],{},"自动路由","：基于文件结构自动生成路由",[37,1019,1020,1023],{},[197,1021,1022],{},"全文搜索","：内置搜索功能",[37,1025,1026,1029],{},[197,1027,1028],{},"SEO 友好","：支持 SSR/SSG，搜索引擎优化",[11,1031,1032],{"id":1032},"快速开始",[53,1034,1036],{"id":1035},"_1-安装模块","1. 安装模块",[65,1038,1042],{"className":1039,"code":1040,"language":1041,"meta":70,"style":70},"language-bash shiki shiki-themes github-light github-dark","pnpm add @nuxt/content\n","bash",[25,1043,1044],{"__ignoreMap":70},[74,1045,1046,1049,1052],{"class":76,"line":77},[74,1047,1048],{"class":87},"pnpm",[74,1050,1051],{"class":117}," add",[74,1053,1054],{"class":117}," @nuxt/content\n",[53,1056,1058],{"id":1057},"_2-配置模块","2. 配置模块",[15,1060,1061,1062,1065],{},"在 ",[25,1063,1064],{},"nuxt.config.ts"," 中添加：",[65,1067,1071],{"className":1068,"code":1069,"language":1070,"meta":70,"style":70},"language-ts shiki shiki-themes github-light github-dark","export default defineNuxtConfig({\n  modules: ['@nuxt/content']\n})\n","ts",[25,1072,1073,1087,1098],{"__ignoreMap":70},[74,1074,1075,1078,1081,1084],{"class":76,"line":77},[74,1076,1077],{"class":80},"export",[74,1079,1080],{"class":80}," default",[74,1082,1083],{"class":87}," defineNuxtConfig",[74,1085,1086],{"class":91},"({\n",[74,1088,1089,1092,1095],{"class":76,"line":95},[74,1090,1091],{"class":91},"  modules: [",[74,1093,1094],{"class":117},"'@nuxt/content'",[74,1096,1097],{"class":91},"]\n",[74,1099,1100],{"class":76,"line":124},[74,1101,1102],{"class":91},"})\n",[53,1104,1106],{"id":1105},"_3-创建内容","3. 创建内容",[15,1108,1061,1109,1112],{},[25,1110,1111],{},"content/"," 目录下创建 Markdown 文件：",[65,1114,1118],{"className":1115,"code":1116,"language":1117,"meta":70,"style":70},"language-markdown shiki shiki-themes github-light github-dark","---\ntitle: \"我的第一篇文章\"\ndate: 2026-06-13\n---\n\n这是文章内容...\n","markdown",[25,1119,1120,1125,1130,1135,1139,1144],{"__ignoreMap":70},[74,1121,1122],{"class":76,"line":77},[74,1123,1124],{},"---\n",[74,1126,1127],{"class":76,"line":95},[74,1128,1129],{},"title: \"我的第一篇文章\"\n",[74,1131,1132],{"class":76,"line":124},[74,1133,1134],{},"date: 2026-06-13\n",[74,1136,1137],{"class":76,"line":136},[74,1138,1124],{},[74,1140,1141],{"class":76,"line":148},[74,1142,1143],{"emptyLinePlaceholder":938},"\n",[74,1145,1146],{"class":76,"line":154},[74,1147,1148],{},"这是文章内容...\n",[11,1150,1151],{"id":1151},"结语",[15,1153,1154],{},"Nuxt Content 是个人博客的理想选择，它简单、强大且易于维护。",[917,1156,1157],{},"html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html pre.shiki code .sScJk, html code.shiki .sScJk{--shiki-default:#6F42C1;--shiki-dark:#B392F0}html pre.shiki code .sZZnC, html code.shiki .sZZnC{--shiki-default:#032F62;--shiki-dark:#9ECBFF}html pre.shiki code .szBVR, html code.shiki .szBVR{--shiki-default:#D73A49;--shiki-dark:#F97583}html pre.shiki code .sVt8B, html code.shiki .sVt8B{--shiki-default:#24292E;--shiki-dark:#E1E4E8}",{"title":70,"searchDepth":95,"depth":95,"links":1159},[1160,1161,1162,1167],{"id":989,"depth":95,"text":990},{"id":996,"depth":95,"text":997},{"id":1032,"depth":95,"text":1032,"children":1163},[1164,1165,1166],{"id":1035,"depth":124,"text":1036},{"id":1057,"depth":124,"text":1058},{"id":1105,"depth":124,"text":1106},{"id":1151,"depth":95,"text":1151},"2026-06-13","本指南将带你了解如何使用 Nuxt Content 模块构建一个现代化的博客系统",{},"/articles/getting-started",{"title":984,"description":1169},"articles/getting-started",[1175,1176,1177],"Nuxt","前端","教程","ruaH3EONC9hK-XzSYN2M_TQCjnRcHsMZN6KV7VzpNbw",{"id":1180,"title":1181,"body":1182,"date":1396,"description":1397,"extension":935,"featured":938,"meta":1398,"navigation":938,"path":1399,"seo":1400,"stem":1401,"tags":1402,"tip":980,"__hash__":1405},"articles/articles/frontend-tips.md","2026 年前端开发技巧分享",{"type":8,"value":1183,"toc":1388},[1184,1187,1191,1194,1279,1283,1286,1326,1330,1333,1379,1382,1385],[11,1185,1186],{"id":1186},"前端开发技巧",[53,1188,1190],{"id":1189},"_1-使用-css-container-queries","1. 使用 CSS Container Queries",[15,1192,1193],{},"Container Queries 让组件可以根据父容器的尺寸来调整样式：",[65,1195,1199],{"className":1196,"code":1197,"language":1198,"meta":70,"style":70},"language-css shiki shiki-themes github-light github-dark",".card-container {\n  container-type: inline-size;\n}\n\n@container (min-width: 400px) {\n  .card {\n    display: grid;\n    grid-template-columns: 1fr 2fr;\n  }\n}\n","css",[25,1200,1201,1208,1216,1220,1224,1232,1239,1251,1270,1275],{"__ignoreMap":70},[74,1202,1203,1206],{"class":76,"line":77},[74,1204,1205],{"class":87},".card-container",[74,1207,386],{"class":91},[74,1209,1210,1213],{"class":76,"line":95},[74,1211,1212],{"class":101},"  container-type",[74,1214,1215],{"class":91},": inline-size;\n",[74,1217,1218],{"class":76,"line":124},[74,1219,174],{"class":91},[74,1221,1222],{"class":76,"line":136},[74,1223,1143],{"emptyLinePlaceholder":938},[74,1225,1226,1229],{"class":76,"line":148},[74,1227,1228],{"class":80},"@container",[74,1230,1231],{"class":91}," (min-width: 400px) {\n",[74,1233,1234,1237],{"class":76,"line":154},[74,1235,1236],{"class":87},"  .card",[74,1238,386],{"class":91},[74,1240,1241,1244,1246,1249],{"class":76,"line":171},[74,1242,1243],{"class":101},"    display",[74,1245,580],{"class":91},[74,1247,1248],{"class":101},"grid",[74,1250,542],{"class":91},[74,1252,1253,1256,1258,1260,1263,1266,1268],{"class":76,"line":401},[74,1254,1255],{"class":101},"    grid-template-columns",[74,1257,580],{"class":91},[74,1259,259],{"class":101},[74,1261,1262],{"class":80},"fr",[74,1264,1265],{"class":101}," 2",[74,1267,1262],{"class":80},[74,1269,542],{"class":91},[74,1271,1272],{"class":76,"line":413},[74,1273,1274],{"class":91},"  }\n",[74,1276,1277],{"class":76,"line":425},[74,1278,174],{"class":91},[53,1280,1282],{"id":1281},"_2-利用-view-transitions-api","2. 利用 View Transitions API",[15,1284,1285],{},"创建流畅的页面过渡动画：",[65,1287,1291],{"className":1288,"code":1289,"language":1290,"meta":70,"style":70},"language-js shiki shiki-themes github-light github-dark","document.startViewTransition(() => {\n  // 更新 DOM\n  updateContent()\n})\n","js",[25,1292,1293,1309,1314,1322],{"__ignoreMap":70},[74,1294,1295,1298,1301,1304,1307],{"class":76,"line":77},[74,1296,1297],{"class":91},"document.",[74,1299,1300],{"class":87},"startViewTransition",[74,1302,1303],{"class":91},"(() ",[74,1305,1306],{"class":80},"=>",[74,1308,386],{"class":91},[74,1310,1311],{"class":76,"line":95},[74,1312,1313],{"class":663},"  // 更新 DOM\n",[74,1315,1316,1319],{"class":76,"line":124},[74,1317,1318],{"class":87},"  updateContent",[74,1320,1321],{"class":91},"()\n",[74,1323,1324],{"class":76,"line":136},[74,1325,1102],{"class":91},[53,1327,1329],{"id":1328},"_3-使用-temporal-api","3. 使用 Temporal API",[15,1331,1332],{},"JavaScript 的新日期时间 API：",[65,1334,1336],{"className":1288,"code":1335,"language":1290,"meta":70,"style":70},"const now = Temporal.Now.zonedDateTime()\nconst tomorrow = now.add({ days: 1 })\n",[25,1337,1338,1356],{"__ignoreMap":70},[74,1339,1340,1343,1346,1348,1351,1354],{"class":76,"line":77},[74,1341,1342],{"class":80},"const",[74,1344,1345],{"class":101}," now",[74,1347,105],{"class":80},[74,1349,1350],{"class":91}," Temporal.Now.",[74,1352,1353],{"class":87},"zonedDateTime",[74,1355,1321],{"class":91},[74,1357,1358,1360,1363,1365,1368,1371,1374,1376],{"class":76,"line":95},[74,1359,1342],{"class":80},[74,1361,1362],{"class":101}," tomorrow",[74,1364,105],{"class":80},[74,1366,1367],{"class":91}," now.",[74,1369,1370],{"class":87},"add",[74,1372,1373],{"class":91},"({ days: ",[74,1375,259],{"class":101},[74,1377,1378],{"class":91}," })\n",[11,1380,1381],{"id":1381},"总结",[15,1383,1384],{},"保持学习，关注 Web 平台的新特性，能让你的开发工作更加高效。",[917,1386,1387],{},"html pre.shiki code .sScJk, html code.shiki .sScJk{--shiki-default:#6F42C1;--shiki-dark:#B392F0}html pre.shiki code .sVt8B, html code.shiki .sVt8B{--shiki-default:#24292E;--shiki-dark:#E1E4E8}html pre.shiki code .sj4cs, html code.shiki .sj4cs{--shiki-default:#005CC5;--shiki-dark:#79B8FF}html pre.shiki code .szBVR, html code.shiki .szBVR{--shiki-default:#D73A49;--shiki-dark:#F97583}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html pre.shiki code .sJ8bj, html code.shiki .sJ8bj{--shiki-default:#6A737D;--shiki-dark:#6A737D}",{"title":70,"searchDepth":95,"depth":95,"links":1389},[1390,1395],{"id":1186,"depth":95,"text":1186,"children":1391},[1392,1393,1394],{"id":1189,"depth":124,"text":1190},{"id":1281,"depth":124,"text":1282},{"id":1328,"depth":124,"text":1329},{"id":1381,"depth":95,"text":1381},"2026-06-10","分享一些实用的前端开发技巧和最佳实践",{},"/articles/frontend-tips",{"title":1181,"description":1397},"articles/frontend-tips",[1176,1403,1404],"JavaScript","Vue","tkfCAox48ywXyN4w_h3uyGDOSZY3n7lBm1PrUYxQ2jk",{"id":1407,"title":1408,"body":1409,"date":1552,"description":1553,"extension":935,"featured":936,"meta":1554,"navigation":938,"path":1555,"seo":1556,"stem":1557,"tags":1558,"tip":980,"__hash__":1559},"articles/articles/nuxt4-guide.md","Nuxt 4 新特性详解",{"type":8,"value":1410,"toc":1542},[1411,1415,1418,1422,1429,1435,1438,1442,1453,1457,1468,1472,1479,1506,1509,1534,1536,1539],[11,1412,1414],{"id":1413},"nuxt-4-新特性","Nuxt 4 新特性",[15,1416,1417],{},"Nuxt 4 带来了许多令人兴奋的新特性，让我们一起来看看。",[53,1419,1421],{"id":1420},"_1-app-directory-模式","1. App Directory 模式",[15,1423,1424,1425,1428],{},"Nuxt 4 推荐使用 ",[25,1426,1427],{},"app/"," 目录来组织源代码：",[65,1430,1433],{"className":1431,"code":1432,"language":826},[824],"├── app/\n│   ├── components/\n│   ├── pages/\n│   ├── composables/\n│   └── app.vue\n├── public/\n└── nuxt.config.ts\n",[25,1434,1432],{"__ignoreMap":70},[15,1436,1437],{},"这种结构更加清晰，便于管理大型项目。",[53,1439,1441],{"id":1440},"_2-改进的-typescript-支持","2. 改进的 TypeScript 支持",[34,1443,1444,1447,1450],{},[37,1445,1446],{},"更好的类型推断",[37,1448,1449],{},"自动导入类型",[37,1451,1452],{},"改进的 IDE 支持",[53,1454,1456],{"id":1455},"_3-性能优化","3. 性能优化",[34,1458,1459,1462,1465],{},[37,1460,1461],{},"更快的构建速度",[37,1463,1464],{},"更小的包体积",[37,1466,1467],{},"改进的热重载",[53,1469,1471],{"id":1470},"_4-兼容性日期","4. 兼容性日期",[15,1473,1474,1475,1478],{},"使用 ",[25,1476,1477],{},"compatibilityDate"," 来确保项目稳定性：",[65,1480,1482],{"className":1068,"code":1481,"language":1070,"meta":70,"style":70},"export default defineNuxtConfig({\n  compatibilityDate: '2025-07-15'\n})\n",[25,1483,1484,1494,1502],{"__ignoreMap":70},[74,1485,1486,1488,1490,1492],{"class":76,"line":77},[74,1487,1077],{"class":80},[74,1489,1080],{"class":80},[74,1491,1083],{"class":87},[74,1493,1086],{"class":91},[74,1495,1496,1499],{"class":76,"line":95},[74,1497,1498],{"class":91},"  compatibilityDate: ",[74,1500,1501],{"class":117},"'2025-07-15'\n",[74,1503,1504],{"class":76,"line":124},[74,1505,1102],{"class":91},[11,1507,1508],{"id":1508},"升级指南",[833,1510,1511,1518,1524,1527],{},[37,1512,1513,1514,1517],{},"更新 ",[25,1515,1516],{},"package.json"," 中的依赖版本",[37,1519,1520,1521,1523],{},"将源代码移动到 ",[25,1522,1427],{}," 目录",[37,1525,1526],{},"检查废弃的 API 并迁移到新 API",[37,1528,1529,1530,1533],{},"运行 ",[25,1531,1532],{},"nuxt prepare"," 更新类型定义",[11,1535,1151],{"id":1151},[15,1537,1538],{},"Nuxt 4 是一个重要的版本更新，带来了更好的开发体验和性能。",[917,1540,1541],{},"html pre.shiki code .szBVR, html code.shiki .szBVR{--shiki-default:#D73A49;--shiki-dark:#F97583}html pre.shiki code .sScJk, html code.shiki .sScJk{--shiki-default:#6F42C1;--shiki-dark:#B392F0}html pre.shiki code .sVt8B, html code.shiki .sVt8B{--shiki-default:#24292E;--shiki-dark:#E1E4E8}html pre.shiki code .sZZnC, html code.shiki .sZZnC{--shiki-default:#032F62;--shiki-dark:#9ECBFF}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}",{"title":70,"searchDepth":95,"depth":95,"links":1543},[1544,1550,1551],{"id":1413,"depth":95,"text":1414,"children":1545},[1546,1547,1548,1549],{"id":1420,"depth":124,"text":1421},{"id":1440,"depth":124,"text":1441},{"id":1455,"depth":124,"text":1456},{"id":1470,"depth":124,"text":1471},{"id":1508,"depth":95,"text":1508},{"id":1151,"depth":95,"text":1151},"2026-06-05","深入了解 Nuxt 4 的新特性和改进",{},"/articles/nuxt4-guide",{"title":1408,"description":1553},"articles/nuxt4-guide",[1175,1404,1176],"fefVathsBoCvp-NrP1lOFqi5ydzu3wqDFKFteJOLYMc",1787216606322]