[{"data":1,"prerenderedAt":9793},["ShallowReactive",2],{"post-jira-cli-intro":3,"series-source-jira-cli-intro":1502},{"id":4,"title":5,"author":6,"body":7,"description":1484,"difficulty":1485,"draft":1486,"extension":1487,"image":1488,"meta":1489,"navigation":104,"path":1490,"pinned":1486,"published":1491,"seo":1492,"series":6,"seriesOrder":6,"stem":1493,"tags":1494,"updated":1491,"__hash__":1501},"posts\u002Fposts\u002Fjira-cli-intro\u002Findex.md","jira_cli — 把企业 Jira 装进命令行，AI Agent 直接调用",null,{"type":8,"value":9,"toc":1463},"minimark",[10,15,35,124,127,130,213,216,251,258,261,266,389,393,529,533,602,605,681,684,687,691,694,714,717,734,737,741,748,886,897,929,933,936,1033,1037,1040,1160,1164,1167,1231,1234,1238,1241,1309,1312,1320,1323,1357,1364,1367,1428,1431,1434,1451,1459],[11,12,14],"h2",{"id":13},"jira_cli-是什么","jira_cli 是什么",[16,17,18,25,26,30,31,34],"p",{},[19,20,24],"a",{"href":21,"rel":22},"https:\u002F\u002Fgithub.com\u002Fwishesl\u002Fjira_cli",[23],"nofollow","jira_cli"," 是一个面向企业 Jira（Server \u002F Data Center）的本地命令行工具，Go 语言编写，MIT 协议开源。它同时提供 ",[27,28,29],"strong",{},"Cobra CLI"," 和 ",[27,32,33],{},"MCP stdio server"," 两种入口，既能当日常命令行用，又能直接接入 AI 编程工具。",[36,37,42],"pre",{"className":38,"code":39,"language":40,"meta":41,"style":41},"language-bash shiki shiki-themes github-light github-dark","# CLI 日常使用\njira_cli issue search \"project = PROJ ORDER BY updated DESC\"\njira_cli issue get PROJ-123\njira_cli issue comment PROJ-123 \"处理完成，请测试验证\"\n\n# MCP 模式 — 给 AI 装上操作 Jira 的手\njira_cli mcp --user=work\n","bash","",[43,44,45,54,70,83,99,106,112],"code",{"__ignoreMap":41},[46,47,50],"span",{"class":48,"line":49},"line",1,[46,51,53],{"class":52},"sHbNN","# CLI 日常使用\n",[46,55,57,60,64,67],{"class":48,"line":56},2,[46,58,24],{"class":59},"sw2iP",[46,61,63],{"class":62},"scXbn"," issue",[46,65,66],{"class":62}," search",[46,68,69],{"class":62}," \"project = PROJ ORDER BY updated DESC\"\n",[46,71,73,75,77,80],{"class":48,"line":72},3,[46,74,24],{"class":59},[46,76,63],{"class":62},[46,78,79],{"class":62}," get",[46,81,82],{"class":62}," PROJ-123\n",[46,84,86,88,90,93,96],{"class":48,"line":85},4,[46,87,24],{"class":59},[46,89,63],{"class":62},[46,91,92],{"class":62}," comment",[46,94,95],{"class":62}," PROJ-123",[46,97,98],{"class":62}," \"处理完成，请测试验证\"\n",[46,100,102],{"class":48,"line":101},5,[46,103,105],{"emptyLinePlaceholder":104},true,"\n",[46,107,109],{"class":48,"line":108},6,[46,110,111],{"class":52},"# MCP 模式 — 给 AI 装上操作 Jira 的手\n",[46,113,115,117,120],{"class":48,"line":114},7,[46,116,24],{"class":59},[46,118,119],{"class":62}," mcp",[46,121,123],{"class":122},"suQ91"," --user=work\n",[11,125,126],{"id":126},"为什么需要它",[16,128,129],{},"大多数 Jira 命令行工具只解决\"开发者手动操作 Jira\"这一个场景。但 2026 年 AI 编程工具已经普及，你的 AI Agent（Claude Code、Codex、Cursor 等）也需要读写 Jira。痛点就来了：",[131,132,133,146],"table",{},[134,135,136],"thead",{},[137,138,139,143],"tr",{},[140,141,142],"th",{},"痛点",[140,144,145],{},"jira_cli 怎么解",[147,148,149,158,166,178,190,201],"tbody",{},[137,150,151,155],{},[152,153,154],"td",{},"企业 Jira 在内网，没有公网 API",[152,156,157],{},"本地 stdio MCP server，不经过外网",[137,159,160,163],{},[152,161,162],{},"AI 可能误删、误写 Jira 数据",[152,164,165],{},"账号级只读策略，AI 只能查不能改",[137,167,168,171],{},[152,169,170],{},"团队多人多 Jira 实例",[152,172,173,174,177],{},"多 Profile 管理，",[43,175,176],{},"--user=work"," 切换",[137,179,180,183],{},[152,181,182],{},"Windows 终端体验差",[152,184,185,186,189],{},"原生 ",[43,187,188],{},".exe"," 构建 + 一键安装脚本",[137,191,192,195],{},[152,193,194],{},"AI 生成 JQL 困难",[152,196,197,198],{},"字段元数据发现：",[43,199,200],{},"field list --query xxx",[137,202,203,206],{},[152,204,205],{},"MCP 直接拿原始 JSON 太冗长",[152,207,208,209,212],{},"紧凑视图 + ",[43,210,211],{},"--view summary"," 减少 token 消耗",[11,214,215],{"id":215},"安装",[36,217,219],{"className":38,"code":218,"language":40,"meta":41,"style":41},"# 从源码安装（要求 Go 1.26.3+）\ngo install github.com\u002Fwishesl\u002Fjira_cli@latest\n\n# 或下载预编译二进制\n# https:\u002F\u002Fgithub.com\u002Fwishesl\u002Fjira_cli\u002Freleases\n",[43,220,221,226,237,241,246],{"__ignoreMap":41},[46,222,223],{"class":48,"line":49},[46,224,225],{"class":52},"# 从源码安装（要求 Go 1.26.3+）\n",[46,227,228,231,234],{"class":48,"line":56},[46,229,230],{"class":59},"go",[46,232,233],{"class":62}," install",[46,235,236],{"class":62}," github.com\u002Fwishesl\u002Fjira_cli@latest\n",[46,238,239],{"class":48,"line":72},[46,240,105],{"emptyLinePlaceholder":104},[46,242,243],{"class":48,"line":85},[46,244,245],{"class":52},"# 或下载预编译二进制\n",[46,247,248],{"class":48,"line":101},[46,249,250],{"class":52},"# https:\u002F\u002Fgithub.com\u002Fwishesl\u002Fjira_cli\u002Freleases\n",[16,252,253,254,257],{},"Windows 用户下载 zip 包解压后双击 ",[43,255,256],{},"install.bat"," 即可全局安装，自动加入 PATH。",[11,259,260],{"id":260},"快速上手",[262,263,265],"h3",{"id":264},"_1-配置认证","1. 配置认证",[36,267,269],{"className":38,"code":268,"language":40,"meta":41,"style":41},"# 设置 Jira 地址\njira_cli set base-url https:\u002F\u002Fjira.your-company.com --global\n\n# Token 认证（推荐）\njira_cli set token \u003Cyour-token> --global\n\n# 或用户名密码认证\njira_cli set auth \u003Cusername> \u003Cpassword> --global\n\n# 验证一下\njira_cli user me\n",[43,270,271,276,292,296,301,326,330,335,367,372,378],{"__ignoreMap":41},[46,272,273],{"class":48,"line":49},[46,274,275],{"class":52},"# 设置 Jira 地址\n",[46,277,278,280,283,286,289],{"class":48,"line":56},[46,279,24],{"class":59},[46,281,282],{"class":62}," set",[46,284,285],{"class":62}," base-url",[46,287,288],{"class":62}," https:\u002F\u002Fjira.your-company.com",[46,290,291],{"class":122}," --global\n",[46,293,294],{"class":48,"line":72},[46,295,105],{"emptyLinePlaceholder":104},[46,297,298],{"class":48,"line":85},[46,299,300],{"class":52},"# Token 认证（推荐）\n",[46,302,303,305,307,310,314,317,321,324],{"class":48,"line":101},[46,304,24],{"class":59},[46,306,282],{"class":62},[46,308,309],{"class":62}," token",[46,311,313],{"class":312},"siTax"," \u003C",[46,315,316],{"class":62},"your-toke",[46,318,320],{"class":319},"sIX_F","n",[46,322,323],{"class":312},">",[46,325,291],{"class":122},[46,327,328],{"class":48,"line":108},[46,329,105],{"emptyLinePlaceholder":104},[46,331,332],{"class":48,"line":114},[46,333,334],{"class":52},"# 或用户名密码认证\n",[46,336,338,340,342,345,347,350,353,355,357,360,363,365],{"class":48,"line":337},8,[46,339,24],{"class":59},[46,341,282],{"class":62},[46,343,344],{"class":62}," auth",[46,346,313],{"class":312},[46,348,349],{"class":62},"usernam",[46,351,352],{"class":319},"e",[46,354,323],{"class":312},[46,356,313],{"class":312},[46,358,359],{"class":62},"passwor",[46,361,362],{"class":319},"d",[46,364,323],{"class":312},[46,366,291],{"class":122},[46,368,370],{"class":48,"line":369},9,[46,371,105],{"emptyLinePlaceholder":104},[46,373,375],{"class":48,"line":374},10,[46,376,377],{"class":52},"# 验证一下\n",[46,379,381,383,386],{"class":48,"line":380},11,[46,382,24],{"class":59},[46,384,385],{"class":62}," user",[46,387,388],{"class":62}," me\n",[262,390,392],{"id":391},"_2-日常使用","2. 日常使用",[36,394,396],{"className":38,"code":395,"language":40,"meta":41,"style":41},"# 查自己今天的任务\njira_cli issue search \"assignee = currentUser() ORDER BY updated DESC\"\n\n# 看某个任务详情\njira_cli issue get PROJ-123\n\n# 流转状态\njira_cli issue transitions PROJ-123                    # 可以转哪些\njira_cli issue do-transition PROJ-123 21               # 执行流转\n\n# 添加备注\njira_cli issue comment PROJ-123 \"已定位到问题，正在修复\"\n\n# 创建任务\njira_cli issue create PROJ Task --fields @fields.json\n",[43,397,398,403,414,418,423,433,437,442,456,473,477,482,496,501,507],{"__ignoreMap":41},[46,399,400],{"class":48,"line":49},[46,401,402],{"class":52},"# 查自己今天的任务\n",[46,404,405,407,409,411],{"class":48,"line":56},[46,406,24],{"class":59},[46,408,63],{"class":62},[46,410,66],{"class":62},[46,412,413],{"class":62}," \"assignee = currentUser() ORDER BY updated DESC\"\n",[46,415,416],{"class":48,"line":72},[46,417,105],{"emptyLinePlaceholder":104},[46,419,420],{"class":48,"line":85},[46,421,422],{"class":52},"# 看某个任务详情\n",[46,424,425,427,429,431],{"class":48,"line":101},[46,426,24],{"class":59},[46,428,63],{"class":62},[46,430,79],{"class":62},[46,432,82],{"class":62},[46,434,435],{"class":48,"line":108},[46,436,105],{"emptyLinePlaceholder":104},[46,438,439],{"class":48,"line":114},[46,440,441],{"class":52},"# 流转状态\n",[46,443,444,446,448,451,453],{"class":48,"line":337},[46,445,24],{"class":59},[46,447,63],{"class":62},[46,449,450],{"class":62}," transitions",[46,452,95],{"class":62},[46,454,455],{"class":52},"                    # 可以转哪些\n",[46,457,458,460,462,465,467,470],{"class":48,"line":369},[46,459,24],{"class":59},[46,461,63],{"class":62},[46,463,464],{"class":62}," do-transition",[46,466,95],{"class":62},[46,468,469],{"class":122}," 21",[46,471,472],{"class":52},"               # 执行流转\n",[46,474,475],{"class":48,"line":374},[46,476,105],{"emptyLinePlaceholder":104},[46,478,479],{"class":48,"line":380},[46,480,481],{"class":52},"# 添加备注\n",[46,483,485,487,489,491,493],{"class":48,"line":484},12,[46,486,24],{"class":59},[46,488,63],{"class":62},[46,490,92],{"class":62},[46,492,95],{"class":62},[46,494,495],{"class":62}," \"已定位到问题，正在修复\"\n",[46,497,499],{"class":48,"line":498},13,[46,500,105],{"emptyLinePlaceholder":104},[46,502,504],{"class":48,"line":503},14,[46,505,506],{"class":52},"# 创建任务\n",[46,508,510,512,514,517,520,523,526],{"class":48,"line":509},15,[46,511,24],{"class":59},[46,513,63],{"class":62},[46,515,516],{"class":62}," create",[46,518,519],{"class":62}," PROJ",[46,521,522],{"class":62}," Task",[46,524,525],{"class":122}," --fields",[46,527,528],{"class":62}," @fields.json\n",[262,530,532],{"id":531},"_3-配给-ai-使用","3. 配给 AI 使用",[36,534,536],{"className":38,"code":535,"language":40,"meta":41,"style":41},"# 先配置一个只读账号给 AI 用\njira_cli set base-url https:\u002F\u002Fjira.your-company.com --name ai --global\njira_cli set token \u003Ctoken> --name ai --global\njira_cli set readonly on --name ai --global\n",[43,537,538,543,561,584],{"__ignoreMap":41},[46,539,540],{"class":48,"line":49},[46,541,542],{"class":52},"# 先配置一个只读账号给 AI 用\n",[46,544,545,547,549,551,553,556,559],{"class":48,"line":56},[46,546,24],{"class":59},[46,548,282],{"class":62},[46,550,285],{"class":62},[46,552,288],{"class":62},[46,554,555],{"class":122}," --name",[46,557,558],{"class":62}," ai",[46,560,291],{"class":122},[46,562,563,565,567,569,571,574,576,578,580,582],{"class":48,"line":72},[46,564,24],{"class":59},[46,566,282],{"class":62},[46,568,309],{"class":62},[46,570,313],{"class":312},[46,572,573],{"class":62},"toke",[46,575,320],{"class":319},[46,577,323],{"class":312},[46,579,555],{"class":122},[46,581,558],{"class":62},[46,583,291],{"class":122},[46,585,586,588,590,593,596,598,600],{"class":48,"line":85},[46,587,24],{"class":59},[46,589,282],{"class":62},[46,591,592],{"class":62}," readonly",[46,594,595],{"class":62}," on",[46,597,555],{"class":122},[46,599,558],{"class":62},[46,601,291],{"class":122},[16,603,604],{},"然后在 AI 工具的 MCP 配置里加上：",[36,606,610],{"className":607,"code":608,"language":609,"meta":41,"style":41},"language-json shiki shiki-themes github-light github-dark","{\n  \"mcpServers\": {\n    \"jira\": {\n      \"command\": \"jira_cli\",\n      \"args\": [\"mcp\", \"--user=ai\"]\n    }\n  }\n}\n","json",[43,611,612,617,625,632,646,666,671,676],{"__ignoreMap":41},[46,613,614],{"class":48,"line":49},[46,615,616],{"class":319},"{\n",[46,618,619,622],{"class":48,"line":56},[46,620,621],{"class":122},"  \"mcpServers\"",[46,623,624],{"class":319},": {\n",[46,626,627,630],{"class":48,"line":72},[46,628,629],{"class":122},"    \"jira\"",[46,631,624],{"class":319},[46,633,634,637,640,643],{"class":48,"line":85},[46,635,636],{"class":122},"      \"command\"",[46,638,639],{"class":319},": ",[46,641,642],{"class":62},"\"jira_cli\"",[46,644,645],{"class":319},",\n",[46,647,648,651,654,657,660,663],{"class":48,"line":101},[46,649,650],{"class":122},"      \"args\"",[46,652,653],{"class":319},": [",[46,655,656],{"class":62},"\"mcp\"",[46,658,659],{"class":319},", ",[46,661,662],{"class":62},"\"--user=ai\"",[46,664,665],{"class":319},"]\n",[46,667,668],{"class":48,"line":108},[46,669,670],{"class":319},"    }\n",[46,672,673],{"class":48,"line":114},[46,674,675],{"class":319},"  }\n",[46,677,678],{"class":48,"line":337},[46,679,680],{"class":319},"}\n",[16,682,683],{},"这样 AI 就能查 Jira 但不能修改，既有了上下文，又不用担心误操作。",[11,685,686],{"id":686},"亮点",[262,688,690],{"id":689},"_1-账号级只读策略","1. 账号级只读策略",[16,692,693],{},"这是 jira_cli 最实用的设计。给 AI Agent 配置只读账号：",[36,695,697],{"className":38,"code":696,"language":40,"meta":41,"style":41},"jira_cli set readonly on --name ai\n",[43,698,699],{"__ignoreMap":41},[46,700,701,703,705,707,709,711],{"class":48,"line":49},[46,702,24],{"class":59},[46,704,282],{"class":62},[46,706,592],{"class":62},[46,708,595],{"class":62},[46,710,555],{"class":122},[46,712,713],{"class":62}," ai\n",[16,715,716],{},"只读模式下：",[718,719,720,724,727],"ul",{},[721,722,723],"li",{},"CLI 写命令被隐藏和拒绝",[721,725,726],{},"MCP 的写入工具不会注册",[721,728,729,730,733],{},"环境变量 ",[43,731,732],{},"JIRA_READONLY=true"," 也可以临时覆盖",[16,735,736],{},"AI 只能查 JQL、看任务、查字段、读评论，绝对不会误改数据。需要创建\u002F更新时，另配一个有写入权限的账号由开发者自己操作。",[262,738,740],{"id":739},"_2-多-profile-切换","2. 多 Profile 切换",[16,742,743,744,747],{},"一个配置文件里管理多个 Jira 账号，",[43,745,746],{},"--user"," 切换：",[36,749,751],{"className":38,"code":750,"language":40,"meta":41,"style":41},"# 工作账号\njira_cli set base-url https:\u002F\u002Fjira.company.com --name work --global\njira_cli set token \u003Cwork-token> --name work --global\n\n# 个人账号\njira_cli set base-url https:\u002F\u002Fjira.personal.com --name personal --global\njira_cli set token \u003Cpersonal-token> --name personal --global\n\n# 使用时切换\njira_cli --user=work issue search \"project = PROJ\"\njira_cli --user=personal issue search \"project = SIDE\"\n",[43,752,753,758,776,799,803,808,826,849,853,858,872],{"__ignoreMap":41},[46,754,755],{"class":48,"line":49},[46,756,757],{"class":52},"# 工作账号\n",[46,759,760,762,764,766,769,771,774],{"class":48,"line":56},[46,761,24],{"class":59},[46,763,282],{"class":62},[46,765,285],{"class":62},[46,767,768],{"class":62}," https:\u002F\u002Fjira.company.com",[46,770,555],{"class":122},[46,772,773],{"class":62}," work",[46,775,291],{"class":122},[46,777,778,780,782,784,786,789,791,793,795,797],{"class":48,"line":72},[46,779,24],{"class":59},[46,781,282],{"class":62},[46,783,309],{"class":62},[46,785,313],{"class":312},[46,787,788],{"class":62},"work-toke",[46,790,320],{"class":319},[46,792,323],{"class":312},[46,794,555],{"class":122},[46,796,773],{"class":62},[46,798,291],{"class":122},[46,800,801],{"class":48,"line":85},[46,802,105],{"emptyLinePlaceholder":104},[46,804,805],{"class":48,"line":101},[46,806,807],{"class":52},"# 个人账号\n",[46,809,810,812,814,816,819,821,824],{"class":48,"line":108},[46,811,24],{"class":59},[46,813,282],{"class":62},[46,815,285],{"class":62},[46,817,818],{"class":62}," https:\u002F\u002Fjira.personal.com",[46,820,555],{"class":122},[46,822,823],{"class":62}," personal",[46,825,291],{"class":122},[46,827,828,830,832,834,836,839,841,843,845,847],{"class":48,"line":114},[46,829,24],{"class":59},[46,831,282],{"class":62},[46,833,309],{"class":62},[46,835,313],{"class":312},[46,837,838],{"class":62},"personal-toke",[46,840,320],{"class":319},[46,842,323],{"class":312},[46,844,555],{"class":122},[46,846,823],{"class":62},[46,848,291],{"class":122},[46,850,851],{"class":48,"line":337},[46,852,105],{"emptyLinePlaceholder":104},[46,854,855],{"class":48,"line":369},[46,856,857],{"class":52},"# 使用时切换\n",[46,859,860,862,865,867,869],{"class":48,"line":374},[46,861,24],{"class":59},[46,863,864],{"class":122}," --user=work",[46,866,63],{"class":62},[46,868,66],{"class":62},[46,870,871],{"class":62}," \"project = PROJ\"\n",[46,873,874,876,879,881,883],{"class":48,"line":380},[46,875,24],{"class":59},[46,877,878],{"class":122}," --user=personal",[46,880,63],{"class":62},[46,882,66],{"class":62},[46,884,885],{"class":62}," \"project = SIDE\"\n",[16,887,888,889,892,893,896],{},"同时支持",[27,890,891],{},"项目级配置","（",[43,894,895],{},"--local","）和工作目录绑定：",[36,898,900],{"className":38,"code":899,"language":40,"meta":41,"style":41},"# 在项目目录下，配置只对当前项目生效\ncd ~\u002Fprojects\u002Fbackend\njira_cli set base-url https:\u002F\u002Fjira-backend.company.com --local\n",[43,901,902,907,915],{"__ignoreMap":41},[46,903,904],{"class":48,"line":49},[46,905,906],{"class":52},"# 在项目目录下，配置只对当前项目生效\n",[46,908,909,912],{"class":48,"line":56},[46,910,911],{"class":122},"cd",[46,913,914],{"class":62}," ~\u002Fprojects\u002Fbackend\n",[46,916,917,919,921,923,926],{"class":48,"line":72},[46,918,24],{"class":59},[46,920,282],{"class":62},[46,922,285],{"class":62},[46,924,925],{"class":62}," https:\u002F\u002Fjira-backend.company.com",[46,927,928],{"class":122}," --local\n",[262,930,932],{"id":931},"_3-对-ai-友好的输出","3. 对 AI 友好的输出",[16,934,935],{},"jira_cli 在输出设计上充分考虑了 AI Agent 的 token 消耗：",[36,937,939],{"className":38,"code":938,"language":40,"meta":41,"style":41},"# 紧凑视图 — 去掉 self\u002Favatar\u002F缩略图等无用字段\njira_cli issue get PROJ-123 --view summary\n\n# 表格输出 — 一眼看到关键信息\njira_cli issue search \"project = PROJ\" --output table\n\n# JSON 输出 — 程序解析，不转义 Unicode\njira_cli --output json issue get PROJ-123\n\n# 自定义搜索列 — 只要需要的字段\njira_cli set search-columns key,summary,fixVersions\n",[43,940,941,946,962,966,971,988,992,997,1012,1016,1021],{"__ignoreMap":41},[46,942,943],{"class":48,"line":49},[46,944,945],{"class":52},"# 紧凑视图 — 去掉 self\u002Favatar\u002F缩略图等无用字段\n",[46,947,948,950,952,954,956,959],{"class":48,"line":56},[46,949,24],{"class":59},[46,951,63],{"class":62},[46,953,79],{"class":62},[46,955,95],{"class":62},[46,957,958],{"class":122}," --view",[46,960,961],{"class":62}," summary\n",[46,963,964],{"class":48,"line":72},[46,965,105],{"emptyLinePlaceholder":104},[46,967,968],{"class":48,"line":85},[46,969,970],{"class":52},"# 表格输出 — 一眼看到关键信息\n",[46,972,973,975,977,979,982,985],{"class":48,"line":101},[46,974,24],{"class":59},[46,976,63],{"class":62},[46,978,66],{"class":62},[46,980,981],{"class":62}," \"project = PROJ\"",[46,983,984],{"class":122}," --output",[46,986,987],{"class":62}," table\n",[46,989,990],{"class":48,"line":108},[46,991,105],{"emptyLinePlaceholder":104},[46,993,994],{"class":48,"line":114},[46,995,996],{"class":52},"# JSON 输出 — 程序解析，不转义 Unicode\n",[46,998,999,1001,1003,1006,1008,1010],{"class":48,"line":337},[46,1000,24],{"class":59},[46,1002,984],{"class":122},[46,1004,1005],{"class":62}," json",[46,1007,63],{"class":62},[46,1009,79],{"class":62},[46,1011,82],{"class":62},[46,1013,1014],{"class":48,"line":369},[46,1015,105],{"emptyLinePlaceholder":104},[46,1017,1018],{"class":48,"line":374},[46,1019,1020],{"class":52},"# 自定义搜索列 — 只要需要的字段\n",[46,1022,1023,1025,1027,1030],{"class":48,"line":380},[46,1024,24],{"class":59},[46,1026,282],{"class":62},[46,1028,1029],{"class":62}," search-columns",[46,1031,1032],{"class":62}," key,summary,fixVersions\n",[262,1034,1036],{"id":1035},"_4-字段元数据发现","4. 字段元数据发现",[16,1038,1039],{},"Jira 的 customfield 让人头疼，jira_cli 提供了完整的字段发现能力：",[36,1041,1043],{"className":38,"code":1042,"language":40,"meta":41,"style":41},"# 列出所有字段及其类型\njira_cli field list\n\n# 搜索包含特定关键词的字段\njira_cli field list --query \"版本\"\n\n# 只看自定义字段\njira_cli field list --custom-only\n\n# 创建任务前，先看看需要哪些字段\njira_cli issue create-meta PROJ --issue-type Task\n\n# 分页查看可选值\njira_cli issue create-meta-values PROJ Task priority --query High\n",[43,1044,1045,1050,1060,1064,1069,1084,1088,1093,1104,1108,1113,1130,1134,1139],{"__ignoreMap":41},[46,1046,1047],{"class":48,"line":49},[46,1048,1049],{"class":52},"# 列出所有字段及其类型\n",[46,1051,1052,1054,1057],{"class":48,"line":56},[46,1053,24],{"class":59},[46,1055,1056],{"class":62}," field",[46,1058,1059],{"class":62}," list\n",[46,1061,1062],{"class":48,"line":72},[46,1063,105],{"emptyLinePlaceholder":104},[46,1065,1066],{"class":48,"line":85},[46,1067,1068],{"class":52},"# 搜索包含特定关键词的字段\n",[46,1070,1071,1073,1075,1078,1081],{"class":48,"line":101},[46,1072,24],{"class":59},[46,1074,1056],{"class":62},[46,1076,1077],{"class":62}," list",[46,1079,1080],{"class":122}," --query",[46,1082,1083],{"class":62}," \"版本\"\n",[46,1085,1086],{"class":48,"line":108},[46,1087,105],{"emptyLinePlaceholder":104},[46,1089,1090],{"class":48,"line":114},[46,1091,1092],{"class":52},"# 只看自定义字段\n",[46,1094,1095,1097,1099,1101],{"class":48,"line":337},[46,1096,24],{"class":59},[46,1098,1056],{"class":62},[46,1100,1077],{"class":62},[46,1102,1103],{"class":122}," --custom-only\n",[46,1105,1106],{"class":48,"line":369},[46,1107,105],{"emptyLinePlaceholder":104},[46,1109,1110],{"class":48,"line":374},[46,1111,1112],{"class":52},"# 创建任务前，先看看需要哪些字段\n",[46,1114,1115,1117,1119,1122,1124,1127],{"class":48,"line":380},[46,1116,24],{"class":59},[46,1118,63],{"class":62},[46,1120,1121],{"class":62}," create-meta",[46,1123,519],{"class":62},[46,1125,1126],{"class":122}," --issue-type",[46,1128,1129],{"class":62}," Task\n",[46,1131,1132],{"class":48,"line":484},[46,1133,105],{"emptyLinePlaceholder":104},[46,1135,1136],{"class":48,"line":498},[46,1137,1138],{"class":52},"# 分页查看可选值\n",[46,1140,1141,1143,1145,1148,1150,1152,1155,1157],{"class":48,"line":503},[46,1142,24],{"class":59},[46,1144,63],{"class":62},[46,1146,1147],{"class":62}," create-meta-values",[46,1149,519],{"class":62},[46,1151,522],{"class":62},[46,1153,1154],{"class":62}," priority",[46,1156,1080],{"class":122},[46,1158,1159],{"class":62}," High\n",[262,1161,1163],{"id":1162},"_5-mcp-写入控制","5. MCP 写入控制",[16,1165,1166],{},"除了全局只读，MCP 还有更细粒度的控制：",[131,1168,1169,1179],{},[134,1170,1171],{},[137,1172,1173,1176],{},[140,1174,1175],{},"环境变量",[140,1177,1178],{},"作用",[147,1180,1181,1191,1201,1211,1221],{},[137,1182,1183,1188],{},[152,1184,1185],{},[43,1186,1187],{},"JIRA_CREATE_OPEN=false",[152,1189,1190],{},"禁用创建\u002F更新工具",[137,1192,1193,1198],{},[152,1194,1195],{},[43,1196,1197],{},"JIRA_DISABLE_DISCLAIMER=true",[152,1199,1200],{},"去掉 AI 自动添加的备注声明",[137,1202,1203,1208],{},[152,1204,1205],{},[43,1206,1207],{},"JIRA_CREATE_SUMMARY_MAX",[152,1209,1210],{},"限制摘要长度",[137,1212,1213,1218],{},[152,1214,1215],{},[43,1216,1217],{},"JIRA_CREATE_DESCRIPTION_MAX",[152,1219,1220],{},"限制描述长度",[137,1222,1223,1228],{},[152,1224,1225],{},[43,1226,1227],{},"JIRA_CREATE_FIELDS_MAX_BYTES",[152,1229,1230],{},"限制创建字段的 JSON 大小",[16,1232,1233],{},"这些限制确保 AI 即使有写入权限也不会\"失控\"——例如防止 AI 往描述里塞几万字的废话。",[262,1235,1237],{"id":1236},"_6-中英双语帮助","6. 中英双语帮助",[16,1239,1240],{},"帮助信息默认中文，也支持一键切英文：",[36,1242,1244],{"className":38,"code":1243,"language":40,"meta":41,"style":41},"# 保存英文偏好\njira_cli set help-language en\n\n# 切回中文\njira_cli set help-language zh\n\n# 临时覆盖\njira_cli --lang en issue search -h\n",[43,1245,1246,1251,1263,1267,1272,1283,1287,1292],{"__ignoreMap":41},[46,1247,1248],{"class":48,"line":49},[46,1249,1250],{"class":52},"# 保存英文偏好\n",[46,1252,1253,1255,1257,1260],{"class":48,"line":56},[46,1254,24],{"class":59},[46,1256,282],{"class":62},[46,1258,1259],{"class":62}," help-language",[46,1261,1262],{"class":62}," en\n",[46,1264,1265],{"class":48,"line":72},[46,1266,105],{"emptyLinePlaceholder":104},[46,1268,1269],{"class":48,"line":85},[46,1270,1271],{"class":52},"# 切回中文\n",[46,1273,1274,1276,1278,1280],{"class":48,"line":101},[46,1275,24],{"class":59},[46,1277,282],{"class":62},[46,1279,1259],{"class":62},[46,1281,1282],{"class":62}," zh\n",[46,1284,1285],{"class":48,"line":108},[46,1286,105],{"emptyLinePlaceholder":104},[46,1288,1289],{"class":48,"line":114},[46,1290,1291],{"class":52},"# 临时覆盖\n",[46,1293,1294,1296,1299,1302,1304,1306],{"class":48,"line":337},[46,1295,24],{"class":59},[46,1297,1298],{"class":122}," --lang",[46,1300,1301],{"class":62}," en",[46,1303,63],{"class":62},[46,1305,66],{"class":62},[46,1307,1308],{"class":122}," -h\n",[11,1310,1311],{"id":1311},"架构一览",[36,1313,1318],{"className":1314,"code":1316,"language":1317},[1315],"language-text","main.go\n  cmd\u002F                         CLI 命令、MCP 启动、访问策略\n  internal\u002Fconfig\u002F             运行时配置、Profile 持久化\n  internal\u002Ftools\u002Fjira_tools\u002F   MCP 输入\u002F输出适配层\n  pkg\u002Fjira\u002F                    可复用的 Jira REST 客户端\n","text",[43,1319,1316],{"__ignoreMap":41},[16,1321,1322],{},"设计原则清晰：",[718,1324,1325,1333,1341,1349],{},[721,1326,1327,1332],{},[27,1328,1329],{},[43,1330,1331],{},"pkg\u002Fjira"," 是纯 Jira 客户端，HTTP、认证、上下文处理都在这里",[721,1334,1335,1340],{},[27,1336,1337],{},[43,1338,1339],{},"cmd"," 只管 Cobra 解析和呈现",[721,1342,1343,1348],{},[27,1344,1345],{},[43,1346,1347],{},"internal\u002Ftools\u002Fjira_tools"," 只管 MCP schema 验证和结果适配",[721,1350,1351,1356],{},[27,1352,1353],{},[43,1354,1355],{},"internal\u002Fconfig"," 拥有 Profile 的全部逻辑，不分散到 cmd",[16,1358,1359,1360,1363],{},"每一层职责单一，新增 Jira 能力时按 ",[43,1361,1362],{},"endpoints.go → Client 方法 → CLI 命令 → MCP tool → 策略注册"," 的路径走就行。",[11,1365,1366],{"id":1366},"和已有方案的对比",[131,1368,1369,1382],{},[134,1370,1371],{},[137,1372,1373,1376,1379],{},[140,1374,1375],{},"方案",[140,1377,1378],{},"适合场景",[140,1380,1381],{},"jira_cli 的区别",[147,1383,1384,1395,1406,1417],{},[137,1385,1386,1389,1392],{},[152,1387,1388],{},"Atlassian Rovo MCP Server",[152,1390,1391],{},"Jira Cloud、官方 OAuth",[152,1393,1394],{},"jira_cli 是本地 stdio，适合内网 Server\u002FDC",[137,1396,1397,1400,1403],{},[152,1398,1399],{},"go-jira \u002F jira-cli 社区版",[152,1401,1402],{},"纯命令行管理",[152,1404,1405],{},"jira_cli 同时提供 MCP server，面向 AI 工作流",[137,1407,1408,1411,1414],{},[152,1409,1410],{},"直接调 REST API",[152,1412,1413],{},"自定义集成",[152,1415,1416],{},"jira_cli 封装了认证、只读策略、字段元数据",[137,1418,1419,1422,1425],{},[152,1420,1421],{},"Jira 网页",[152,1423,1424],{},"日常使用",[152,1426,1427],{},"jira_cli 把 Jira 变成终端里的一行命令",[11,1429,1430],{"id":1430},"总结",[16,1432,1433],{},"如果你的团队满足以下任一条件，jira_cli 值得一试：",[718,1435,1436,1439,1442,1445,1448],{},[721,1437,1438],{},"企业 Jira 部署在内网，想接入 AI 编程工具",[721,1440,1441],{},"想给 AI Agent 只读权限来查 Jira 上下文",[721,1443,1444],{},"需要命令行快速查看\u002F操作 Jira，不想切浏览器",[721,1446,1447],{},"需要管理多个 Jira 实例和多套账号",[721,1449,1450],{},"Windows 用户想要无摩擦的 CLI 体验",[16,1452,1453,1454,1458],{},"GitHub：",[19,1455,1457],{"href":21,"rel":1456},[23],"github.com\u002Fwishesl\u002Fjira_cli"," | MIT 协议 | Go 1.26.3+",[1460,1461,1462],"style",{},"html pre.shiki code .sHbNN, html code.shiki .sHbNN{--shiki-light:#6A737D;--shiki-dark:#6A737D}html pre.shiki code .sw2iP, html code.shiki .sw2iP{--shiki-light:#6F42C1;--shiki-dark:#B392F0}html pre.shiki code .scXbn, html code.shiki .scXbn{--shiki-light:#032F62;--shiki-dark:#9ECBFF}html pre.shiki code .suQ91, html code.shiki .suQ91{--shiki-light:#005CC5;--shiki-dark:#79B8FF}html .light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html.light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-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 .siTax, html code.shiki .siTax{--shiki-light:#D73A49;--shiki-dark:#F97583}html pre.shiki code .sIX_F, html code.shiki .sIX_F{--shiki-light:#24292E;--shiki-dark:#E1E4E8}",{"title":41,"searchDepth":56,"depth":72,"links":1464},[1465,1466,1467,1468,1473,1481,1482,1483],{"id":13,"depth":56,"text":14},{"id":126,"depth":56,"text":126},{"id":215,"depth":56,"text":215},{"id":260,"depth":56,"text":260,"children":1469},[1470,1471,1472],{"id":264,"depth":72,"text":265},{"id":391,"depth":72,"text":392},{"id":531,"depth":72,"text":532},{"id":686,"depth":56,"text":686,"children":1474},[1475,1476,1477,1478,1479,1480],{"id":689,"depth":72,"text":690},{"id":739,"depth":72,"text":740},{"id":931,"depth":72,"text":932},{"id":1035,"depth":72,"text":1036},{"id":1162,"depth":72,"text":1163},{"id":1236,"depth":72,"text":1237},{"id":1311,"depth":56,"text":1311},{"id":1366,"depth":56,"text":1366},{"id":1430,"depth":56,"text":1430},"介绍 jira_cli，一个开源的企业 Jira CLI 命令行工具和MCP，支持 Jira Server \u002F Data Center，自带 MCP stdio server，让 Claude Code、Codex 等 AI 编程工具直接读写 Jira 任务。","入门",false,"md","\u002Fposts\u002Fjira-cli-intro\u002Fimg\u002Fcover.svg",{},"\u002Fposts\u002Fjira-cli-intro","2026-07-17",{"title":5,"description":1484},"posts\u002Fjira-cli-intro\u002Findex",[1495,1496,1497,1498,1499,1500],"Jira","CLI","MCP","Go","AI Agent","效率工具","V1-ms-ndMO09C96sfR-xfjgfdErA4lWoy1XiFen6Izw",[1503,2307,2723,2944,3356,4542,4828,6889,7664,8641],{"id":1504,"title":1505,"author":6,"body":1506,"description":2294,"difficulty":2295,"draft":1486,"extension":1487,"image":2296,"meta":2297,"navigation":104,"path":2298,"pinned":1486,"published":2299,"seo":2300,"series":2301,"seriesOrder":49,"stem":2302,"tags":2303,"updated":2299,"__hash__":2306},"posts\u002Fposts\u002Fdocker-musl-build\u002Findex.md","Docker 编译 musl 可执行文件",{"type":8,"value":1507,"toc":2283},[1508,1511,1514,1517,1743,1746,1789,1792,1810,1813,1963,1966,2099,2102,2105,2163,2166,2280],[11,1509,1510],{"id":1510},"解决问题",[16,1512,1513],{},"用 Go 进行跨平台编译时往往步骤复杂，用 Docker 可以轻松解决。但 Docker 的当前最小镜像 Alpine 其动态链接库采用 musl，与主流的 glibc 不同，往往不能方便执行。",[11,1515,1516],{"id":1516},"查阅资料",[36,1518,1520],{"className":38,"code":1519,"language":40,"meta":41,"style":41},"# 启动容器并挂载当前目录到 `\u002Fapp`\ndocker run -it --rm -v \"$PWD\":\u002Fapp -w \u002Fapp ubuntu:20.04 bash\n\n# 在容器内执行以下操作：\napt-get update && apt-get install -y musl-tools golang-go  # 安装 musl 和 Go\nexport CC=musl-gcc  # 指定 musl 编译器\nexport CGO_ENABLED=1  # 启用 CGO\n# 设置代理\ngo env -w GOPROXY=https:\u002F\u002Fgoproxy.cn,direct\ngo build -ldflags '-linkmode external -extldflags \"-static\"' -o myapp  # 静态编译\nexit  # 退出容器\n\n# 验证结果（在宿主机执行）\nfile myapp  # 应显示 \"statically linked\"\nldd myapp   # 应显示 \"not a dynamic executable\"\n\nwget https:\u002F\u002Fgo.dev\u002Fdl\u002Fgo1.21.linux-amd64.tar.gz\n\nENV PATH=\"\u002Fdata\u002Fgo\u002Fbin:$PATH\"\n",[43,1521,1522,1527,1565,1569,1574,1601,1618,1633,1638,1650,1672,1680,1684,1689,1699,1709,1714,1723,1728],{"__ignoreMap":41},[46,1523,1524],{"class":48,"line":49},[46,1525,1526],{"class":52},"# 启动容器并挂载当前目录到 `\u002Fapp`\n",[46,1528,1529,1532,1535,1538,1541,1544,1547,1550,1553,1556,1559,1562],{"class":48,"line":56},[46,1530,1531],{"class":59},"docker",[46,1533,1534],{"class":62}," run",[46,1536,1537],{"class":122}," -it",[46,1539,1540],{"class":122}," --rm",[46,1542,1543],{"class":122}," -v",[46,1545,1546],{"class":62}," \"",[46,1548,1549],{"class":319},"$PWD",[46,1551,1552],{"class":62},"\":\u002Fapp",[46,1554,1555],{"class":122}," -w",[46,1557,1558],{"class":62}," \u002Fapp",[46,1560,1561],{"class":62}," ubuntu:20.04",[46,1563,1564],{"class":62}," bash\n",[46,1566,1567],{"class":48,"line":72},[46,1568,105],{"emptyLinePlaceholder":104},[46,1570,1571],{"class":48,"line":85},[46,1572,1573],{"class":52},"# 在容器内执行以下操作：\n",[46,1575,1576,1579,1582,1585,1587,1589,1592,1595,1598],{"class":48,"line":101},[46,1577,1578],{"class":59},"apt-get",[46,1580,1581],{"class":62}," update",[46,1583,1584],{"class":319}," && ",[46,1586,1578],{"class":59},[46,1588,233],{"class":62},[46,1590,1591],{"class":122}," -y",[46,1593,1594],{"class":62}," musl-tools",[46,1596,1597],{"class":62}," golang-go",[46,1599,1600],{"class":52},"  # 安装 musl 和 Go\n",[46,1602,1603,1606,1609,1612,1615],{"class":48,"line":108},[46,1604,1605],{"class":312},"export",[46,1607,1608],{"class":319}," CC",[46,1610,1611],{"class":312},"=",[46,1613,1614],{"class":319},"musl-gcc  ",[46,1616,1617],{"class":52},"# 指定 musl 编译器\n",[46,1619,1620,1622,1625,1627,1630],{"class":48,"line":114},[46,1621,1605],{"class":312},[46,1623,1624],{"class":319}," CGO_ENABLED",[46,1626,1611],{"class":312},[46,1628,1629],{"class":122},"1",[46,1631,1632],{"class":52},"  # 启用 CGO\n",[46,1634,1635],{"class":48,"line":337},[46,1636,1637],{"class":52},"# 设置代理\n",[46,1639,1640,1642,1645,1647],{"class":48,"line":369},[46,1641,230],{"class":59},[46,1643,1644],{"class":62}," env",[46,1646,1555],{"class":122},[46,1648,1649],{"class":62}," GOPROXY=https:\u002F\u002Fgoproxy.cn,direct\n",[46,1651,1652,1654,1657,1660,1663,1666,1669],{"class":48,"line":374},[46,1653,230],{"class":59},[46,1655,1656],{"class":62}," build",[46,1658,1659],{"class":122}," -ldflags",[46,1661,1662],{"class":62}," '-linkmode external -extldflags \"-static\"'",[46,1664,1665],{"class":122}," -o",[46,1667,1668],{"class":62}," myapp",[46,1670,1671],{"class":52},"  # 静态编译\n",[46,1673,1674,1677],{"class":48,"line":380},[46,1675,1676],{"class":122},"exit",[46,1678,1679],{"class":52},"  # 退出容器\n",[46,1681,1682],{"class":48,"line":484},[46,1683,105],{"emptyLinePlaceholder":104},[46,1685,1686],{"class":48,"line":498},[46,1687,1688],{"class":52},"# 验证结果（在宿主机执行）\n",[46,1690,1691,1694,1696],{"class":48,"line":503},[46,1692,1693],{"class":59},"file",[46,1695,1668],{"class":62},[46,1697,1698],{"class":52},"  # 应显示 \"statically linked\"\n",[46,1700,1701,1704,1706],{"class":48,"line":509},[46,1702,1703],{"class":59},"ldd",[46,1705,1668],{"class":62},[46,1707,1708],{"class":52},"   # 应显示 \"not a dynamic executable\"\n",[46,1710,1712],{"class":48,"line":1711},16,[46,1713,105],{"emptyLinePlaceholder":104},[46,1715,1717,1720],{"class":48,"line":1716},17,[46,1718,1719],{"class":59},"wget",[46,1721,1722],{"class":62}," https:\u002F\u002Fgo.dev\u002Fdl\u002Fgo1.21.linux-amd64.tar.gz\n",[46,1724,1726],{"class":48,"line":1725},18,[46,1727,105],{"emptyLinePlaceholder":104},[46,1729,1731,1734,1737,1740],{"class":48,"line":1730},19,[46,1732,1733],{"class":59},"ENV",[46,1735,1736],{"class":62}," PATH=\"\u002Fdata\u002Fgo\u002Fbin:",[46,1738,1739],{"class":319},"$PATH",[46,1741,1742],{"class":62},"\"\n",[262,1744,1745],{"id":1745},"遇到问题",[1747,1748,1749,1775],"ol",{},[721,1750,1751,1754,1755,1758,1761,1762,1765,1767,1768,1770],{},[43,1752,1753],{},"go mod tidy"," 时报错 ",[43,1756,1757],{},"tls: failed to verify certificate: x509: certificate",[1759,1760],"br",{},"解决：",[43,1763,1764],{},"RUN apt-get update && apt-get install -y ca-certificates && rm -rf \u002Fvar\u002Flib\u002Fapt\u002Flists\u002F*",[1759,1766],{},"基础镜像的证书未更新，导致 https 的请求出现类似错误。",[1759,1769],{},[1771,1772],"img",{"alt":1773,"src":1774},"image-20250327100813512","\u002Fposts\u002Fdocker-musl-build\u002Fimg\u002Fimage-20250327100813512.png",[721,1776,1777,1778,1781,1782,1784,1785,1788],{},"直接安装 ",[43,1779,1780],{},"golang-go"," 其版本过低",[1759,1783],{},"解决: ",[43,1786,1787],{},"PATH=\"\u002Fdata\u002Fgo\u002Fbin:$PATH\""," 手动指定环境",[11,1790,1791],{"id":1791},"最终方案",[1747,1793,1794,1801,1804],{},[721,1795,1796,1797,1800],{},"下载 ",[43,1798,1799],{},"go1.24.1.linux-amd64.tar.gz"," 解压",[721,1802,1803],{},"编写 Dockerfile",[721,1805,1806,1807],{},"执行 ",[43,1808,1809],{},"docker build -t go-build:v1 .",[16,1811,1812],{},"Dockerfile 内容：",[36,1814,1818],{"className":1815,"code":1816,"language":1817,"meta":41,"style":41},"language-dockerfile shiki shiki-themes github-light github-dark","FROM ubuntu:20.04\n\nLABEL org.opencontainers.image.authors=\"sutong\"\n\nCOPY go\u002F \u002Fgo\u002F\n\nENV PATH=\"\u002Fgo\u002Fbin:$PATH\"\n\nRUN apt-get update && apt-get install -y musl-tools\n\n## go mod tidy 时报错 tls: failed to verify certificate: x509: certificate\nRUN apt-get update && apt-get install -y ca-certificates && rm -rf \u002Fvar\u002Flib\u002Fapt\u002Flists\u002F*\n# 指定 musl 编译器\nENV CC=musl-gcc\n# 启用 CGO\nENV CGO_ENABLED=1\n\nVOLUME \u002Fdata\n\nWORKDIR \u002Fdata\n\nCMD [\"\u002Fbin\u002Fbash\"]\n","dockerfile",[43,1819,1820,1828,1832,1843,1847,1855,1859,1869,1873,1881,1885,1890,1897,1901,1908,1913,1920,1924,1932,1936,1944,1949],{"__ignoreMap":41},[46,1821,1822,1825],{"class":48,"line":49},[46,1823,1824],{"class":312},"FROM",[46,1826,1827],{"class":319}," ubuntu:20.04\n",[46,1829,1830],{"class":48,"line":56},[46,1831,105],{"emptyLinePlaceholder":104},[46,1833,1834,1837,1840],{"class":48,"line":72},[46,1835,1836],{"class":312},"LABEL",[46,1838,1839],{"class":319}," org.opencontainers.image.authors=",[46,1841,1842],{"class":62},"\"sutong\"\n",[46,1844,1845],{"class":48,"line":85},[46,1846,105],{"emptyLinePlaceholder":104},[46,1848,1849,1852],{"class":48,"line":101},[46,1850,1851],{"class":312},"COPY",[46,1853,1854],{"class":319}," go\u002F \u002Fgo\u002F\n",[46,1856,1857],{"class":48,"line":108},[46,1858,105],{"emptyLinePlaceholder":104},[46,1860,1861,1863,1866],{"class":48,"line":114},[46,1862,1733],{"class":312},[46,1864,1865],{"class":319}," PATH=",[46,1867,1868],{"class":62},"\"\u002Fgo\u002Fbin:$PATH\"\n",[46,1870,1871],{"class":48,"line":337},[46,1872,105],{"emptyLinePlaceholder":104},[46,1874,1875,1878],{"class":48,"line":369},[46,1876,1877],{"class":312},"RUN",[46,1879,1880],{"class":319}," apt-get update && apt-get install -y musl-tools\n",[46,1882,1883],{"class":48,"line":374},[46,1884,105],{"emptyLinePlaceholder":104},[46,1886,1887],{"class":48,"line":380},[46,1888,1889],{"class":52},"## go mod tidy 时报错 tls: failed to verify certificate: x509: certificate\n",[46,1891,1892,1894],{"class":48,"line":484},[46,1893,1877],{"class":312},[46,1895,1896],{"class":319}," apt-get update && apt-get install -y ca-certificates && rm -rf \u002Fvar\u002Flib\u002Fapt\u002Flists\u002F*\n",[46,1898,1899],{"class":48,"line":498},[46,1900,1617],{"class":52},[46,1902,1903,1905],{"class":48,"line":503},[46,1904,1733],{"class":312},[46,1906,1907],{"class":319}," CC=musl-gcc\n",[46,1909,1910],{"class":48,"line":509},[46,1911,1912],{"class":52},"# 启用 CGO\n",[46,1914,1915,1917],{"class":48,"line":1711},[46,1916,1733],{"class":312},[46,1918,1919],{"class":319}," CGO_ENABLED=1\n",[46,1921,1922],{"class":48,"line":1716},[46,1923,105],{"emptyLinePlaceholder":104},[46,1925,1926,1929],{"class":48,"line":1725},[46,1927,1928],{"class":312},"VOLUME",[46,1930,1931],{"class":319}," \u002Fdata\n",[46,1933,1934],{"class":48,"line":1730},[46,1935,105],{"emptyLinePlaceholder":104},[46,1937,1939,1942],{"class":48,"line":1938},20,[46,1940,1941],{"class":312},"WORKDIR",[46,1943,1931],{"class":319},[46,1945,1947],{"class":48,"line":1946},21,[46,1948,105],{"emptyLinePlaceholder":104},[46,1950,1952,1955,1958,1961],{"class":48,"line":1951},22,[46,1953,1954],{"class":312},"CMD",[46,1956,1957],{"class":319}," [",[46,1959,1960],{"class":62},"\"\u002Fbin\u002Fbash\"",[46,1962,665],{"class":319},[262,1964,1965],{"id":1965},"构建",[36,1967,1969],{"className":38,"code":1968,"language":40,"meta":41,"style":41},"# docker build -t go-build:v1 .\n\n# 直接执行编译 编译后删除\ndocker run --rm -v .:\u002Fdata go-build:v1 go build -o app_musl\n\n# 构建编译环境\ndocker run -it -d -v .:\u002Fdata --name \u003C容器名称> go-build:v1 \u002Fbin\u002Fbash\n# eg：docker run -it -d -v .:\u002Fdata --name logbus-build go-build:v1 \u002Fbin\u002Fbash\n\ndocker exec -it \u003C容器名称> go build -o \u003Cmyapp>\n# eg：docker exec -it logbus-build go build -o logbus_musl_1\n",[43,1970,1971,1976,1980,1985,2011,2015,2020,2052,2057,2061,2094],{"__ignoreMap":41},[46,1972,1973],{"class":48,"line":49},[46,1974,1975],{"class":52},"# docker build -t go-build:v1 .\n",[46,1977,1978],{"class":48,"line":56},[46,1979,105],{"emptyLinePlaceholder":104},[46,1981,1982],{"class":48,"line":72},[46,1983,1984],{"class":52},"# 直接执行编译 编译后删除\n",[46,1986,1987,1989,1991,1993,1995,1998,2001,2004,2006,2008],{"class":48,"line":85},[46,1988,1531],{"class":59},[46,1990,1534],{"class":62},[46,1992,1540],{"class":122},[46,1994,1543],{"class":122},[46,1996,1997],{"class":62}," .:\u002Fdata",[46,1999,2000],{"class":62}," go-build:v1",[46,2002,2003],{"class":62}," go",[46,2005,1656],{"class":62},[46,2007,1665],{"class":122},[46,2009,2010],{"class":62}," app_musl\n",[46,2012,2013],{"class":48,"line":101},[46,2014,105],{"emptyLinePlaceholder":104},[46,2016,2017],{"class":48,"line":108},[46,2018,2019],{"class":52},"# 构建编译环境\n",[46,2021,2022,2024,2026,2028,2031,2033,2035,2037,2039,2042,2045,2047,2049],{"class":48,"line":114},[46,2023,1531],{"class":59},[46,2025,1534],{"class":62},[46,2027,1537],{"class":122},[46,2029,2030],{"class":122}," -d",[46,2032,1543],{"class":122},[46,2034,1997],{"class":62},[46,2036,555],{"class":122},[46,2038,313],{"class":312},[46,2040,2041],{"class":62},"容器名",[46,2043,2044],{"class":319},"称",[46,2046,323],{"class":312},[46,2048,2000],{"class":62},[46,2050,2051],{"class":62}," \u002Fbin\u002Fbash\n",[46,2053,2054],{"class":48,"line":337},[46,2055,2056],{"class":52},"# eg：docker run -it -d -v .:\u002Fdata --name logbus-build go-build:v1 \u002Fbin\u002Fbash\n",[46,2058,2059],{"class":48,"line":369},[46,2060,105],{"emptyLinePlaceholder":104},[46,2062,2063,2065,2068,2070,2072,2074,2076,2078,2080,2082,2084,2086,2089,2091],{"class":48,"line":374},[46,2064,1531],{"class":59},[46,2066,2067],{"class":62}," exec",[46,2069,1537],{"class":122},[46,2071,313],{"class":312},[46,2073,2041],{"class":62},[46,2075,2044],{"class":319},[46,2077,323],{"class":312},[46,2079,2003],{"class":62},[46,2081,1656],{"class":62},[46,2083,1665],{"class":122},[46,2085,313],{"class":312},[46,2087,2088],{"class":62},"myap",[46,2090,16],{"class":319},[46,2092,2093],{"class":312},">\n",[46,2095,2096],{"class":48,"line":380},[46,2097,2098],{"class":52},"# eg：docker exec -it logbus-build go build -o logbus_musl_1\n",[262,2100,2101],{"id":2101},"额外注意",[16,2103,2104],{},"若没科学上网需要设置代理：",[36,2106,2108],{"className":38,"code":2107,"language":40,"meta":41,"style":41},"docker exec -it \u003C容器名称> \u002Fbin\u002Fbash\ngo env -w GOPROXY=https:\u002F\u002Fgoproxy.cn,direct\ngo build -ldflags '-linkmode external -extldflags \"-static\"' -o myapp  # 静态编译\ngo build # musl 库编译\n",[43,2109,2110,2128,2138,2154],{"__ignoreMap":41},[46,2111,2112,2114,2116,2118,2120,2122,2124,2126],{"class":48,"line":49},[46,2113,1531],{"class":59},[46,2115,2067],{"class":62},[46,2117,1537],{"class":122},[46,2119,313],{"class":312},[46,2121,2041],{"class":62},[46,2123,2044],{"class":319},[46,2125,323],{"class":312},[46,2127,2051],{"class":62},[46,2129,2130,2132,2134,2136],{"class":48,"line":56},[46,2131,230],{"class":59},[46,2133,1644],{"class":62},[46,2135,1555],{"class":122},[46,2137,1649],{"class":62},[46,2139,2140,2142,2144,2146,2148,2150,2152],{"class":48,"line":72},[46,2141,230],{"class":59},[46,2143,1656],{"class":62},[46,2145,1659],{"class":122},[46,2147,1662],{"class":62},[46,2149,1665],{"class":122},[46,2151,1668],{"class":62},[46,2153,1671],{"class":52},[46,2155,2156,2158,2160],{"class":48,"line":85},[46,2157,230],{"class":59},[46,2159,1656],{"class":62},[46,2161,2162],{"class":52}," # musl 库编译\n",[262,2164,2165],{"id":2165},"运行环境示例",[36,2167,2169],{"className":1815,"code":2168,"language":1817,"meta":41,"style":41},"FROM alpine:latest\n\nLABEL org.opencontainers.image.authors=\"sutong\"\n\nCOPY glog_musl \u002Fapp\u002F\n\nCOPY conf.ini \u002Fdata\u002F\nCOPY ipname.ini \u002Fdata\u002F\n\nWORKDIR \u002Fapp\n\nRUN chmod 777 \u002Fapp\u002Fglog_musl\n\nCMD [\".\u002Fglog_musl\", \"logsweb\", \"\u002Fdata\u002Fconf.ini\"]\n\n# docker build -t glog:v1 .\n# docker run -p 6801:6801 glog:v1\n",[43,2170,2171,2178,2182,2190,2194,2201,2205,2212,2219,2223,2230,2234,2241,2245,2266,2270,2275],{"__ignoreMap":41},[46,2172,2173,2175],{"class":48,"line":49},[46,2174,1824],{"class":312},[46,2176,2177],{"class":319}," alpine:latest\n",[46,2179,2180],{"class":48,"line":56},[46,2181,105],{"emptyLinePlaceholder":104},[46,2183,2184,2186,2188],{"class":48,"line":72},[46,2185,1836],{"class":312},[46,2187,1839],{"class":319},[46,2189,1842],{"class":62},[46,2191,2192],{"class":48,"line":85},[46,2193,105],{"emptyLinePlaceholder":104},[46,2195,2196,2198],{"class":48,"line":101},[46,2197,1851],{"class":312},[46,2199,2200],{"class":319}," glog_musl \u002Fapp\u002F\n",[46,2202,2203],{"class":48,"line":108},[46,2204,105],{"emptyLinePlaceholder":104},[46,2206,2207,2209],{"class":48,"line":114},[46,2208,1851],{"class":312},[46,2210,2211],{"class":319}," conf.ini \u002Fdata\u002F\n",[46,2213,2214,2216],{"class":48,"line":337},[46,2215,1851],{"class":312},[46,2217,2218],{"class":319}," ipname.ini \u002Fdata\u002F\n",[46,2220,2221],{"class":48,"line":369},[46,2222,105],{"emptyLinePlaceholder":104},[46,2224,2225,2227],{"class":48,"line":374},[46,2226,1941],{"class":312},[46,2228,2229],{"class":319}," \u002Fapp\n",[46,2231,2232],{"class":48,"line":380},[46,2233,105],{"emptyLinePlaceholder":104},[46,2235,2236,2238],{"class":48,"line":484},[46,2237,1877],{"class":312},[46,2239,2240],{"class":319}," chmod 777 \u002Fapp\u002Fglog_musl\n",[46,2242,2243],{"class":48,"line":498},[46,2244,105],{"emptyLinePlaceholder":104},[46,2246,2247,2249,2251,2254,2256,2259,2261,2264],{"class":48,"line":503},[46,2248,1954],{"class":312},[46,2250,1957],{"class":319},[46,2252,2253],{"class":62},"\".\u002Fglog_musl\"",[46,2255,659],{"class":319},[46,2257,2258],{"class":62},"\"logsweb\"",[46,2260,659],{"class":319},[46,2262,2263],{"class":62},"\"\u002Fdata\u002Fconf.ini\"",[46,2265,665],{"class":319},[46,2267,2268],{"class":48,"line":509},[46,2269,105],{"emptyLinePlaceholder":104},[46,2271,2272],{"class":48,"line":1711},[46,2273,2274],{"class":52},"# docker build -t glog:v1 .\n",[46,2276,2277],{"class":48,"line":1716},[46,2278,2279],{"class":52},"# docker run -p 6801:6801 glog:v1\n",[1460,2281,2282],{},"html pre.shiki code .sHbNN, html code.shiki .sHbNN{--shiki-light:#6A737D;--shiki-dark:#6A737D}html pre.shiki code .sw2iP, html code.shiki .sw2iP{--shiki-light:#6F42C1;--shiki-dark:#B392F0}html pre.shiki code .scXbn, html code.shiki .scXbn{--shiki-light:#032F62;--shiki-dark:#9ECBFF}html pre.shiki code .suQ91, html code.shiki .suQ91{--shiki-light:#005CC5;--shiki-dark:#79B8FF}html pre.shiki code .sIX_F, html code.shiki .sIX_F{--shiki-light:#24292E;--shiki-dark:#E1E4E8}html pre.shiki code .siTax, html code.shiki .siTax{--shiki-light:#D73A49;--shiki-dark:#F97583}html .light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html.light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-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":41,"searchDepth":56,"depth":72,"links":2284},[2285,2286,2289],{"id":1510,"depth":56,"text":1510},{"id":1516,"depth":56,"text":1516,"children":2287},[2288],{"id":1745,"depth":72,"text":1745},{"id":1791,"depth":56,"text":1791,"children":2290},[2291,2292,2293],{"id":1965,"depth":72,"text":1965},{"id":2101,"depth":72,"text":2101},{"id":2165,"depth":72,"text":2165},"解决 Go 语言跨平台编译问题，使用 Docker + musl 编译静态链接的可执行文件。","进阶","\u002Fposts\u002Fdocker-musl-build\u002Fimg\u002Fcover.svg",{},"\u002Fposts\u002Fdocker-musl-build","2025-03-27",{"title":1505,"description":2294},"Docker 实践笔记","posts\u002Fdocker-musl-build\u002Findex",[2304,1498,2305],"Docker","运维","YLtdOOc-gMVDSNjE-ntKXz-7hff-VtvMB2dB5RXNi1o",{"id":2308,"title":2309,"author":6,"body":2310,"description":2713,"difficulty":1485,"draft":1486,"extension":1487,"image":2714,"meta":2715,"navigation":104,"path":2716,"pinned":1486,"published":2717,"seo":2718,"series":2301,"seriesOrder":56,"stem":2719,"tags":2720,"updated":2717,"__hash__":2722},"posts\u002Fposts\u002Fdocker-user-start\u002Findex.md","Docker 构建时指定用户启动",{"type":8,"value":2311,"toc":2703},[2312,2315,2318,2321,2325,2328,2345,2349,2382,2386,2392,2440,2443,2662,2664,2667,2697,2700],[11,2313,2314],{"id":2314},"前言",[16,2316,2317],{},"我们在 Linux 使用应用时，往往不会直接使用 root 用户作为应用启动项，但 Dockerfile 默认就是 root 用户，导致挂载生成的文件路径往往是 root 权限，非 root 用户访问就很不方便。",[11,2319,2320],{"id":2320},"解决方案",[262,2322,2324],{"id":2323},"_1-安装用户管理工具","1. 安装用户管理工具",[16,2326,2327],{},"若镜像非自带用户管理：",[36,2329,2331],{"className":1815,"code":2330,"language":1817,"meta":41,"style":41},"# 权限管理\nRUN apk add --no-cache shadow\n",[43,2332,2333,2338],{"__ignoreMap":41},[46,2334,2335],{"class":48,"line":49},[46,2336,2337],{"class":52},"# 权限管理\n",[46,2339,2340,2342],{"class":48,"line":56},[46,2341,1877],{"class":312},[46,2343,2344],{"class":319}," apk add --no-cache shadow\n",[262,2346,2348],{"id":2347},"_2-创建用户并修改权限","2. 创建用户并修改权限",[36,2350,2352],{"className":1815,"code":2351,"language":1817,"meta":41,"style":41},"# 创建用户组和用户\nRUN addgroup -S \u003Capp_user_group> && adduser -S -G \u003Capp_user_group> \u003Capp_user>\n\n# 修改目录权限\nRUN chown -R \u003Capp_user_group>:\u003Capp_user> \u002Fdata\n",[43,2353,2354,2359,2366,2370,2375],{"__ignoreMap":41},[46,2355,2356],{"class":48,"line":49},[46,2357,2358],{"class":52},"# 创建用户组和用户\n",[46,2360,2361,2363],{"class":48,"line":56},[46,2362,1877],{"class":312},[46,2364,2365],{"class":319}," addgroup -S \u003Capp_user_group> && adduser -S -G \u003Capp_user_group> \u003Capp_user>\n",[46,2367,2368],{"class":48,"line":72},[46,2369,105],{"emptyLinePlaceholder":104},[46,2371,2372],{"class":48,"line":85},[46,2373,2374],{"class":52},"# 修改目录权限\n",[46,2376,2377,2379],{"class":48,"line":101},[46,2378,1877],{"class":312},[46,2380,2381],{"class":319}," chown -R \u003Capp_user_group>:\u003Capp_user> \u002Fdata\n",[262,2383,2385],{"id":2384},"_3-指定用户启动","3. 指定用户启动",[16,2387,2388,2389,2391],{},"执行 docker 时加 ",[43,2390,746],{}," 参数：",[36,2393,2395],{"className":38,"code":2394,"language":40,"meta":41,"style":41},"docker run -it --user \u003Capp_user> \u003Capp_image>:\u003Cimage_version>\n",[43,2396,2397],{"__ignoreMap":41},[46,2398,2399,2401,2403,2405,2408,2410,2413,2416,2418,2420,2423,2425,2427,2430,2433,2436,2438],{"class":48,"line":49},[46,2400,1531],{"class":59},[46,2402,1534],{"class":62},[46,2404,1537],{"class":122},[46,2406,2407],{"class":122}," --user",[46,2409,313],{"class":312},[46,2411,2412],{"class":62},"app_use",[46,2414,2415],{"class":319},"r",[46,2417,323],{"class":312},[46,2419,313],{"class":312},[46,2421,2422],{"class":62},"app_imag",[46,2424,352],{"class":319},[46,2426,323],{"class":312},[46,2428,2429],{"class":62},":",[46,2431,2432],{"class":312},"\u003C",[46,2434,2435],{"class":62},"image_versio",[46,2437,320],{"class":319},[46,2439,2093],{"class":312},[11,2441,2442],{"id":2442},"完整示例",[36,2444,2446],{"className":1815,"code":2445,"language":1817,"meta":41,"style":41},"FROM alpine:latest\n\n# 国内源\nRUN sed -i 's\u002Fdl-cdn.alpinelinux.org\u002Fmirrors.ustc.edu.cn\u002Fg' \u002Fetc\u002Fapk\u002Frepositories\n\nRUN apk update --no-cache\n# 权限管理\nRUN apk add --no-cache shadow\n# 调试\nRUN apk add --no-cache bash\n# 设置时区\nRUN apk add --no-cache tzdata\n\nENV TZ=Asia\u002FShanghai\n\n# 创建用户组和用户\nRUN addgroup -S glog && adduser -S -G glog glog\n\nCOPY .\u002Fglog_static_musl_1 \u002Fapp\u002F\n\nRUN mkdir \u002Fdata\n\nVOLUME [ \"\u002Fdata\" ]\nWORKDIR \u002Fdata\n\n# 修改权限\nRUN chown -R glog:glog \u002Fdata\nRUN chown -R glog:glog \u002Fapp\n\n# 指定用户启动\nUSER glog\nCMD [ \"\u002Fapp\u002Fglog_static_musl_1\", \"logsweb\", \".\u002Fconf.ini\" ]\n",[43,2447,2448,2454,2458,2463,2476,2480,2487,2491,2497,2502,2509,2514,2521,2525,2532,2536,2540,2547,2551,2558,2562,2569,2573,2587,2594,2599,2605,2613,2621,2626,2632,2641],{"__ignoreMap":41},[46,2449,2450,2452],{"class":48,"line":49},[46,2451,1824],{"class":312},[46,2453,2177],{"class":319},[46,2455,2456],{"class":48,"line":56},[46,2457,105],{"emptyLinePlaceholder":104},[46,2459,2460],{"class":48,"line":72},[46,2461,2462],{"class":52},"# 国内源\n",[46,2464,2465,2467,2470,2473],{"class":48,"line":85},[46,2466,1877],{"class":312},[46,2468,2469],{"class":319}," sed -i ",[46,2471,2472],{"class":62},"'s\u002Fdl-cdn.alpinelinux.org\u002Fmirrors.ustc.edu.cn\u002Fg'",[46,2474,2475],{"class":319}," \u002Fetc\u002Fapk\u002Frepositories\n",[46,2477,2478],{"class":48,"line":101},[46,2479,105],{"emptyLinePlaceholder":104},[46,2481,2482,2484],{"class":48,"line":108},[46,2483,1877],{"class":312},[46,2485,2486],{"class":319}," apk update --no-cache\n",[46,2488,2489],{"class":48,"line":114},[46,2490,2337],{"class":52},[46,2492,2493,2495],{"class":48,"line":337},[46,2494,1877],{"class":312},[46,2496,2344],{"class":319},[46,2498,2499],{"class":48,"line":369},[46,2500,2501],{"class":52},"# 调试\n",[46,2503,2504,2506],{"class":48,"line":374},[46,2505,1877],{"class":312},[46,2507,2508],{"class":319}," apk add --no-cache bash\n",[46,2510,2511],{"class":48,"line":380},[46,2512,2513],{"class":52},"# 设置时区\n",[46,2515,2516,2518],{"class":48,"line":484},[46,2517,1877],{"class":312},[46,2519,2520],{"class":319}," apk add --no-cache tzdata\n",[46,2522,2523],{"class":48,"line":498},[46,2524,105],{"emptyLinePlaceholder":104},[46,2526,2527,2529],{"class":48,"line":503},[46,2528,1733],{"class":312},[46,2530,2531],{"class":319}," TZ=Asia\u002FShanghai\n",[46,2533,2534],{"class":48,"line":509},[46,2535,105],{"emptyLinePlaceholder":104},[46,2537,2538],{"class":48,"line":1711},[46,2539,2358],{"class":52},[46,2541,2542,2544],{"class":48,"line":1716},[46,2543,1877],{"class":312},[46,2545,2546],{"class":319}," addgroup -S glog && adduser -S -G glog glog\n",[46,2548,2549],{"class":48,"line":1725},[46,2550,105],{"emptyLinePlaceholder":104},[46,2552,2553,2555],{"class":48,"line":1730},[46,2554,1851],{"class":312},[46,2556,2557],{"class":319}," .\u002Fglog_static_musl_1 \u002Fapp\u002F\n",[46,2559,2560],{"class":48,"line":1938},[46,2561,105],{"emptyLinePlaceholder":104},[46,2563,2564,2566],{"class":48,"line":1946},[46,2565,1877],{"class":312},[46,2567,2568],{"class":319}," mkdir \u002Fdata\n",[46,2570,2571],{"class":48,"line":1951},[46,2572,105],{"emptyLinePlaceholder":104},[46,2574,2576,2578,2581,2584],{"class":48,"line":2575},23,[46,2577,1928],{"class":312},[46,2579,2580],{"class":319}," [ ",[46,2582,2583],{"class":62},"\"\u002Fdata\"",[46,2585,2586],{"class":319}," ]\n",[46,2588,2590,2592],{"class":48,"line":2589},24,[46,2591,1941],{"class":312},[46,2593,1931],{"class":319},[46,2595,2597],{"class":48,"line":2596},25,[46,2598,105],{"emptyLinePlaceholder":104},[46,2600,2602],{"class":48,"line":2601},26,[46,2603,2604],{"class":52},"# 修改权限\n",[46,2606,2608,2610],{"class":48,"line":2607},27,[46,2609,1877],{"class":312},[46,2611,2612],{"class":319}," chown -R glog:glog \u002Fdata\n",[46,2614,2616,2618],{"class":48,"line":2615},28,[46,2617,1877],{"class":312},[46,2619,2620],{"class":319}," chown -R glog:glog \u002Fapp\n",[46,2622,2624],{"class":48,"line":2623},29,[46,2625,105],{"emptyLinePlaceholder":104},[46,2627,2629],{"class":48,"line":2628},30,[46,2630,2631],{"class":52},"# 指定用户启动\n",[46,2633,2635,2638],{"class":48,"line":2634},31,[46,2636,2637],{"class":312},"USER",[46,2639,2640],{"class":319}," glog\n",[46,2642,2644,2646,2648,2651,2653,2655,2657,2660],{"class":48,"line":2643},32,[46,2645,1954],{"class":312},[46,2647,2580],{"class":319},[46,2649,2650],{"class":62},"\"\u002Fapp\u002Fglog_static_musl_1\"",[46,2652,659],{"class":319},[46,2654,2258],{"class":62},[46,2656,659],{"class":319},[46,2658,2659],{"class":62},"\".\u002Fconf.ini\"",[46,2661,2586],{"class":319},[11,2663,1430],{"id":1430},[16,2665,2666],{},"关键步骤：",[1747,2668,2669,2675,2685,2691],{},[721,2670,2671,2674],{},[43,2672,2673],{},"apk add --no-cache shadow"," - 安装用户管理工具",[721,2676,2677,2680,2681,2684],{},[43,2678,2679],{},"addgroup"," + ",[43,2682,2683],{},"adduser"," - 创建用户",[721,2686,2687,2690],{},[43,2688,2689],{},"chown -R"," - 修改目录权限",[721,2692,2693,2696],{},[43,2694,2695],{},"USER \u003Cusername>"," - 指定运行用户",[16,2698,2699],{},"这样容器内的应用就不再以 root 身份运行，更加安全。",[1460,2701,2702],{},"html pre.shiki code .sHbNN, html code.shiki .sHbNN{--shiki-light:#6A737D;--shiki-dark:#6A737D}html pre.shiki code .siTax, html code.shiki .siTax{--shiki-light:#D73A49;--shiki-dark:#F97583}html pre.shiki code .sIX_F, html code.shiki .sIX_F{--shiki-light:#24292E;--shiki-dark:#E1E4E8}html .light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html.light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-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 .sw2iP, html code.shiki .sw2iP{--shiki-light:#6F42C1;--shiki-dark:#B392F0}html pre.shiki code .scXbn, html code.shiki .scXbn{--shiki-light:#032F62;--shiki-dark:#9ECBFF}html pre.shiki code .suQ91, html code.shiki .suQ91{--shiki-light:#005CC5;--shiki-dark:#79B8FF}",{"title":41,"searchDepth":56,"depth":72,"links":2704},[2705,2706,2711,2712],{"id":2314,"depth":56,"text":2314},{"id":2320,"depth":56,"text":2320,"children":2707},[2708,2709,2710],{"id":2323,"depth":72,"text":2324},{"id":2347,"depth":72,"text":2348},{"id":2384,"depth":72,"text":2385},{"id":2442,"depth":56,"text":2442},{"id":1430,"depth":56,"text":1430},"解决 Docker 容器默认以 root 用户运行导致的权限问题，指定非 root 用户启动应用。","\u002Fposts\u002Fdocker-user-start\u002Fimg\u002Fcover.svg",{},"\u002Fposts\u002Fdocker-user-start","2025-04-01",{"title":2309,"description":2713},"posts\u002Fdocker-user-start\u002Findex",[2304,2305,2721],"Linux","b4Uyw5ms87xr7NhpIkavHnNbT20oaEPtXz8k1w6UPj0",{"id":2724,"title":2725,"author":6,"body":2726,"description":2934,"difficulty":6,"draft":1486,"extension":1487,"image":2935,"meta":2936,"navigation":104,"path":2937,"pinned":1486,"published":2938,"seo":2939,"series":6,"seriesOrder":6,"stem":2940,"tags":2941,"updated":6,"__hash__":2943},"posts\u002Fposts\u002Fsveltekit-intro\u002Findex.md","SvelteKit 入门：为什么它比你想象的更简单",{"type":8,"value":2727,"toc":2924},[2728,2732,2735,2741,2744,2748,2755,2806,2810,2816,2822,2826,2829,2832,2902,2904,2915,2921],[11,2729,2731],{"id":2730},"什么是-sveltekit","什么是 SvelteKit？",[16,2733,2734],{},"SvelteKit 是 Svelte 的官方全栈框架，类似于 React 之于 Next.js 的关系。它提供了路由、SSR、SSG 等能力，让开发者可以快速构建现代 Web 应用。",[16,2736,2737],{},[1771,2738],{"alt":2739,"src":2740},"SvelteKit Logo","https:\u002F\u002Fraw.githubusercontent.com\u002Fsveltejs\u002Fbranding\u002Fmaster\u002Fsvelte-logo.svg",[11,2742,2743],{"id":2743},"核心特点",[262,2745,2747],{"id":2746},"_1-编译时框架","1. 编译时框架",[16,2749,2750,2751,2754],{},"Svelte 和 React\u002FVue 最大的区别在于：",[27,2752,2753],{},"它在编译阶段就把组件转成原生 JS","，运行时没有虚拟 DOM。",[36,2756,2760],{"className":2757,"code":2758,"language":2759,"meta":41,"style":41},"language-typescript shiki shiki-themes github-light github-dark","\u002F\u002F Svelte 组件（编译后变成直接的 DOM 操作）\nlet count = 0\nfunction increment() {\n  count += 1\n}\n","typescript",[43,2761,2762,2767,2780,2791,2802],{"__ignoreMap":41},[46,2763,2764],{"class":48,"line":49},[46,2765,2766],{"class":52},"\u002F\u002F Svelte 组件（编译后变成直接的 DOM 操作）\n",[46,2768,2769,2772,2775,2777],{"class":48,"line":56},[46,2770,2771],{"class":312},"let",[46,2773,2774],{"class":319}," count ",[46,2776,1611],{"class":312},[46,2778,2779],{"class":122}," 0\n",[46,2781,2782,2785,2788],{"class":48,"line":72},[46,2783,2784],{"class":312},"function",[46,2786,2787],{"class":59}," increment",[46,2789,2790],{"class":319},"() {\n",[46,2792,2793,2796,2799],{"class":48,"line":85},[46,2794,2795],{"class":319},"  count ",[46,2797,2798],{"class":312},"+=",[46,2800,2801],{"class":122}," 1\n",[46,2803,2804],{"class":48,"line":101},[46,2805,680],{"class":319},[262,2807,2809],{"id":2808},"_2-文件路由","2. 文件路由",[16,2811,2812,2815],{},[43,2813,2814],{},"src\u002Froutes\u002F"," 目录结构即 URL，不需要手动配置路由表：",[36,2817,2820],{"className":2818,"code":2819,"language":1317},[1315],"src\u002Froutes\u002F\n├── +page.svelte        → \u002F\n├── about\u002F\n│   └── +page.svelte    → \u002Fabout\n└── posts\u002F\n    ├── +page.svelte    → \u002Fposts\n    └── [slug]\u002F\n        └── +page.svelte → \u002Fposts\u002F:slug\n",[43,2821,2819],{"__ignoreMap":41},[262,2823,2825],{"id":2824},"_3-全栈能力","3. 全栈能力",[16,2827,2828],{},"SvelteKit 内置了服务端渲染（SSR）、静态站点生成（SSG）、API 路由等功能，开箱即用。",[11,2830,2831],{"id":2831},"和其他框架对比",[131,2833,2834,2850],{},[134,2835,2836],{},[137,2837,2838,2841,2844,2847],{},[140,2839,2840],{},"特性",[140,2842,2843],{},"SvelteKit",[140,2845,2846],{},"Next.js",[140,2848,2849],{},"Nuxt",[147,2851,2852,2865,2878,2891],{},[137,2853,2854,2857,2860,2863],{},[152,2855,2856],{},"学习曲线",[152,2858,2859],{},"低",[152,2861,2862],{},"中",[152,2864,2862],{},[137,2866,2867,2870,2873,2876],{},[152,2868,2869],{},"包体积",[152,2871,2872],{},"小",[152,2874,2875],{},"大",[152,2877,2862],{},[137,2879,2880,2883,2886,2889],{},[152,2881,2882],{},"运行时开销",[152,2884,2885],{},"无",[152,2887,2888],{},"有",[152,2890,2888],{},[137,2892,2893,2896,2898,2900],{},[152,2894,2895],{},"生态",[152,2897,2872],{},[152,2899,2875],{},[152,2901,2875],{},[11,2903,1430],{"id":1430},[16,2905,2906,2907,2910,2911,2914],{},"如果你追求",[27,2908,2909],{},"简洁的代码","和",[27,2912,2913],{},"极致的性能","，SvelteKit 是一个非常值得尝试的选择。它的学习曲线平缓，写起来就像在写原生 HTML\u002FCSS\u002FJS。",[16,2916,2917],{},[1771,2918],{"alt":2919,"src":2920},"Svelte 编译过程","https:\u002F\u002Fsvelte.dev\u002Fsvelte-logo-horizontal.svg",[1460,2922,2923],{},"html pre.shiki code .sHbNN, html code.shiki .sHbNN{--shiki-light:#6A737D;--shiki-dark:#6A737D}html pre.shiki code .siTax, html code.shiki .siTax{--shiki-light:#D73A49;--shiki-dark:#F97583}html pre.shiki code .sIX_F, html code.shiki .sIX_F{--shiki-light:#24292E;--shiki-dark:#E1E4E8}html pre.shiki code .suQ91, html code.shiki .suQ91{--shiki-light:#005CC5;--shiki-dark:#79B8FF}html pre.shiki code .sw2iP, html code.shiki .sw2iP{--shiki-light:#6F42C1;--shiki-dark:#B392F0}html .light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html.light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-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":41,"searchDepth":56,"depth":72,"links":2925},[2926,2927,2932,2933],{"id":2730,"depth":56,"text":2731},{"id":2743,"depth":56,"text":2743,"children":2928},[2929,2930,2931],{"id":2746,"depth":72,"text":2747},{"id":2808,"depth":72,"text":2809},{"id":2824,"depth":72,"text":2825},{"id":2831,"depth":56,"text":2831},{"id":1430,"depth":56,"text":1430},"SvelteKit 是一个全栈 Svelte 框架，这篇文章带你了解它的核心概念和优势。","\u002Fposts\u002Fsveltekit-intro\u002Fimg\u002Fcover.jpg",{},"\u002Fposts\u002Fsveltekit-intro","2026-05-30",{"title":2725,"description":2934},"posts\u002Fsveltekit-intro\u002Findex",[2843,2942],"前端框架","vsEGyunpCWR7DiJAdyV1SPDfa57xXLITBI-lKM8UeGQ",{"id":2945,"title":2946,"author":6,"body":2947,"description":3344,"difficulty":1485,"draft":1486,"extension":1487,"image":3345,"meta":3346,"navigation":104,"path":3347,"pinned":104,"published":3348,"seo":3349,"series":3350,"seriesOrder":49,"stem":3351,"tags":3352,"updated":3354,"__hash__":3355},"posts\u002Fposts\u002Fhello-world\u002Findex.md","Hello World - 欢迎来到尼克的小窝",{"type":8,"value":2948,"toc":3335},[2949,2953,2960,2963,2966,2969,3043,3046,3049,3111,3114,3117,3182,3185,3249,3252,3255,3271,3278,3281,3312,3315,3324,3327,3332],[11,2950,2952],{"id":2951},"博客上线啦","博客上线啦！",[16,2954,2955,2956,2959],{},"Hello World！欢迎来到",[27,2957,2958],{},"尼克的小窝","，这是我的第一篇博客文章。",[16,2961,2962],{},"经过一段时间的折腾，这个基于 Nuxt 3 的个人博客终于上线了。在这里记录一下这个项目的技术选型和功能特性。",[11,2964,2965],{"id":2965},"技术栈",[16,2967,2968],{},"本站使用了以下技术：",[131,2970,2971,2981],{},[134,2972,2973],{},[137,2974,2975,2978],{},[140,2976,2977],{},"技术",[140,2979,2980],{},"用途",[147,2982,2983,2993,3003,3013,3023,3033],{},[137,2984,2985,2990],{},[152,2986,2987],{},[27,2988,2989],{},"Nuxt 3",[152,2991,2992],{},"Vue 3 全栈框架，提供 SSG、路由、API 等能力",[137,2994,2995,3000],{},[152,2996,2997],{},[27,2998,2999],{},"TypeScript",[152,3001,3002],{},"类型安全，提升开发体验",[137,3004,3005,3010],{},[152,3006,3007],{},[27,3008,3009],{},"Tailwind CSS v4",[152,3011,3012],{},"原子化 CSS，快速构建 UI",[137,3014,3015,3020],{},[152,3016,3017],{},[27,3018,3019],{},"@nuxt\u002Fcontent",[152,3021,3022],{},"Markdown 驱动的内容管理",[137,3024,3025,3030],{},[152,3026,3027],{},[27,3028,3029],{},"Shiki",[152,3031,3032],{},"代码语法高亮",[137,3034,3035,3040],{},[152,3036,3037],{},[27,3038,3039],{},"Giscus",[152,3041,3042],{},"基于 GitHub Discussions 的评论系统",[11,3044,3045],{"id":3045},"功能特性",[16,3047,3048],{},"目前已实现的功能：",[718,3050,3051,3057,3063,3069,3075,3081,3087,3093,3099,3105],{},[721,3052,3053,3056],{},[27,3054,3055],{},"博客系统"," - Markdown 文章，支持代码高亮、目录导航、搜索",[721,3058,3059,3062],{},[27,3060,3061],{},"暗色模式"," - 跟随系统或手动切换",[721,3064,3065,3068],{},[27,3066,3067],{},"文章搜索"," - 支持标题、描述、正文、标签多维度搜索",[721,3070,3071,3074],{},[27,3072,3073],{},"标签系统"," - 文章标签分类，高频标签快速筛选",[721,3076,3077,3080],{},[27,3078,3079],{},"友链页面"," - 展示友情链接，支持申请",[721,3082,3083,3086],{},[27,3084,3085],{},"赞助页面"," - 微信收款码展示",[721,3088,3089,3092],{},[27,3090,3091],{},"封面制作"," - 在线生成封面图片工具",[721,3094,3095,3098],{},[27,3096,3097],{},"网易云热评"," - 首页随机展示音乐评论",[721,3100,3101,3104],{},[27,3102,3103],{},"访客统计"," - 不蒜子计数器，带数字滚动动画",[721,3106,3107,3110],{},[27,3108,3109],{},"SEO 优化"," - 自动生成站点地图、Open Graph 标签",[11,3112,3113],{"id":3113},"代码示例",[16,3115,3116],{},"本站的配置非常集中，一个文件搞定全站定制：",[36,3118,3120],{"className":2757,"code":3119,"language":2759,"meta":41,"style":41},"\u002F\u002F config\u002Fsite.ts\nexport const siteConfig = {\n  siteName: '尼克的小窝',\n  url: 'https:\u002F\u002Fnixus.top\u002F',\n  avatar: 'https:\u002F\u002Fq2.qlogo.cn\u002Fheadimg_dl?dst_uin=xxx&spec=100',\n  \u002F\u002F ... 改这一个文件，全站生效\n}\n",[43,3121,3122,3127,3143,3153,3163,3173,3178],{"__ignoreMap":41},[46,3123,3124],{"class":48,"line":49},[46,3125,3126],{"class":52},"\u002F\u002F config\u002Fsite.ts\n",[46,3128,3129,3131,3134,3137,3140],{"class":48,"line":56},[46,3130,1605],{"class":312},[46,3132,3133],{"class":312}," const",[46,3135,3136],{"class":122}," siteConfig",[46,3138,3139],{"class":312}," =",[46,3141,3142],{"class":319}," {\n",[46,3144,3145,3148,3151],{"class":48,"line":72},[46,3146,3147],{"class":319},"  siteName: ",[46,3149,3150],{"class":62},"'尼克的小窝'",[46,3152,645],{"class":319},[46,3154,3155,3158,3161],{"class":48,"line":85},[46,3156,3157],{"class":319},"  url: ",[46,3159,3160],{"class":62},"'https:\u002F\u002Fnixus.top\u002F'",[46,3162,645],{"class":319},[46,3164,3165,3168,3171],{"class":48,"line":101},[46,3166,3167],{"class":319},"  avatar: ",[46,3169,3170],{"class":62},"'https:\u002F\u002Fq2.qlogo.cn\u002Fheadimg_dl?dst_uin=xxx&spec=100'",[46,3172,645],{"class":319},[46,3174,3175],{"class":48,"line":108},[46,3176,3177],{"class":52},"  \u002F\u002F ... 改这一个文件，全站生效\n",[46,3179,3180],{"class":48,"line":114},[46,3181,680],{"class":319},[16,3183,3184],{},"文章的 frontmatter 也很简洁：",[36,3186,3190],{"className":3187,"code":3188,"language":3189,"meta":41,"style":41},"language-yaml shiki shiki-themes github-light github-dark","---\ntitle: 文章标题\npublished: 2026-05-31T08:00:00\ndescription: 文章简介\ntags: ['Nuxt', 'TypeScript']\n---\n","yaml",[43,3191,3192,3197,3208,3218,3228,3245],{"__ignoreMap":41},[46,3193,3194],{"class":48,"line":49},[46,3195,3196],{"class":59},"---\n",[46,3198,3199,3203,3205],{"class":48,"line":56},[46,3200,3202],{"class":3201},"sbB4o","title",[46,3204,639],{"class":319},[46,3206,3207],{"class":62},"文章标题\n",[46,3209,3210,3213,3215],{"class":48,"line":72},[46,3211,3212],{"class":3201},"published",[46,3214,639],{"class":319},[46,3216,3217],{"class":122},"2026-05-31T08:00:00\n",[46,3219,3220,3223,3225],{"class":48,"line":85},[46,3221,3222],{"class":3201},"description",[46,3224,639],{"class":319},[46,3226,3227],{"class":62},"文章简介\n",[46,3229,3230,3233,3235,3238,3240,3243],{"class":48,"line":101},[46,3231,3232],{"class":3201},"tags",[46,3234,653],{"class":319},[46,3236,3237],{"class":62},"'Nuxt'",[46,3239,659],{"class":319},[46,3241,3242],{"class":62},"'TypeScript'",[46,3244,665],{"class":319},[46,3246,3247],{"class":48,"line":108},[46,3248,3196],{"class":59},[11,3250,3251],{"id":3251},"部署",[16,3253,3254],{},"本站使用 SSG 静态生成，构建后直接部署到静态托管即可：",[36,3256,3258],{"className":38,"code":3257,"language":40,"meta":41,"style":41},"npx nuxi generate\n",[43,3259,3260],{"__ignoreMap":41},[46,3261,3262,3265,3268],{"class":48,"line":49},[46,3263,3264],{"class":59},"npx",[46,3266,3267],{"class":62}," nuxi",[46,3269,3270],{"class":62}," generate\n",[16,3272,3273,3274,3277],{},"构建产物在 ",[43,3275,3276],{},".output\u002Fpublic"," 目录下，可以直接丢到 Nginx、Vercel、Netlify 等平台。",[11,3279,3280],{"id":3280},"后续计划",[718,3282,3285,3294,3300,3306],{"className":3283},[3284],"contains-task-list",[721,3286,3289,3293],{"className":3287},[3288],"task-list-item",[3290,3291],"input",{"disabled":104,"type":3292},"checkbox"," RSS 订阅",[721,3295,3297,3299],{"className":3296},[3288],[3290,3298],{"disabled":104,"type":3292}," 文章目录优化",[721,3301,3303,3305],{"className":3302},[3288],[3290,3304],{"disabled":104,"type":3292}," 更多小工具",[721,3307,3309,3311],{"className":3308},[3288],[3290,3310],{"disabled":104,"type":3292}," 持续输出内容",[11,3313,3314],{"id":3314},"致谢",[16,3316,3317,3318,3323],{},"感谢 ",[19,3319,3322],{"href":3320,"rel":3321},"https:\u002F\u002F2x.nz",[23],"二叉树树"," 的开源项目，本站的 UI 设计参考了他的项目。",[3325,3326],"hr",{},[16,3328,3329],{},[27,3330,3331],{},"感谢阅读！如果觉得不错，欢迎点个 Star 或者留个评论 (☞ﾟヮﾟ)☞",[1460,3333,3334],{},"html pre.shiki code .sHbNN, html code.shiki .sHbNN{--shiki-light:#6A737D;--shiki-dark:#6A737D}html pre.shiki code .siTax, html code.shiki .siTax{--shiki-light:#D73A49;--shiki-dark:#F97583}html pre.shiki code .suQ91, html code.shiki .suQ91{--shiki-light:#005CC5;--shiki-dark:#79B8FF}html pre.shiki code .sIX_F, html code.shiki .sIX_F{--shiki-light:#24292E;--shiki-dark:#E1E4E8}html pre.shiki code .scXbn, html code.shiki .scXbn{--shiki-light:#032F62;--shiki-dark:#9ECBFF}html .light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html.light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-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 .sw2iP, html code.shiki .sw2iP{--shiki-light:#6F42C1;--shiki-dark:#B392F0}html pre.shiki code .sbB4o, html code.shiki .sbB4o{--shiki-light:#22863A;--shiki-dark:#85E89D}",{"title":41,"searchDepth":56,"depth":72,"links":3336},[3337,3338,3339,3340,3341,3342,3343],{"id":2951,"depth":56,"text":2952},{"id":2965,"depth":56,"text":2965},{"id":3045,"depth":56,"text":3045},{"id":3113,"depth":56,"text":3113},{"id":3251,"depth":56,"text":3251},{"id":3280,"depth":56,"text":3280},{"id":3314,"depth":56,"text":3314},"博客上线啦！这篇文章介绍本站的技术架构、功能特性和搭建过程中的一些思考。","\u002Fposts\u002Fhello-world\u002Fimg\u002Fcover.jpg",{},"\u002Fposts\u002Fhello-world","2026-05-31T08:00:00",{"title":2946,"description":3344},"Nuxt 博客开发","posts\u002Fhello-world\u002Findex",[3353,2849],"博客","2026-05-31","s0QPHV3aEea6nZra0FNTkxjNO-kLBsrYzuTPbNDtFvI",{"id":3357,"title":3358,"author":6,"body":3359,"description":4531,"difficulty":2295,"draft":1486,"extension":1487,"image":4532,"meta":4533,"navigation":104,"path":4534,"pinned":1486,"published":4535,"seo":4536,"series":3350,"seriesOrder":56,"stem":4537,"tags":4538,"updated":3354,"__hash__":4541},"posts\u002Fposts\u002Fnuxt-content-lessons\u002Findex.md","Nuxt Content v3 踩坑记录：目录、图片与那些反直觉的设计",{"type":8,"value":3360,"toc":4521},[3361,3365,3368,3371,3375,3378,3389,3392,3399,3464,3471,3473,3480,3758,3768,3772,3775,3778,3804,3818,3821,3844,3850,3853,3856,3859,3864,3871,4098,4103,4147,4151,4154,4161,4164,4213,4219,4448,4458,4460,4512,4515,4518],[3362,3363,3364],"h1",{"id":3364},"背景",[16,3366,3367],{},"最近在用 Nuxt 3 + Nuxt Content v3 搭建一个个人博客，参考了一个 SvelteKit 项目（svaf）的架构。原以为换个框架只是语法差异，没想到在几个关键功能上踩了不少坑。",[16,3369,3370],{},"这篇文章记录了三个核心问题的排查过程和最终方案。",[3362,3372,3374],{"id":3373},"问题一目录导航toc只有-h2","问题一：目录导航（TOC）只有 h2",[11,3376,3377],{"id":3377},"现象",[16,3379,3380,3381,3384,3385,3388],{},"Nuxt Content 内置了 TOC 数据，挂在 ",[43,3382,3383],{},"post.body.toc"," 上。直接用它渲染目录，发现",[27,3386,3387],{},"只有 h2 标题，h3 全部丢失","。",[11,3390,3391],{"id":3391},"排查",[16,3393,3394,3395,3398],{},"查了官方文档，发现 ",[43,3396,3397],{},"build.markdown.toc.depth"," 可以控制深度：",[36,3400,3402],{"className":2757,"code":3401,"language":2759,"meta":41,"style":41},"\u002F\u002F nuxt.config.ts\ncontent: {\n  build: {\n    markdown: {\n      toc: { depth: 3 }  \u002F\u002F 包含 h3\n    }\n  }\n}\n",[43,3403,3404,3409,3416,3423,3430,3452,3456,3460],{"__ignoreMap":41},[46,3405,3406],{"class":48,"line":49},[46,3407,3408],{"class":52},"\u002F\u002F nuxt.config.ts\n",[46,3410,3411,3414],{"class":48,"line":56},[46,3412,3413],{"class":59},"content",[46,3415,624],{"class":319},[46,3417,3418,3421],{"class":48,"line":72},[46,3419,3420],{"class":59},"  build",[46,3422,624],{"class":319},[46,3424,3425,3428],{"class":48,"line":85},[46,3426,3427],{"class":59},"    markdown",[46,3429,624],{"class":319},[46,3431,3432,3435,3438,3441,3443,3446,3449],{"class":48,"line":101},[46,3433,3434],{"class":59},"      toc",[46,3436,3437],{"class":319},": { ",[46,3439,3440],{"class":59},"depth",[46,3442,639],{"class":319},[46,3444,3445],{"class":122},"3",[46,3447,3448],{"class":319}," }  ",[46,3450,3451],{"class":52},"\u002F\u002F 包含 h3\n",[46,3453,3454],{"class":48,"line":108},[46,3455,670],{"class":319},[46,3457,3458],{"class":48,"line":114},[46,3459,675],{"class":319},[46,3461,3462],{"class":48,"line":337},[46,3463,680],{"class":319},[16,3465,3466,3467,3470],{},"配置改了，清了缓存重启，",[27,3468,3469],{},"依然只有 h2","。这个配置似乎不起作用。",[11,3472,1791],{"id":1791},[16,3474,3475,3476,3479],{},"放弃依赖 Nuxt Content 的 toc，直接从 ",[43,3477,3478],{},"post.body"," 的 AST 里递归提取所有标题：",[36,3481,3483],{"className":2757,"code":3482,"language":2759,"meta":41,"style":41},"function extractHeadings(node: any, list: Heading[] = []): Heading[] {\n  if (!node) return list\n  if (Array.isArray(node)) {\n    \u002F\u002F minimark 格式：[\"h2\", {id: \"xxx\"}, \"文本\"]\n    if (typeof node[0] === 'string' && \u002F^h[1-6]$\u002F.test(node[0])) {\n      const level = Number(node[0].charAt(1))\n      const text = extractText(node.slice(2)).trim()\n      list.push({ id: node[1]?.id || slugify(text), text, level })\n    }\n    for (const child of node) extractHeadings(child, list)\n  }\n  return list\n}\n",[43,3484,3485,3529,3548,3561,3566,3627,3657,3689,3714,3718,3743,3747,3754],{"__ignoreMap":41},[46,3486,3487,3489,3492,3495,3499,3501,3504,3506,3509,3511,3514,3517,3519,3522,3524,3526],{"class":48,"line":49},[46,3488,2784],{"class":312},[46,3490,3491],{"class":59}," extractHeadings",[46,3493,3494],{"class":319},"(",[46,3496,3498],{"class":3497},"sPK6S","node",[46,3500,2429],{"class":312},[46,3502,3503],{"class":122}," any",[46,3505,659],{"class":319},[46,3507,3508],{"class":3497},"list",[46,3510,2429],{"class":312},[46,3512,3513],{"class":59}," Heading",[46,3515,3516],{"class":319},"[] ",[46,3518,1611],{"class":312},[46,3520,3521],{"class":319}," [])",[46,3523,2429],{"class":312},[46,3525,3513],{"class":59},[46,3527,3528],{"class":319},"[] {\n",[46,3530,3531,3534,3537,3540,3543,3546],{"class":48,"line":56},[46,3532,3533],{"class":312},"  if",[46,3535,3536],{"class":319}," (",[46,3538,3539],{"class":312},"!",[46,3541,3542],{"class":319},"node) ",[46,3544,3545],{"class":312},"return",[46,3547,1059],{"class":319},[46,3549,3550,3552,3555,3558],{"class":48,"line":72},[46,3551,3533],{"class":312},[46,3553,3554],{"class":319}," (Array.",[46,3556,3557],{"class":59},"isArray",[46,3559,3560],{"class":319},"(node)) {\n",[46,3562,3563],{"class":48,"line":85},[46,3564,3565],{"class":52},"    \u002F\u002F minimark 格式：[\"h2\", {id: \"xxx\"}, \"文本\"]\n",[46,3567,3568,3571,3573,3576,3579,3582,3585,3588,3591,3594,3597,3600,3604,3607,3610,3613,3616,3619,3622,3624],{"class":48,"line":101},[46,3569,3570],{"class":312},"    if",[46,3572,3536],{"class":319},[46,3574,3575],{"class":312},"typeof",[46,3577,3578],{"class":319}," node[",[46,3580,3581],{"class":122},"0",[46,3583,3584],{"class":319},"] ",[46,3586,3587],{"class":312},"===",[46,3589,3590],{"class":62}," 'string'",[46,3592,3593],{"class":312}," &&",[46,3595,3596],{"class":62}," \u002F",[46,3598,3599],{"class":312},"^",[46,3601,3603],{"class":3602},"sPKmS","h",[46,3605,3606],{"class":122},"[1-6]",[46,3608,3609],{"class":312},"$",[46,3611,3612],{"class":62},"\u002F",[46,3614,3615],{"class":319},".",[46,3617,3618],{"class":59},"test",[46,3620,3621],{"class":319},"(node[",[46,3623,3581],{"class":122},[46,3625,3626],{"class":319},"])) {\n",[46,3628,3629,3632,3635,3637,3640,3642,3644,3647,3650,3652,3654],{"class":48,"line":108},[46,3630,3631],{"class":312},"      const",[46,3633,3634],{"class":122}," level",[46,3636,3139],{"class":312},[46,3638,3639],{"class":59}," Number",[46,3641,3621],{"class":319},[46,3643,3581],{"class":122},[46,3645,3646],{"class":319},"].",[46,3648,3649],{"class":59},"charAt",[46,3651,3494],{"class":319},[46,3653,1629],{"class":122},[46,3655,3656],{"class":319},"))\n",[46,3658,3659,3661,3664,3666,3669,3672,3675,3677,3680,3683,3686],{"class":48,"line":114},[46,3660,3631],{"class":312},[46,3662,3663],{"class":122}," text",[46,3665,3139],{"class":312},[46,3667,3668],{"class":59}," extractText",[46,3670,3671],{"class":319},"(node.",[46,3673,3674],{"class":59},"slice",[46,3676,3494],{"class":319},[46,3678,3679],{"class":122},"2",[46,3681,3682],{"class":319},")).",[46,3684,3685],{"class":59},"trim",[46,3687,3688],{"class":319},"()\n",[46,3690,3691,3694,3697,3700,3702,3705,3708,3711],{"class":48,"line":337},[46,3692,3693],{"class":319},"      list.",[46,3695,3696],{"class":59},"push",[46,3698,3699],{"class":319},"({ id: node[",[46,3701,1629],{"class":122},[46,3703,3704],{"class":319},"]?.id ",[46,3706,3707],{"class":312},"||",[46,3709,3710],{"class":59}," slugify",[46,3712,3713],{"class":319},"(text), text, level })\n",[46,3715,3716],{"class":48,"line":369},[46,3717,670],{"class":319},[46,3719,3720,3723,3725,3728,3731,3734,3737,3740],{"class":48,"line":374},[46,3721,3722],{"class":312},"    for",[46,3724,3536],{"class":319},[46,3726,3727],{"class":312},"const",[46,3729,3730],{"class":122}," child",[46,3732,3733],{"class":312}," of",[46,3735,3736],{"class":319}," node) ",[46,3738,3739],{"class":59},"extractHeadings",[46,3741,3742],{"class":319},"(child, list)\n",[46,3744,3745],{"class":48,"line":380},[46,3746,675],{"class":319},[46,3748,3749,3752],{"class":48,"line":484},[46,3750,3751],{"class":312},"  return",[46,3753,1059],{"class":319},[46,3755,3756],{"class":48,"line":498},[46,3757,680],{"class":319},[16,3759,3760,3761,3763,3764,3767],{},"关键点：Nuxt Content v3 的 body 使用 ",[27,3762,8],{}," 格式，标题节点是数组 ",[43,3765,3766],{},"[\"h2\", {id: \"xxx\"}, \"标题文本\"]","，不是对象。",[3362,3769,3771],{"id":3770},"问题二图片路径解析错误","问题二：图片路径解析错误",[11,3773,3377],{"id":3774},"现象-1",[16,3776,3777],{},"markdown 里用相对路径引用图片：",[36,3779,3783],{"className":3780,"code":3781,"language":3782,"meta":41,"style":41},"language-markdown shiki shiki-themes github-light github-dark","![SSHFS挂载效果](img\u002Fsshfs.avif)\n","markdown",[43,3784,3785],{"__ignoreMap":41},[46,3786,3787,3790,3794,3797,3801],{"class":48,"line":49},[46,3788,3789],{"class":319},"![",[46,3791,3793],{"class":3792},"smiIW","SSHFS挂载效果",[46,3795,3796],{"class":319},"](",[46,3798,3800],{"class":3799},"szbl0","img\u002Fsshfs.avif",[46,3802,3803],{"class":319},")\n",[16,3805,3806,3807,3810,3811,3814,3815,3388],{},"HTML 输出的 ",[43,3808,3809],{},"\u003Cimg src=\"img\u002Fsshfs.avif\">"," 看起来没问题，但浏览器请求的却是 ",[43,3812,3813],{},"\u002Fposts\u002Fimg\u002Fsshfs.avif"," 而不是 ",[43,3816,3817],{},"\u002Fposts\u002Fsshfs\u002Fimg\u002Fsshfs.avif",[11,3819,3391],{"id":3820},"排查-1",[1747,3822,3823,3830,3833,3839],{},[721,3824,3825,3826,3829],{},"检查了 ",[43,3827,3828],{},"\u003Cbase>"," 标签 —— 没有",[721,3831,3832],{},"检查了重定向 —— 没有",[721,3834,3835,3836,3838],{},"直接访问 ",[43,3837,3817],{}," —— 返回 200 OK",[721,3840,3835,3841,3843],{},[43,3842,3813],{}," —— 404",[16,3845,3846,3847,3388],{},"服务器路由没问题，问题是",[27,3848,3849],{},"浏览器解析相对路径时基于了错误的基准 URL",[16,3851,3852],{},"这可能和 Nuxt 的客户端路由有关：SPA 导航时，浏览器的\"当前页面\"和 Vue Router 的\"当前路由\"不同步，导致相对路径解析出错。",[11,3854,1791],{"id":3855},"最终方案-1",[16,3857,3858],{},"两步解决：",[16,3860,3861],{},[27,3862,3863],{},"第一步：自定义 ProseImg 组件",[16,3865,3866,3867,3870],{},"创建 ",[43,3868,3869],{},"components\u002Fcontent\u002FProseImg.vue","，覆盖 Nuxt Content 默认的图片渲染：",[36,3872,3876],{"className":3873,"code":3874,"language":3875,"meta":41,"style":41},"language-vue shiki shiki-themes github-light github-dark","\u003Cscript setup>\nconst route = useRoute()\nconst resolvedSrc = computed(() => {\n  if (!props.src) return ''\n  if (props.src.startsWith('\u002F') || props.src.startsWith('http')) return props.src\n  const base = route.path.replace(\u002F\\\u002F$\u002F, '')\n  return `${base}\u002F${props.src}`\n})\n\u003C\u002Fscript>\n\n\u003Ctemplate>\n  \u003Cimg :src=\"resolvedSrc\" :alt=\"alt\" \u002F>\n\u003C\u002Ftemplate>\n","vue",[43,3877,3878,3890,3904,3924,3940,3978,4013,4037,4042,4051,4055,4064,4090],{"__ignoreMap":41},[46,3879,3880,3882,3885,3888],{"class":48,"line":49},[46,3881,2432],{"class":319},[46,3883,3884],{"class":3201},"script",[46,3886,3887],{"class":59}," setup",[46,3889,2093],{"class":319},[46,3891,3892,3894,3897,3899,3902],{"class":48,"line":56},[46,3893,3727],{"class":312},[46,3895,3896],{"class":122}," route",[46,3898,3139],{"class":312},[46,3900,3901],{"class":59}," useRoute",[46,3903,3688],{"class":319},[46,3905,3906,3908,3911,3913,3916,3919,3922],{"class":48,"line":72},[46,3907,3727],{"class":312},[46,3909,3910],{"class":122}," resolvedSrc",[46,3912,3139],{"class":312},[46,3914,3915],{"class":59}," computed",[46,3917,3918],{"class":319},"(() ",[46,3920,3921],{"class":312},"=>",[46,3923,3142],{"class":319},[46,3925,3926,3928,3930,3932,3935,3937],{"class":48,"line":85},[46,3927,3533],{"class":312},[46,3929,3536],{"class":319},[46,3931,3539],{"class":312},[46,3933,3934],{"class":319},"props.src) ",[46,3936,3545],{"class":312},[46,3938,3939],{"class":62}," ''\n",[46,3941,3942,3944,3947,3950,3952,3955,3958,3960,3963,3965,3967,3970,3973,3975],{"class":48,"line":101},[46,3943,3533],{"class":312},[46,3945,3946],{"class":319}," (props.src.",[46,3948,3949],{"class":59},"startsWith",[46,3951,3494],{"class":319},[46,3953,3954],{"class":62},"'\u002F'",[46,3956,3957],{"class":319},") ",[46,3959,3707],{"class":312},[46,3961,3962],{"class":319}," props.src.",[46,3964,3949],{"class":59},[46,3966,3494],{"class":319},[46,3968,3969],{"class":62},"'http'",[46,3971,3972],{"class":319},")) ",[46,3974,3545],{"class":312},[46,3976,3977],{"class":319}," props.src\n",[46,3979,3980,3983,3986,3988,3991,3994,3996,3998,4002,4004,4006,4008,4011],{"class":48,"line":108},[46,3981,3982],{"class":312},"  const",[46,3984,3985],{"class":122}," base",[46,3987,3139],{"class":312},[46,3989,3990],{"class":319}," route.path.",[46,3992,3993],{"class":59},"replace",[46,3995,3494],{"class":319},[46,3997,3612],{"class":62},[46,3999,4001],{"class":4000},"sCnZR","\\\u002F",[46,4003,3609],{"class":312},[46,4005,3612],{"class":62},[46,4007,659],{"class":319},[46,4009,4010],{"class":62},"''",[46,4012,3803],{"class":319},[46,4014,4015,4017,4020,4023,4026,4029,4031,4034],{"class":48,"line":114},[46,4016,3751],{"class":312},[46,4018,4019],{"class":62}," `${",[46,4021,4022],{"class":319},"base",[46,4024,4025],{"class":62},"}\u002F${",[46,4027,4028],{"class":319},"props",[46,4030,3615],{"class":62},[46,4032,4033],{"class":319},"src",[46,4035,4036],{"class":62},"}`\n",[46,4038,4039],{"class":48,"line":337},[46,4040,4041],{"class":319},"})\n",[46,4043,4044,4047,4049],{"class":48,"line":369},[46,4045,4046],{"class":319},"\u003C\u002F",[46,4048,3884],{"class":3201},[46,4050,2093],{"class":319},[46,4052,4053],{"class":48,"line":374},[46,4054,105],{"emptyLinePlaceholder":104},[46,4056,4057,4059,4062],{"class":48,"line":380},[46,4058,2432],{"class":319},[46,4060,4061],{"class":3201},"template",[46,4063,2093],{"class":319},[46,4065,4066,4069,4071,4074,4076,4079,4082,4084,4087],{"class":48,"line":484},[46,4067,4068],{"class":319},"  \u003C",[46,4070,1771],{"class":3201},[46,4072,4073],{"class":59}," :src",[46,4075,1611],{"class":319},[46,4077,4078],{"class":62},"\"resolvedSrc\"",[46,4080,4081],{"class":59}," :alt",[46,4083,1611],{"class":319},[46,4085,4086],{"class":62},"\"alt\"",[46,4088,4089],{"class":319}," \u002F>\n",[46,4091,4092,4094,4096],{"class":48,"line":498},[46,4093,4046],{"class":319},[46,4095,4061],{"class":3201},[46,4097,2093],{"class":319},[16,4099,4100],{},[27,4101,4102],{},"第二步：启用 MDC prose 组件",[36,4104,4106],{"className":2757,"code":4105,"language":2759,"meta":41,"style":41},"\u002F\u002F nuxt.config.ts\nmdc: {\n  components: {\n    prose: true  \u002F\u002F 让 Nuxt Content 使用自定义 ProseImg\n  }\n}\n",[43,4107,4108,4112,4119,4126,4139,4143],{"__ignoreMap":41},[46,4109,4110],{"class":48,"line":49},[46,4111,3408],{"class":52},[46,4113,4114,4117],{"class":48,"line":56},[46,4115,4116],{"class":59},"mdc",[46,4118,624],{"class":319},[46,4120,4121,4124],{"class":48,"line":72},[46,4122,4123],{"class":59},"  components",[46,4125,624],{"class":319},[46,4127,4128,4131,4133,4136],{"class":48,"line":85},[46,4129,4130],{"class":59},"    prose",[46,4132,639],{"class":319},[46,4134,4135],{"class":122},"true",[46,4137,4138],{"class":52},"  \u002F\u002F 让 Nuxt Content 使用自定义 ProseImg\n",[46,4140,4141],{"class":48,"line":101},[46,4142,675],{"class":319},[46,4144,4145],{"class":48,"line":108},[46,4146,680],{"class":319},[3362,4148,4150],{"id":4149},"问题三content-目录的图片如何服务","问题三：content 目录的图片如何服务",[11,4152,3377],{"id":4153},"现象-2",[16,4155,4156,4157,4160],{},"原站（SvelteKit）的图片放在 ",[43,4158,4159],{},"content\u002Fposts\u002F{slug}\u002Fimg\u002F"," 下，markdown 用相对路径引用，一切正常。Nuxt 项目里，content 目录的文件不会被当成静态资源服务。",[11,4162,4163],{"id":4163},"方案对比",[131,4165,4166,4178],{},[134,4167,4168],{},[137,4169,4170,4172,4175],{},[140,4171,1375],{},[140,4173,4174],{},"优点",[140,4176,4177],{},"缺点",[147,4179,4180,4191,4202],{},[137,4181,4182,4185,4188],{},[152,4183,4184],{},"复制到 public\u002F",[152,4186,4187],{},"简单",[152,4189,4190],{},"文件重复，需同步",[137,4192,4193,4196,4199],{},[152,4194,4195],{},"Vite 插件拦截",[152,4197,4198],{},"原站方案",[152,4200,4201],{},"Nuxt 不直接支持",[137,4203,4204,4207,4210],{},[152,4205,4206],{},"Server Route",[152,4208,4209],{},"不复制文件",[152,4211,4212],{},"需要手动写路由",[16,4214,4215,4216,4218],{},"最终选择 ",[27,4217,4206],{},"，和原站的 Vite 插件思路一致：",[36,4220,4222],{"className":2757,"code":4221,"language":2759,"meta":41,"style":41},"\u002F\u002F server\u002Froutes\u002Fposts\u002F[slug]\u002Fimg\u002F[...file].ts\nexport default defineEventHandler(async (event) => {\n  const url = getRequestURL(event)\n  const match = url.pathname.match(\u002F^\\\u002Fposts\\\u002F([^\u002F]+)\\\u002Fimg\\\u002F(.+)$\u002F)\n  if (!match) return\n\n  const [, slug, filename] = match\n  const filePath = resolve('content\u002Fposts', slug, 'img', filename)\n  const data = await readFile(filePath)\n  setResponseHeader(event, 'content-type', MIME[ext])\n  return data\n})\n",[43,4223,4224,4229,4255,4270,4334,4348,4352,4374,4400,4418,4437,4444],{"__ignoreMap":41},[46,4225,4226],{"class":48,"line":49},[46,4227,4228],{"class":52},"\u002F\u002F server\u002Froutes\u002Fposts\u002F[slug]\u002Fimg\u002F[...file].ts\n",[46,4230,4231,4233,4236,4239,4241,4244,4246,4249,4251,4253],{"class":48,"line":56},[46,4232,1605],{"class":312},[46,4234,4235],{"class":312}," default",[46,4237,4238],{"class":59}," defineEventHandler",[46,4240,3494],{"class":319},[46,4242,4243],{"class":312},"async",[46,4245,3536],{"class":319},[46,4247,4248],{"class":3497},"event",[46,4250,3957],{"class":319},[46,4252,3921],{"class":312},[46,4254,3142],{"class":319},[46,4256,4257,4259,4262,4264,4267],{"class":48,"line":72},[46,4258,3982],{"class":312},[46,4260,4261],{"class":122}," url",[46,4263,3139],{"class":312},[46,4265,4266],{"class":59}," getRequestURL",[46,4268,4269],{"class":319},"(event)\n",[46,4271,4272,4274,4277,4279,4282,4285,4287,4289,4291,4293,4296,4298,4300,4303,4305,4308,4311,4314,4316,4318,4320,4322,4324,4326,4328,4330,4332],{"class":48,"line":85},[46,4273,3982],{"class":312},[46,4275,4276],{"class":122}," match",[46,4278,3139],{"class":312},[46,4280,4281],{"class":319}," url.pathname.",[46,4283,4284],{"class":59},"match",[46,4286,3494],{"class":319},[46,4288,3612],{"class":62},[46,4290,3599],{"class":312},[46,4292,4001],{"class":4000},[46,4294,4295],{"class":3602},"posts",[46,4297,4001],{"class":4000},[46,4299,3494],{"class":3602},[46,4301,4302],{"class":122},"[",[46,4304,3599],{"class":312},[46,4306,4307],{"class":122},"\u002F]",[46,4309,4310],{"class":312},"+",[46,4312,4313],{"class":3602},")",[46,4315,4001],{"class":4000},[46,4317,1771],{"class":3602},[46,4319,4001],{"class":4000},[46,4321,3494],{"class":3602},[46,4323,3615],{"class":122},[46,4325,4310],{"class":312},[46,4327,4313],{"class":3602},[46,4329,3609],{"class":312},[46,4331,3612],{"class":62},[46,4333,3803],{"class":319},[46,4335,4336,4338,4340,4342,4345],{"class":48,"line":101},[46,4337,3533],{"class":312},[46,4339,3536],{"class":319},[46,4341,3539],{"class":312},[46,4343,4344],{"class":319},"match) ",[46,4346,4347],{"class":312},"return\n",[46,4349,4350],{"class":48,"line":108},[46,4351,105],{"emptyLinePlaceholder":104},[46,4353,4354,4356,4359,4362,4364,4367,4369,4371],{"class":48,"line":114},[46,4355,3982],{"class":312},[46,4357,4358],{"class":319}," [, ",[46,4360,4361],{"class":122},"slug",[46,4363,659],{"class":319},[46,4365,4366],{"class":122},"filename",[46,4368,3584],{"class":319},[46,4370,1611],{"class":312},[46,4372,4373],{"class":319}," match\n",[46,4375,4376,4378,4381,4383,4386,4388,4391,4394,4397],{"class":48,"line":337},[46,4377,3982],{"class":312},[46,4379,4380],{"class":122}," filePath",[46,4382,3139],{"class":312},[46,4384,4385],{"class":59}," resolve",[46,4387,3494],{"class":319},[46,4389,4390],{"class":62},"'content\u002Fposts'",[46,4392,4393],{"class":319},", slug, ",[46,4395,4396],{"class":62},"'img'",[46,4398,4399],{"class":319},", filename)\n",[46,4401,4402,4404,4407,4409,4412,4415],{"class":48,"line":369},[46,4403,3982],{"class":312},[46,4405,4406],{"class":122}," data",[46,4408,3139],{"class":312},[46,4410,4411],{"class":312}," await",[46,4413,4414],{"class":59}," readFile",[46,4416,4417],{"class":319},"(filePath)\n",[46,4419,4420,4423,4426,4429,4431,4434],{"class":48,"line":374},[46,4421,4422],{"class":59},"  setResponseHeader",[46,4424,4425],{"class":319},"(event, ",[46,4427,4428],{"class":62},"'content-type'",[46,4430,659],{"class":319},[46,4432,4433],{"class":122},"MIME",[46,4435,4436],{"class":319},"[ext])\n",[46,4438,4439,4441],{"class":48,"line":380},[46,4440,3751],{"class":312},[46,4442,4443],{"class":319}," data\n",[46,4445,4446],{"class":48,"line":484},[46,4447,4041],{"class":319},[16,4449,4450,4451,4453,4454,4457],{},"请求 ",[43,4452,3817],{}," → 读取 ",[43,4455,4456],{},"content\u002Fposts\u002Fsshfs\u002Fimg\u002Fsshfs.avif"," → 返回图片。",[3362,4459,1430],{"id":1430},[131,4461,4462,4474],{},[134,4463,4464],{},[137,4465,4466,4469,4472],{},[140,4467,4468],{},"问题",[140,4470,4471],{},"根因",[140,4473,1375],{},[147,4475,4476,4490,4501],{},[137,4477,4478,4481,4487],{},[152,4479,4480],{},"TOC 只有 h2",[152,4482,4483,4486],{},[43,4484,4485],{},"toc.depth"," 配置不生效",[152,4488,4489],{},"从 body AST 手动提取",[137,4491,4492,4495,4498],{},[152,4493,4494],{},"图片路径错误",[152,4496,4497],{},"SPA 路由导致相对路径解析基准错误",[152,4499,4500],{},"ProseImg 组件 + mdc.prose 配置",[137,4502,4503,4506,4509],{},[152,4504,4505],{},"content 图片 404",[152,4507,4508],{},"content 目录不暴露给浏览器",[152,4510,4511],{},"Server Route 直接读取返回",[16,4513,4514],{},"Nuxt Content v3 的设计理念是\"图片放 public\u002F，用绝对路径\"。如果想像 SvelteKit 项目那样把图片和文章放一起，需要额外做不少工作。",[16,4516,4517],{},"不过一旦这些坑都踩过，整体开发体验还是很流畅的。Nuxt 的生态系统、自动导入、模块系统都比 SvelteKit 成熟很多。",[1460,4519,4520],{},"html pre.shiki code .sHbNN, html code.shiki .sHbNN{--shiki-light:#6A737D;--shiki-dark:#6A737D}html pre.shiki code .sw2iP, html code.shiki .sw2iP{--shiki-light:#6F42C1;--shiki-dark:#B392F0}html pre.shiki code .sIX_F, html code.shiki .sIX_F{--shiki-light:#24292E;--shiki-dark:#E1E4E8}html pre.shiki code .suQ91, html code.shiki .suQ91{--shiki-light:#005CC5;--shiki-dark:#79B8FF}html .light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html.light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-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 .siTax, html code.shiki .siTax{--shiki-light:#D73A49;--shiki-dark:#F97583}html pre.shiki code .sPK6S, html code.shiki .sPK6S{--shiki-light:#E36209;--shiki-dark:#FFAB70}html pre.shiki code .scXbn, html code.shiki .scXbn{--shiki-light:#032F62;--shiki-dark:#9ECBFF}html pre.shiki code .sPKmS, html code.shiki .sPKmS{--shiki-light:#032F62;--shiki-dark:#DBEDFF}html pre.shiki code .sCnZR, html code.shiki .sCnZR{--shiki-light:#22863A;--shiki-light-font-weight:bold;--shiki-dark:#85E89D;--shiki-dark-font-weight:bold}html pre.shiki code .smiIW, html code.shiki .smiIW{--shiki-light:#032F62;--shiki-light-text-decoration:underline;--shiki-dark:#DBEDFF;--shiki-dark-text-decoration:underline}html pre.shiki code .szbl0, html code.shiki .szbl0{--shiki-light:#24292E;--shiki-light-text-decoration:underline;--shiki-dark:#E1E4E8;--shiki-dark-text-decoration:underline}html pre.shiki code .sbB4o, html code.shiki .sbB4o{--shiki-light:#22863A;--shiki-dark:#85E89D}",{"title":41,"searchDepth":56,"depth":72,"links":4522},[4523,4524,4525,4526,4527,4528,4529,4530],{"id":3377,"depth":56,"text":3377},{"id":3391,"depth":56,"text":3391},{"id":1791,"depth":56,"text":1791},{"id":3774,"depth":56,"text":3377},{"id":3820,"depth":56,"text":3391},{"id":3855,"depth":56,"text":1791},{"id":4153,"depth":56,"text":3377},{"id":4163,"depth":56,"text":4163},"从 SvelteKit 迁移到 Nuxt 3 的过程中，遇到了不少 Nuxt Content v3 的坑。这篇文章记录了目录导航、图片路径、ProseImg 组件等问题的排查和解决过程。","\u002Fposts\u002Fnuxt-content-lessons\u002Fimg\u002Fcover.jpg",{},"\u002Fposts\u002Fnuxt-content-lessons","2026-05-31T14:00:00",{"title":3358,"description":4531},"posts\u002Fnuxt-content-lessons\u002Findex",[2849,4539,4540],"踩坑","Nuxt Content","vbSt6ND1z7xt7L8FxgZ8Qe3EdxV9InsRerN3E23GC7s",{"id":4543,"title":4544,"author":4545,"body":4546,"description":4817,"difficulty":2295,"draft":1486,"extension":1487,"image":4818,"meta":4819,"navigation":104,"path":4820,"pinned":1486,"published":4821,"seo":4822,"series":3350,"seriesOrder":72,"stem":4823,"tags":4824,"updated":3354,"__hash__":4827},"posts\u002Fposts\u002Fnuxt-tailwind-underline-fix\u002Findex.md","Nuxt 3 + Tailwind CSS 客户端导航链接下划线闪烁问题","Nixus",{"type":8,"value":4547,"toc":4810},[4548,4551,4554,4569,4572,4575,4578,4619,4630,4637,4659,4666,4670,4673,4676,4678,4681,4704,4715,4772,4777,4790,4792,4798,4807],[11,4549,4550],{"id":4550},"问题描述",[16,4552,4553],{},"在 Nuxt 3 项目中使用 Tailwind CSS 时，发现一个奇怪的现象：",[718,4555,4556,4566],{},[721,4557,4558,4559,4562,4563],{},"首次通过客户端导航（点击 ",[43,4560,4561],{},"\u003CNuxtLink>","）跳转到某个页面时，",[27,4564,4565],{},"所有链接文字都会出现下划线",[721,4567,4568],{},"手动刷新页面后，下划线消失，样式恢复正常",[16,4570,4571],{},"这个问题在开发模式和生产构建后都会出现。",[11,4573,4574],{"id":4574},"原因分析",[16,4576,4577],{},"Tailwind CSS 的 Preflight（基于 modern-normalize）会重置链接样式：",[36,4579,4583],{"className":4580,"code":4581,"language":4582,"meta":41,"style":41},"language-css shiki shiki-themes github-light github-dark","a {\n  color: inherit;\n  text-decoration: inherit;\n}\n","css",[43,4584,4585,4591,4604,4615],{"__ignoreMap":41},[46,4586,4587,4589],{"class":48,"line":49},[46,4588,19],{"class":3201},[46,4590,3142],{"class":319},[46,4592,4593,4596,4598,4601],{"class":48,"line":56},[46,4594,4595],{"class":122},"  color",[46,4597,639],{"class":319},[46,4599,4600],{"class":122},"inherit",[46,4602,4603],{"class":319},";\n",[46,4605,4606,4609,4611,4613],{"class":48,"line":72},[46,4607,4608],{"class":122},"  text-decoration",[46,4610,639],{"class":319},[46,4612,4600],{"class":122},[46,4614,4603],{"class":319},[46,4616,4617],{"class":48,"line":85},[46,4618,680],{"class":319},[16,4620,4621,4622,4625,4626,4629],{},"正常情况下，这会让链接继承父元素的 ",[43,4623,4624],{},"text-decoration","（通常是 ",[43,4627,4628],{},"none","），从而去掉浏览器默认的下划线。",[16,4631,4632,4633,4636],{},"问题出在 ",[27,4634,4635],{},"Nuxt 的客户端导航机制","：",[1747,4638,4639,4645,4648,4653,4656],{},[721,4640,4641,4642,4644],{},"用户点击 ",[43,4643,4561],{},"，Vue Router 拦截导航",[721,4646,4647],{},"新页面组件异步加载并渲染",[721,4649,4650],{},[27,4651,4652],{},"在 CSS 完全加载\u002F应用之前，页面已经渲染了",[721,4654,4655],{},"此时浏览器使用默认样式（链接带下划线）",[721,4657,4658],{},"CSS 加载完成后，下划线消失",[16,4660,4661,4662,4665],{},"这就是典型的 ",[27,4663,4664],{},"FOUC（Flash of Unstyled Content）"," 问题。",[11,4667,4669],{"id":4668},"为什么刷新就正常了","为什么刷新就正常了？",[16,4671,4672],{},"刷新页面时，浏览器会等待所有 CSS 加载完成后再渲染页面（SSR 模式下 HTML 和 CSS 是一起返回的）。所以刷新后样式是正确的。",[16,4674,4675],{},"而客户端导航是 JavaScript 驱动的，CSS 的加载和页面的渲染是异步的，就出现了时序差。",[11,4677,2320],{"id":2320},[16,4679,4680],{},"尝试了多种方案：",[718,4682,4683,4689,4695,4701],{},[721,4684,4685,4688],{},[43,4686,4687],{},"@layer base"," 覆盖 — 无效，CSS layers 的优先级问题",[721,4690,4691,4694],{},[43,4692,4693],{},"corePlugins.preflight: false"," — 无效，且破坏其他基础样式",[721,4696,4697,4700],{},[43,4698,4699],{},"features.inlineStyles: true"," — 无效",[721,4702,4703],{},"页面过渡动画 — 只是掩盖，不能根治",[16,4705,4706,4707,4710,4711,4714],{},"最终有效的方案是在 ",[43,4708,4709],{},"app.vue"," 中用 ",[43,4712,4713],{},"!important"," 强制覆盖：",[36,4716,4718],{"className":3873,"code":4717,"language":3875,"meta":41,"style":41},"\u003Cstyle>\na:not(.prose a) {\n  text-decoration-line: none !important;\n}\n\u003C\u002Fstyle>\n",[43,4719,4720,4728,4746,4760,4764],{"__ignoreMap":41},[46,4721,4722,4724,4726],{"class":48,"line":49},[46,4723,2432],{"class":319},[46,4725,1460],{"class":3201},[46,4727,2093],{"class":319},[46,4729,4730,4732,4735,4737,4740,4743],{"class":48,"line":56},[46,4731,19],{"class":3201},[46,4733,4734],{"class":59},":not",[46,4736,3494],{"class":319},[46,4738,4739],{"class":59},".prose",[46,4741,4742],{"class":3201}," a",[46,4744,4745],{"class":319},") {\n",[46,4747,4748,4751,4753,4755,4758],{"class":48,"line":72},[46,4749,4750],{"class":122},"  text-decoration-line",[46,4752,639],{"class":319},[46,4754,4628],{"class":122},[46,4756,4757],{"class":312}," !important",[46,4759,4603],{"class":319},[46,4761,4762],{"class":48,"line":85},[46,4763,680],{"class":319},[46,4765,4766,4768,4770],{"class":48,"line":101},[46,4767,4046],{"class":319},[46,4769,1460],{"class":3201},[46,4771,2093],{"class":319},[16,4773,4774],{},[27,4775,4776],{},"关键点：",[718,4778,4779,4784],{},[721,4780,4781,4783],{},[43,4782,4713],{}," 是为了对抗 Tailwind CSS layers 的优先级",[721,4785,4786,4789],{},[43,4787,4788],{},":not(.prose a)"," 排除文章正文区域，保留文章内链接的下划线（可读性需要）",[11,4791,1430],{"id":1430},[16,4793,4794,4795,4797],{},"这是 Nuxt 3 + Tailwind CSS 的一个已知问题，本质上是 CSS 加载时序导致的 FOUC。",[43,4796,4713],{}," 虽然不够优雅，但在这个场景下是务实的解决方案。",[16,4799,4800,4801,4806],{},"如果你有更好的方案，欢迎在 ",[19,4802,4805],{"href":4803,"rel":4804},"https:\u002F\u002Fgithub.com\u002Fwishesl\u002FSuBlog\u002Fissues",[23],"Issues"," 提出！",[1460,4808,4809],{},"html pre.shiki code .sbB4o, html code.shiki .sbB4o{--shiki-light:#22863A;--shiki-dark:#85E89D}html pre.shiki code .sIX_F, html code.shiki .sIX_F{--shiki-light:#24292E;--shiki-dark:#E1E4E8}html pre.shiki code .suQ91, html code.shiki .suQ91{--shiki-light:#005CC5;--shiki-dark:#79B8FF}html .light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html.light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-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 .sw2iP, html code.shiki .sw2iP{--shiki-light:#6F42C1;--shiki-dark:#B392F0}html pre.shiki code .siTax, html code.shiki .siTax{--shiki-light:#D73A49;--shiki-dark:#F97583}",{"title":41,"searchDepth":56,"depth":72,"links":4811},[4812,4813,4814,4815,4816],{"id":4550,"depth":56,"text":4550},{"id":4574,"depth":56,"text":4574},{"id":4668,"depth":56,"text":4669},{"id":2320,"depth":56,"text":2320},{"id":1430,"depth":56,"text":1430},"记录 Nuxt 3 配合 Tailwind CSS 时，客户端导航导致链接文字出现下划线闪烁的问题及解决方案。","\u002Fposts\u002Fnuxt-tailwind-underline-fix\u002Fimg\u002Fcover.png",{},"\u002Fposts\u002Fnuxt-tailwind-underline-fix","2026-05-31T18:47:00",{"title":4544,"description":4817},"posts\u002Fnuxt-tailwind-underline-fix\u002Findex",[2849,4825,4826,4539],"Tailwind CSS","CSS","yMReEaosecX7QtrQkKoqINdTG0f9PrvEW4j27IgQF7g",{"id":4829,"title":4830,"author":6,"body":4831,"description":6876,"difficulty":2295,"draft":1486,"extension":1487,"image":6877,"meta":6878,"navigation":104,"path":6879,"pinned":1486,"published":6880,"seo":6881,"series":6882,"seriesOrder":49,"stem":6883,"tags":6884,"updated":6887,"__hash__":6888},"posts\u002Fposts\u002Fgo-mcp-gin\u002Findex.md","Go-MCP 接入 Gin：从零搭建 MCP 服务器",{"type":8,"value":4832,"toc":6857},[4833,4837,4840,4843,4847,4854,4860,4863,4866,4886,4889,4893,4916,4920,5020,5033,5037,5200,5203,5420,5424,5435,5534,5541,5545,5819,5822,5903,5907,5910,5912,5923,5927,5933,6300,6490,6493,6549,6552,6705,6708,6711,6778,6780,6845,6854],[11,4834,4836],{"id":4835},"什么是-mcp","什么是 MCP",[16,4838,4839],{},"MCP（Model Context Protocol）是 Anthropic 提出的开放协议，用于让 AI 助手（如 Claude）与外部服务进行标准化交互。简单来说：你提供一个 MCP 服务器，AI 就能发现并调用你定义的工具（Tools），就像给 AI 装了一双手。",[16,4841,4842],{},"传统做法是写 REST API 然后让 AI 通过函数调用（Function Calling）间接使用，但 MCP 统一了协议层，AI 客户端只需要配置一个 URL 就能接入所有工具。",[11,4844,4846],{"id":4845},"为什么选-gin-mcp-go","为什么选 Gin + mcp-go",[16,4848,4849,4850,4853],{},"Go 生态里做 MCP 服务器最成熟的库是 ",[43,4851,4852],{},"mark3labs\u002Fmcp-go","，它支持 StreamableHTTP 传输模式，可以直接挂载到任意 HTTP 框架上。Gin 作为 Go 最流行的 HTTP 框架，天然适配。",[16,4855,4856,4857],{},"核心思路：",[27,4858,4859],{},"MCP 服务器本质上就是一个 HTTP Handler，挂到 Gin 路由上即可。",[11,4861,4862],{"id":4862},"需求分析",[16,4864,4865],{},"我们要搭建一个具备以下能力的 MCP 服务器：",[1747,4867,4868,4874,4880],{},[721,4869,4870,4873],{},[27,4871,4872],{},"工具注册"," — 向 AI 暴露可调用的工具（列出资源、查看详情、创建\u002F删除）",[721,4875,4876,4879],{},[27,4877,4878],{},"HTTP 集成"," — MCP 端点作为 Gin 路由的一部分，与业务 API 共存",[721,4881,4882,4885],{},[27,4883,4884],{},"鉴权"," — MCP 请求必须经过身份验证，工具处理器能获取当前用户信息",[11,4887,4888],{"id":4888},"实现步骤",[262,4890,4892],{"id":4891},"_1-安装依赖","1. 安装依赖",[36,4894,4896],{"className":38,"code":4895,"language":40,"meta":41,"style":41},"go get github.com\u002Fmark3labs\u002Fmcp-go@v0.54.0\ngo get github.com\u002Fgin-gonic\u002Fgin@v1.12.0\n",[43,4897,4898,4907],{"__ignoreMap":41},[46,4899,4900,4902,4904],{"class":48,"line":49},[46,4901,230],{"class":59},[46,4903,79],{"class":62},[46,4905,4906],{"class":62}," github.com\u002Fmark3labs\u002Fmcp-go@v0.54.0\n",[46,4908,4909,4911,4913],{"class":48,"line":56},[46,4910,230],{"class":59},[46,4912,79],{"class":62},[46,4914,4915],{"class":62}," github.com\u002Fgin-gonic\u002Fgin@v1.12.0\n",[262,4917,4919],{"id":4918},"_2-创建-mcp-服务器","2. 创建 MCP 服务器",[36,4921,4924],{"className":4922,"code":4923,"language":230,"meta":41,"style":41},"language-go shiki shiki-themes github-light github-dark","import \"github.com\u002Fmark3labs\u002Fmcp-go\u002Fserver\"\n\nmcpServer := server.NewMCPServer(\n    \"my-app\",    \u002F\u002F 服务器名称\n    \"1.0.0\",     \u002F\u002F 版本\n    server.WithLogging(),\n    server.WithRecovery(),\n    server.WithInstructions(\"这是一个 MCP 服务器示例\"),\n)\n",[43,4925,4926,4938,4942,4959,4970,4981,4992,5001,5016],{"__ignoreMap":41},[46,4927,4928,4931,4933,4936],{"class":48,"line":49},[46,4929,4930],{"class":312},"import",[46,4932,1546],{"class":62},[46,4934,4935],{"class":59},"github.com\u002Fmark3labs\u002Fmcp-go\u002Fserver",[46,4937,1742],{"class":62},[46,4939,4940],{"class":48,"line":56},[46,4941,105],{"emptyLinePlaceholder":104},[46,4943,4944,4947,4950,4953,4956],{"class":48,"line":72},[46,4945,4946],{"class":319},"mcpServer ",[46,4948,4949],{"class":312},":=",[46,4951,4952],{"class":319}," server.",[46,4954,4955],{"class":59},"NewMCPServer",[46,4957,4958],{"class":319},"(\n",[46,4960,4961,4964,4967],{"class":48,"line":85},[46,4962,4963],{"class":62},"    \"my-app\"",[46,4965,4966],{"class":319},",    ",[46,4968,4969],{"class":52},"\u002F\u002F 服务器名称\n",[46,4971,4972,4975,4978],{"class":48,"line":101},[46,4973,4974],{"class":62},"    \"1.0.0\"",[46,4976,4977],{"class":319},",     ",[46,4979,4980],{"class":52},"\u002F\u002F 版本\n",[46,4982,4983,4986,4989],{"class":48,"line":108},[46,4984,4985],{"class":319},"    server.",[46,4987,4988],{"class":59},"WithLogging",[46,4990,4991],{"class":319},"(),\n",[46,4993,4994,4996,4999],{"class":48,"line":114},[46,4995,4985],{"class":319},[46,4997,4998],{"class":59},"WithRecovery",[46,5000,4991],{"class":319},[46,5002,5003,5005,5008,5010,5013],{"class":48,"line":337},[46,5004,4985],{"class":319},[46,5006,5007],{"class":59},"WithInstructions",[46,5009,3494],{"class":319},[46,5011,5012],{"class":62},"\"这是一个 MCP 服务器示例\"",[46,5014,5015],{"class":319},"),\n",[46,5017,5018],{"class":48,"line":369},[46,5019,3803],{"class":319},[16,5021,5022,5024,5025,5028,5029,5032],{},[43,5023,4955],{}," 创建一个 MCP 协议实例，它负责处理工具发现（",[43,5026,5027],{},"tools\u002Flist","）和工具调用（",[43,5030,5031],{},"tools\u002Fcall","）的协议逻辑。",[262,5034,5036],{"id":5035},"_3-注册工具","3. 注册工具",[36,5038,5040],{"className":4922,"code":5039,"language":230,"meta":41,"style":41},"import \"github.com\u002Fmark3labs\u002Fmcp-go\u002Fmcp\"\n\n\u002F\u002F 定义工具\nlistTool := mcp.NewTool(\"list_items\",\n    mcp.WithDescription(\"列出所有项目\"),\n)\n\n\u002F\u002F 注册处理器\nmcpServer.AddTool(listTool, func(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {\n    \u002F\u002F 业务逻辑\n    return mcp.NewToolResultText(\"返回结果\"), nil\n})\n",[43,5041,5042,5053,5057,5062,5082,5097,5101,5105,5110,5170,5175,5196],{"__ignoreMap":41},[46,5043,5044,5046,5048,5051],{"class":48,"line":49},[46,5045,4930],{"class":312},[46,5047,1546],{"class":62},[46,5049,5050],{"class":59},"github.com\u002Fmark3labs\u002Fmcp-go\u002Fmcp",[46,5052,1742],{"class":62},[46,5054,5055],{"class":48,"line":56},[46,5056,105],{"emptyLinePlaceholder":104},[46,5058,5059],{"class":48,"line":72},[46,5060,5061],{"class":52},"\u002F\u002F 定义工具\n",[46,5063,5064,5067,5069,5072,5075,5077,5080],{"class":48,"line":85},[46,5065,5066],{"class":319},"listTool ",[46,5068,4949],{"class":312},[46,5070,5071],{"class":319}," mcp.",[46,5073,5074],{"class":59},"NewTool",[46,5076,3494],{"class":319},[46,5078,5079],{"class":62},"\"list_items\"",[46,5081,645],{"class":319},[46,5083,5084,5087,5090,5092,5095],{"class":48,"line":101},[46,5085,5086],{"class":319},"    mcp.",[46,5088,5089],{"class":59},"WithDescription",[46,5091,3494],{"class":319},[46,5093,5094],{"class":62},"\"列出所有项目\"",[46,5096,5015],{"class":319},[46,5098,5099],{"class":48,"line":108},[46,5100,3803],{"class":319},[46,5102,5103],{"class":48,"line":114},[46,5104,105],{"emptyLinePlaceholder":104},[46,5106,5107],{"class":48,"line":337},[46,5108,5109],{"class":52},"\u002F\u002F 注册处理器\n",[46,5111,5112,5115,5118,5121,5124,5126,5129,5132,5134,5137,5139,5142,5144,5146,5149,5152,5155,5158,5160,5163,5165,5168],{"class":48,"line":369},[46,5113,5114],{"class":319},"mcpServer.",[46,5116,5117],{"class":59},"AddTool",[46,5119,5120],{"class":319},"(listTool, ",[46,5122,5123],{"class":312},"func",[46,5125,3494],{"class":319},[46,5127,5128],{"class":3497},"ctx",[46,5130,5131],{"class":59}," context",[46,5133,3615],{"class":319},[46,5135,5136],{"class":59},"Context",[46,5138,659],{"class":319},[46,5140,5141],{"class":3497},"req",[46,5143,119],{"class":59},[46,5145,3615],{"class":319},[46,5147,5148],{"class":59},"CallToolRequest",[46,5150,5151],{"class":319},") (",[46,5153,5154],{"class":312},"*",[46,5156,5157],{"class":59},"mcp",[46,5159,3615],{"class":319},[46,5161,5162],{"class":59},"CallToolResult",[46,5164,659],{"class":319},[46,5166,5167],{"class":312},"error",[46,5169,4745],{"class":319},[46,5171,5172],{"class":48,"line":374},[46,5173,5174],{"class":52},"    \u002F\u002F 业务逻辑\n",[46,5176,5177,5180,5182,5185,5187,5190,5193],{"class":48,"line":380},[46,5178,5179],{"class":312},"    return",[46,5181,5071],{"class":319},[46,5183,5184],{"class":59},"NewToolResultText",[46,5186,3494],{"class":319},[46,5188,5189],{"class":62},"\"返回结果\"",[46,5191,5192],{"class":319},"), ",[46,5194,5195],{"class":122},"nil\n",[46,5197,5198],{"class":48,"line":484},[46,5199,4041],{"class":319},[16,5201,5202],{},"带参数的工具：",[36,5204,5206],{"className":4922,"code":5205,"language":230,"meta":41,"style":41},"getTool := mcp.NewTool(\"get_item\",\n    mcp.WithDescription(\"获取指定项目的详情\"),\n    mcp.WithString(\"item_id\",\n        mcp.Required(),\n        mcp.Description(\"项目 ID\"),\n    ),\n)\n\nmcpServer.AddTool(getTool, func(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {\n    itemID, err := req.RequireString(\"item_id\")\n    if err != nil {\n        return mcp.NewToolResultError(\"缺少 item_id 参数\"), nil\n    }\n    \u002F\u002F 查询并返回\n    return mcp.NewToolResultText(\"项目详情...\"), nil\n})\n",[43,5207,5208,5226,5239,5253,5263,5277,5282,5286,5290,5337,5356,5371,5390,5394,5399,5416],{"__ignoreMap":41},[46,5209,5210,5213,5215,5217,5219,5221,5224],{"class":48,"line":49},[46,5211,5212],{"class":319},"getTool ",[46,5214,4949],{"class":312},[46,5216,5071],{"class":319},[46,5218,5074],{"class":59},[46,5220,3494],{"class":319},[46,5222,5223],{"class":62},"\"get_item\"",[46,5225,645],{"class":319},[46,5227,5228,5230,5232,5234,5237],{"class":48,"line":56},[46,5229,5086],{"class":319},[46,5231,5089],{"class":59},[46,5233,3494],{"class":319},[46,5235,5236],{"class":62},"\"获取指定项目的详情\"",[46,5238,5015],{"class":319},[46,5240,5241,5243,5246,5248,5251],{"class":48,"line":72},[46,5242,5086],{"class":319},[46,5244,5245],{"class":59},"WithString",[46,5247,3494],{"class":319},[46,5249,5250],{"class":62},"\"item_id\"",[46,5252,645],{"class":319},[46,5254,5255,5258,5261],{"class":48,"line":85},[46,5256,5257],{"class":319},"        mcp.",[46,5259,5260],{"class":59},"Required",[46,5262,4991],{"class":319},[46,5264,5265,5267,5270,5272,5275],{"class":48,"line":101},[46,5266,5257],{"class":319},[46,5268,5269],{"class":59},"Description",[46,5271,3494],{"class":319},[46,5273,5274],{"class":62},"\"项目 ID\"",[46,5276,5015],{"class":319},[46,5278,5279],{"class":48,"line":108},[46,5280,5281],{"class":319},"    ),\n",[46,5283,5284],{"class":48,"line":114},[46,5285,3803],{"class":319},[46,5287,5288],{"class":48,"line":337},[46,5289,105],{"emptyLinePlaceholder":104},[46,5291,5292,5294,5296,5299,5301,5303,5305,5307,5309,5311,5313,5315,5317,5319,5321,5323,5325,5327,5329,5331,5333,5335],{"class":48,"line":369},[46,5293,5114],{"class":319},[46,5295,5117],{"class":59},[46,5297,5298],{"class":319},"(getTool, ",[46,5300,5123],{"class":312},[46,5302,3494],{"class":319},[46,5304,5128],{"class":3497},[46,5306,5131],{"class":59},[46,5308,3615],{"class":319},[46,5310,5136],{"class":59},[46,5312,659],{"class":319},[46,5314,5141],{"class":3497},[46,5316,119],{"class":59},[46,5318,3615],{"class":319},[46,5320,5148],{"class":59},[46,5322,5151],{"class":319},[46,5324,5154],{"class":312},[46,5326,5157],{"class":59},[46,5328,3615],{"class":319},[46,5330,5162],{"class":59},[46,5332,659],{"class":319},[46,5334,5167],{"class":312},[46,5336,4745],{"class":319},[46,5338,5339,5342,5344,5347,5350,5352,5354],{"class":48,"line":374},[46,5340,5341],{"class":319},"    itemID, err ",[46,5343,4949],{"class":312},[46,5345,5346],{"class":319}," req.",[46,5348,5349],{"class":59},"RequireString",[46,5351,3494],{"class":319},[46,5353,5250],{"class":62},[46,5355,3803],{"class":319},[46,5357,5358,5360,5363,5366,5369],{"class":48,"line":380},[46,5359,3570],{"class":312},[46,5361,5362],{"class":319}," err ",[46,5364,5365],{"class":312},"!=",[46,5367,5368],{"class":122}," nil",[46,5370,3142],{"class":319},[46,5372,5373,5376,5378,5381,5383,5386,5388],{"class":48,"line":484},[46,5374,5375],{"class":312},"        return",[46,5377,5071],{"class":319},[46,5379,5380],{"class":59},"NewToolResultError",[46,5382,3494],{"class":319},[46,5384,5385],{"class":62},"\"缺少 item_id 参数\"",[46,5387,5192],{"class":319},[46,5389,5195],{"class":122},[46,5391,5392],{"class":48,"line":498},[46,5393,670],{"class":319},[46,5395,5396],{"class":48,"line":503},[46,5397,5398],{"class":52},"    \u002F\u002F 查询并返回\n",[46,5400,5401,5403,5405,5407,5409,5412,5414],{"class":48,"line":509},[46,5402,5179],{"class":312},[46,5404,5071],{"class":319},[46,5406,5184],{"class":59},[46,5408,3494],{"class":319},[46,5410,5411],{"class":62},"\"项目详情...\"",[46,5413,5192],{"class":319},[46,5415,5195],{"class":122},[46,5417,5418],{"class":48,"line":1711},[46,5419,4041],{"class":319},[262,5421,5423],{"id":5422},"_4-挂载到-gin-路由","4. 挂载到 Gin 路由",[16,5425,5426,5427,5430,5431,5434],{},"关键一步：用 ",[43,5428,5429],{},"server.NewStreamableHTTPServer"," 把 MCP 实例转成 HTTP Handler，再用 ",[43,5432,5433],{},"gin.WrapH"," 适配 Gin。",[36,5436,5438],{"className":4922,"code":5437,"language":230,"meta":41,"style":41},"import \"github.com\u002Fmark3labs\u002Fmcp-go\u002Fserver\"\n\nmcpHTTPServer := server.NewStreamableHTTPServer(mcpServer,\n    server.WithEndpointPath(\"\u002Fmcp\"),\n    server.WithHeartbeatInterval(30*time.Second),\n)\n\n\u002F\u002F 挂载到 Gin\nr.Any(\"\u002Fmcp\", gin.WrapH(mcpHTTPServer))\n",[43,5439,5440,5450,5454,5469,5483,5500,5504,5508,5513],{"__ignoreMap":41},[46,5441,5442,5444,5446,5448],{"class":48,"line":49},[46,5443,4930],{"class":312},[46,5445,1546],{"class":62},[46,5447,4935],{"class":59},[46,5449,1742],{"class":62},[46,5451,5452],{"class":48,"line":56},[46,5453,105],{"emptyLinePlaceholder":104},[46,5455,5456,5459,5461,5463,5466],{"class":48,"line":72},[46,5457,5458],{"class":319},"mcpHTTPServer ",[46,5460,4949],{"class":312},[46,5462,4952],{"class":319},[46,5464,5465],{"class":59},"NewStreamableHTTPServer",[46,5467,5468],{"class":319},"(mcpServer,\n",[46,5470,5471,5473,5476,5478,5481],{"class":48,"line":85},[46,5472,4985],{"class":319},[46,5474,5475],{"class":59},"WithEndpointPath",[46,5477,3494],{"class":319},[46,5479,5480],{"class":62},"\"\u002Fmcp\"",[46,5482,5015],{"class":319},[46,5484,5485,5487,5490,5492,5495,5497],{"class":48,"line":101},[46,5486,4985],{"class":319},[46,5488,5489],{"class":59},"WithHeartbeatInterval",[46,5491,3494],{"class":319},[46,5493,5494],{"class":122},"30",[46,5496,5154],{"class":312},[46,5498,5499],{"class":319},"time.Second),\n",[46,5501,5502],{"class":48,"line":108},[46,5503,3803],{"class":319},[46,5505,5506],{"class":48,"line":114},[46,5507,105],{"emptyLinePlaceholder":104},[46,5509,5510],{"class":48,"line":337},[46,5511,5512],{"class":52},"\u002F\u002F 挂载到 Gin\n",[46,5514,5515,5518,5521,5523,5525,5528,5531],{"class":48,"line":369},[46,5516,5517],{"class":319},"r.",[46,5519,5520],{"class":59},"Any",[46,5522,3494],{"class":319},[46,5524,5480],{"class":62},[46,5526,5527],{"class":319},", gin.",[46,5529,5530],{"class":59},"WrapH",[46,5532,5533],{"class":319},"(mcpHTTPServer))\n",[16,5535,5536,5537,5540],{},"就这样，",[43,5538,5539],{},"POST \u002Fmcp"," 端点就生效了。AI 客户端通过这个 URL 发送 JSON-RPC 请求，MCP 服务器自动处理协议握手、工具列表返回、工具调用路由。",[262,5542,5544],{"id":5543},"_5-完整的服务启动代码","5. 完整的服务启动代码",[36,5546,5548],{"className":4922,"code":5547,"language":230,"meta":41,"style":41},"func main() {\n    r := gin.Default()\n\n    \u002F\u002F 创建 MCP 服务器\n    mcpServer := server.NewMCPServer(\"my-app\", \"1.0.0\",\n        server.WithLogging(),\n        server.WithRecovery(),\n    )\n\n    \u002F\u002F 注册工具\n    registerTools(mcpServer)\n\n    \u002F\u002F 挂载到 Gin\n    mcpHTTP := server.NewStreamableHTTPServer(mcpServer,\n        server.WithEndpointPath(\"\u002Fmcp\"),\n        server.WithHeartbeatInterval(30*time.Second),\n    )\n    r.Any(\"\u002Fmcp\", gin.WrapH(mcpHTTP))\n\n    \u002F\u002F 其他业务路由\n    r.GET(\"\u002Fapi\u002Fhealth\", func(c *gin.Context) {\n        c.JSON(200, gin.H{\"status\": \"ok\"})\n    })\n\n    r.Run(\":8080\")\n}\n",[43,5549,5550,5559,5574,5578,5583,5606,5615,5623,5628,5632,5637,5645,5649,5654,5667,5679,5693,5697,5715,5719,5724,5757,5792,5797,5801,5815],{"__ignoreMap":41},[46,5551,5552,5554,5557],{"class":48,"line":49},[46,5553,5123],{"class":312},[46,5555,5556],{"class":59}," main",[46,5558,2790],{"class":319},[46,5560,5561,5564,5566,5569,5572],{"class":48,"line":56},[46,5562,5563],{"class":319},"    r ",[46,5565,4949],{"class":312},[46,5567,5568],{"class":319}," gin.",[46,5570,5571],{"class":59},"Default",[46,5573,3688],{"class":319},[46,5575,5576],{"class":48,"line":72},[46,5577,105],{"emptyLinePlaceholder":104},[46,5579,5580],{"class":48,"line":85},[46,5581,5582],{"class":52},"    \u002F\u002F 创建 MCP 服务器\n",[46,5584,5585,5588,5590,5592,5594,5596,5599,5601,5604],{"class":48,"line":101},[46,5586,5587],{"class":319},"    mcpServer ",[46,5589,4949],{"class":312},[46,5591,4952],{"class":319},[46,5593,4955],{"class":59},[46,5595,3494],{"class":319},[46,5597,5598],{"class":62},"\"my-app\"",[46,5600,659],{"class":319},[46,5602,5603],{"class":62},"\"1.0.0\"",[46,5605,645],{"class":319},[46,5607,5608,5611,5613],{"class":48,"line":108},[46,5609,5610],{"class":319},"        server.",[46,5612,4988],{"class":59},[46,5614,4991],{"class":319},[46,5616,5617,5619,5621],{"class":48,"line":114},[46,5618,5610],{"class":319},[46,5620,4998],{"class":59},[46,5622,4991],{"class":319},[46,5624,5625],{"class":48,"line":337},[46,5626,5627],{"class":319},"    )\n",[46,5629,5630],{"class":48,"line":369},[46,5631,105],{"emptyLinePlaceholder":104},[46,5633,5634],{"class":48,"line":374},[46,5635,5636],{"class":52},"    \u002F\u002F 注册工具\n",[46,5638,5639,5642],{"class":48,"line":380},[46,5640,5641],{"class":59},"    registerTools",[46,5643,5644],{"class":319},"(mcpServer)\n",[46,5646,5647],{"class":48,"line":484},[46,5648,105],{"emptyLinePlaceholder":104},[46,5650,5651],{"class":48,"line":498},[46,5652,5653],{"class":52},"    \u002F\u002F 挂载到 Gin\n",[46,5655,5656,5659,5661,5663,5665],{"class":48,"line":503},[46,5657,5658],{"class":319},"    mcpHTTP ",[46,5660,4949],{"class":312},[46,5662,4952],{"class":319},[46,5664,5465],{"class":59},[46,5666,5468],{"class":319},[46,5668,5669,5671,5673,5675,5677],{"class":48,"line":509},[46,5670,5610],{"class":319},[46,5672,5475],{"class":59},[46,5674,3494],{"class":319},[46,5676,5480],{"class":62},[46,5678,5015],{"class":319},[46,5680,5681,5683,5685,5687,5689,5691],{"class":48,"line":1711},[46,5682,5610],{"class":319},[46,5684,5489],{"class":59},[46,5686,3494],{"class":319},[46,5688,5494],{"class":122},[46,5690,5154],{"class":312},[46,5692,5499],{"class":319},[46,5694,5695],{"class":48,"line":1716},[46,5696,5627],{"class":319},[46,5698,5699,5702,5704,5706,5708,5710,5712],{"class":48,"line":1725},[46,5700,5701],{"class":319},"    r.",[46,5703,5520],{"class":59},[46,5705,3494],{"class":319},[46,5707,5480],{"class":62},[46,5709,5527],{"class":319},[46,5711,5530],{"class":59},[46,5713,5714],{"class":319},"(mcpHTTP))\n",[46,5716,5717],{"class":48,"line":1730},[46,5718,105],{"emptyLinePlaceholder":104},[46,5720,5721],{"class":48,"line":1938},[46,5722,5723],{"class":52},"    \u002F\u002F 其他业务路由\n",[46,5725,5726,5728,5731,5733,5736,5738,5740,5742,5745,5748,5751,5753,5755],{"class":48,"line":1946},[46,5727,5701],{"class":319},[46,5729,5730],{"class":59},"GET",[46,5732,3494],{"class":319},[46,5734,5735],{"class":62},"\"\u002Fapi\u002Fhealth\"",[46,5737,659],{"class":319},[46,5739,5123],{"class":312},[46,5741,3494],{"class":319},[46,5743,5744],{"class":3497},"c",[46,5746,5747],{"class":312}," *",[46,5749,5750],{"class":59},"gin",[46,5752,3615],{"class":319},[46,5754,5136],{"class":59},[46,5756,4745],{"class":319},[46,5758,5759,5762,5765,5767,5770,5772,5774,5776,5779,5782,5785,5787,5790],{"class":48,"line":1951},[46,5760,5761],{"class":319},"        c.",[46,5763,5764],{"class":59},"JSON",[46,5766,3494],{"class":319},[46,5768,5769],{"class":122},"200",[46,5771,659],{"class":319},[46,5773,5750],{"class":59},[46,5775,3615],{"class":319},[46,5777,5778],{"class":59},"H",[46,5780,5781],{"class":319},"{",[46,5783,5784],{"class":62},"\"status\"",[46,5786,639],{"class":319},[46,5788,5789],{"class":62},"\"ok\"",[46,5791,4041],{"class":319},[46,5793,5794],{"class":48,"line":2575},[46,5795,5796],{"class":319},"    })\n",[46,5798,5799],{"class":48,"line":2589},[46,5800,105],{"emptyLinePlaceholder":104},[46,5802,5803,5805,5808,5810,5813],{"class":48,"line":2596},[46,5804,5701],{"class":319},[46,5806,5807],{"class":59},"Run",[46,5809,3494],{"class":319},[46,5811,5812],{"class":62},"\":8080\"",[46,5814,3803],{"class":319},[46,5816,5817],{"class":48,"line":2601},[46,5818,680],{"class":319},[16,5820,5821],{},"此时启动服务，用 curl 测试：",[36,5823,5825],{"className":38,"code":5824,"language":40,"meta":41,"style":41},"# 发现工具列表\ncurl -X POST http:\u002F\u002Flocalhost:8080\u002Fmcp \\\n  -H \"Content-Type: application\u002Fjson\" \\\n  -d '{\"jsonrpc\":\"2.0\",\"id\":1,\"method\":\"tools\u002Flist\"}'\n\n# 调用工具\ncurl -X POST http:\u002F\u002Flocalhost:8080\u002Fmcp \\\n  -H \"Content-Type: application\u002Fjson\" \\\n  -d '{\"jsonrpc\":\"2.0\",\"id\":2,\"method\":\"tools\u002Fcall\",\"params\":{\"name\":\"list_items\",\"arguments\":{}}}'\n",[43,5826,5827,5832,5849,5859,5867,5871,5876,5888,5896],{"__ignoreMap":41},[46,5828,5829],{"class":48,"line":49},[46,5830,5831],{"class":52},"# 发现工具列表\n",[46,5833,5834,5837,5840,5843,5846],{"class":48,"line":56},[46,5835,5836],{"class":59},"curl",[46,5838,5839],{"class":122}," -X",[46,5841,5842],{"class":62}," POST",[46,5844,5845],{"class":62}," http:\u002F\u002Flocalhost:8080\u002Fmcp",[46,5847,5848],{"class":122}," \\\n",[46,5850,5851,5854,5857],{"class":48,"line":72},[46,5852,5853],{"class":122},"  -H",[46,5855,5856],{"class":62}," \"Content-Type: application\u002Fjson\"",[46,5858,5848],{"class":122},[46,5860,5861,5864],{"class":48,"line":85},[46,5862,5863],{"class":122},"  -d",[46,5865,5866],{"class":62}," '{\"jsonrpc\":\"2.0\",\"id\":1,\"method\":\"tools\u002Flist\"}'\n",[46,5868,5869],{"class":48,"line":101},[46,5870,105],{"emptyLinePlaceholder":104},[46,5872,5873],{"class":48,"line":108},[46,5874,5875],{"class":52},"# 调用工具\n",[46,5877,5878,5880,5882,5884,5886],{"class":48,"line":114},[46,5879,5836],{"class":59},[46,5881,5839],{"class":122},[46,5883,5842],{"class":62},[46,5885,5845],{"class":62},[46,5887,5848],{"class":122},[46,5889,5890,5892,5894],{"class":48,"line":337},[46,5891,5853],{"class":122},[46,5893,5856],{"class":62},[46,5895,5848],{"class":122},[46,5897,5898,5900],{"class":48,"line":369},[46,5899,5863],{"class":122},[46,5901,5902],{"class":62}," '{\"jsonrpc\":\"2.0\",\"id\":2,\"method\":\"tools\u002Fcall\",\"params\":{\"name\":\"list_items\",\"arguments\":{}}}'\n",[11,5904,5906],{"id":5905},"拓展接入鉴权","拓展：接入鉴权",[16,5908,5909],{},"裸奔的 MCP 端点任何人都能调用，生产环境必须加鉴权。思路和普通 API 鉴权一致：Bearer Token。",[262,5911,4468],{"id":4468},[16,5913,5914,5915,5918,5919,5922],{},"MCP 的工具处理器签名为 ",[43,5916,5917],{},"func(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error)","，它不接收 Gin 的 ",[43,5920,5921],{},"*gin.Context","，所以无法直接在工具处理器里读取请求头。",[262,5924,5926],{"id":5925},"解决方案中间件传递用户信息","解决方案：中间件传递用户信息",[16,5928,5929,5930,3388],{},"用两层中间件：第一层做 Gin 的 Token 验证，第二层把用户信息从 Gin Context 复制到 ",[43,5931,5932],{},"request.Context",[36,5934,5936],{"className":4922,"code":5935,"language":230,"meta":41,"style":41},"\u002F\u002F Gin 鉴权中间件\nfunc AuthRequired(authService *AuthService) gin.HandlerFunc {\n    return func(c *gin.Context) {\n        authHeader := c.GetHeader(\"Authorization\")\n        if authHeader == \"\" {\n            c.AbortWithStatusJSON(401, gin.H{\"message\": \"缺少认证令牌\"})\n            return\n        }\n\n        parts := strings.SplitN(authHeader, \" \", 2)\n        if len(parts) != 2 || !strings.EqualFold(parts[0], \"Bearer\") {\n            c.AbortWithStatusJSON(401, gin.H{\"message\": \"格式错误，请使用 Bearer \u003Ctoken>\"})\n            return\n        }\n\n        user, err := authService.GetUserByToken(parts[1])\n        if err != nil {\n            c.AbortWithStatusJSON(401, gin.H{\"message\": \"无效的认证令牌\"})\n            return\n        }\n\n        c.Set(\"userID\", user.ID)\n        c.Set(\"username\", user.Username)\n        c.Next()\n    }\n}\n",[43,5937,5938,5943,5971,5992,6012,6028,6061,6066,6071,6075,6100,6140,6169,6173,6177,6181,6201,6213,6242,6246,6250,6254,6269,6283,6292,6296],{"__ignoreMap":41},[46,5939,5940],{"class":48,"line":49},[46,5941,5942],{"class":52},"\u002F\u002F Gin 鉴权中间件\n",[46,5944,5945,5947,5950,5952,5955,5957,5960,5962,5964,5966,5969],{"class":48,"line":56},[46,5946,5123],{"class":312},[46,5948,5949],{"class":59}," AuthRequired",[46,5951,3494],{"class":319},[46,5953,5954],{"class":3497},"authService",[46,5956,5747],{"class":312},[46,5958,5959],{"class":59},"AuthService",[46,5961,3957],{"class":319},[46,5963,5750],{"class":59},[46,5965,3615],{"class":319},[46,5967,5968],{"class":59},"HandlerFunc",[46,5970,3142],{"class":319},[46,5972,5973,5975,5978,5980,5982,5984,5986,5988,5990],{"class":48,"line":72},[46,5974,5179],{"class":312},[46,5976,5977],{"class":312}," func",[46,5979,3494],{"class":319},[46,5981,5744],{"class":3497},[46,5983,5747],{"class":312},[46,5985,5750],{"class":59},[46,5987,3615],{"class":319},[46,5989,5136],{"class":59},[46,5991,4745],{"class":319},[46,5993,5994,5997,5999,6002,6005,6007,6010],{"class":48,"line":85},[46,5995,5996],{"class":319},"        authHeader ",[46,5998,4949],{"class":312},[46,6000,6001],{"class":319}," c.",[46,6003,6004],{"class":59},"GetHeader",[46,6006,3494],{"class":319},[46,6008,6009],{"class":62},"\"Authorization\"",[46,6011,3803],{"class":319},[46,6013,6014,6017,6020,6023,6026],{"class":48,"line":101},[46,6015,6016],{"class":312},"        if",[46,6018,6019],{"class":319}," authHeader ",[46,6021,6022],{"class":312},"==",[46,6024,6025],{"class":62}," \"\"",[46,6027,3142],{"class":319},[46,6029,6030,6033,6036,6038,6041,6043,6045,6047,6049,6051,6054,6056,6059],{"class":48,"line":108},[46,6031,6032],{"class":319},"            c.",[46,6034,6035],{"class":59},"AbortWithStatusJSON",[46,6037,3494],{"class":319},[46,6039,6040],{"class":122},"401",[46,6042,659],{"class":319},[46,6044,5750],{"class":59},[46,6046,3615],{"class":319},[46,6048,5778],{"class":59},[46,6050,5781],{"class":319},[46,6052,6053],{"class":62},"\"message\"",[46,6055,639],{"class":319},[46,6057,6058],{"class":62},"\"缺少认证令牌\"",[46,6060,4041],{"class":319},[46,6062,6063],{"class":48,"line":114},[46,6064,6065],{"class":312},"            return\n",[46,6067,6068],{"class":48,"line":337},[46,6069,6070],{"class":319},"        }\n",[46,6072,6073],{"class":48,"line":369},[46,6074,105],{"emptyLinePlaceholder":104},[46,6076,6077,6080,6082,6085,6088,6091,6094,6096,6098],{"class":48,"line":374},[46,6078,6079],{"class":319},"        parts ",[46,6081,4949],{"class":312},[46,6083,6084],{"class":319}," strings.",[46,6086,6087],{"class":59},"SplitN",[46,6089,6090],{"class":319},"(authHeader, ",[46,6092,6093],{"class":62},"\" \"",[46,6095,659],{"class":319},[46,6097,3679],{"class":122},[46,6099,3803],{"class":319},[46,6101,6102,6104,6107,6110,6112,6115,6118,6121,6124,6127,6130,6132,6135,6138],{"class":48,"line":380},[46,6103,6016],{"class":312},[46,6105,6106],{"class":59}," len",[46,6108,6109],{"class":319},"(parts) ",[46,6111,5365],{"class":312},[46,6113,6114],{"class":122}," 2",[46,6116,6117],{"class":312}," ||",[46,6119,6120],{"class":312}," !",[46,6122,6123],{"class":319},"strings.",[46,6125,6126],{"class":59},"EqualFold",[46,6128,6129],{"class":319},"(parts[",[46,6131,3581],{"class":122},[46,6133,6134],{"class":319},"], ",[46,6136,6137],{"class":62},"\"Bearer\"",[46,6139,4745],{"class":319},[46,6141,6142,6144,6146,6148,6150,6152,6154,6156,6158,6160,6162,6164,6167],{"class":48,"line":484},[46,6143,6032],{"class":319},[46,6145,6035],{"class":59},[46,6147,3494],{"class":319},[46,6149,6040],{"class":122},[46,6151,659],{"class":319},[46,6153,5750],{"class":59},[46,6155,3615],{"class":319},[46,6157,5778],{"class":59},[46,6159,5781],{"class":319},[46,6161,6053],{"class":62},[46,6163,639],{"class":319},[46,6165,6166],{"class":62},"\"格式错误，请使用 Bearer \u003Ctoken>\"",[46,6168,4041],{"class":319},[46,6170,6171],{"class":48,"line":498},[46,6172,6065],{"class":312},[46,6174,6175],{"class":48,"line":503},[46,6176,6070],{"class":319},[46,6178,6179],{"class":48,"line":509},[46,6180,105],{"emptyLinePlaceholder":104},[46,6182,6183,6186,6188,6191,6194,6196,6198],{"class":48,"line":1711},[46,6184,6185],{"class":319},"        user, err ",[46,6187,4949],{"class":312},[46,6189,6190],{"class":319}," authService.",[46,6192,6193],{"class":59},"GetUserByToken",[46,6195,6129],{"class":319},[46,6197,1629],{"class":122},[46,6199,6200],{"class":319},"])\n",[46,6202,6203,6205,6207,6209,6211],{"class":48,"line":1716},[46,6204,6016],{"class":312},[46,6206,5362],{"class":319},[46,6208,5365],{"class":312},[46,6210,5368],{"class":122},[46,6212,3142],{"class":319},[46,6214,6215,6217,6219,6221,6223,6225,6227,6229,6231,6233,6235,6237,6240],{"class":48,"line":1725},[46,6216,6032],{"class":319},[46,6218,6035],{"class":59},[46,6220,3494],{"class":319},[46,6222,6040],{"class":122},[46,6224,659],{"class":319},[46,6226,5750],{"class":59},[46,6228,3615],{"class":319},[46,6230,5778],{"class":59},[46,6232,5781],{"class":319},[46,6234,6053],{"class":62},[46,6236,639],{"class":319},[46,6238,6239],{"class":62},"\"无效的认证令牌\"",[46,6241,4041],{"class":319},[46,6243,6244],{"class":48,"line":1730},[46,6245,6065],{"class":312},[46,6247,6248],{"class":48,"line":1938},[46,6249,6070],{"class":319},[46,6251,6252],{"class":48,"line":1946},[46,6253,105],{"emptyLinePlaceholder":104},[46,6255,6256,6258,6261,6263,6266],{"class":48,"line":1951},[46,6257,5761],{"class":319},[46,6259,6260],{"class":59},"Set",[46,6262,3494],{"class":319},[46,6264,6265],{"class":62},"\"userID\"",[46,6267,6268],{"class":319},", user.ID)\n",[46,6270,6271,6273,6275,6277,6280],{"class":48,"line":2575},[46,6272,5761],{"class":319},[46,6274,6260],{"class":59},[46,6276,3494],{"class":319},[46,6278,6279],{"class":62},"\"username\"",[46,6281,6282],{"class":319},", user.Username)\n",[46,6284,6285,6287,6290],{"class":48,"line":2589},[46,6286,5761],{"class":319},[46,6288,6289],{"class":59},"Next",[46,6291,3688],{"class":319},[46,6293,6294],{"class":48,"line":2596},[46,6295,670],{"class":319},[46,6297,6298],{"class":48,"line":2601},[46,6299,680],{"class":319},[36,6301,6303],{"className":4922,"code":6302,"language":230,"meta":41,"style":41},"\u002F\u002F 用户信息桥接中间件：Gin Context → request.Context\ntype ctxKey string\nconst (\n    ctxUserID   ctxKey = \"userID\"\n    ctxUsername ctxKey = \"username\"\n)\n\nfunc WithUserContext() gin.HandlerFunc {\n    return func(c *gin.Context) {\n        ctx := c.Request.Context()\n        ctx = context.WithValue(ctx, ctxUserID, c.GetUint(\"userID\"))\n        ctx = context.WithValue(ctx, ctxUsername, c.GetString(\"username\"))\n        c.Request = c.Request.WithContext(ctx)\n        c.Next()\n    }\n}\n",[43,6304,6305,6310,6321,6328,6341,6353,6357,6361,6379,6399,6413,6437,6459,6474,6482,6486],{"__ignoreMap":41},[46,6306,6307],{"class":48,"line":49},[46,6308,6309],{"class":52},"\u002F\u002F 用户信息桥接中间件：Gin Context → request.Context\n",[46,6311,6312,6315,6318],{"class":48,"line":56},[46,6313,6314],{"class":312},"type",[46,6316,6317],{"class":59}," ctxKey",[46,6319,6320],{"class":312}," string\n",[46,6322,6323,6325],{"class":48,"line":72},[46,6324,3727],{"class":312},[46,6326,6327],{"class":319}," (\n",[46,6329,6330,6333,6336,6338],{"class":48,"line":85},[46,6331,6332],{"class":122},"    ctxUserID",[46,6334,6335],{"class":59},"   ctxKey",[46,6337,3139],{"class":312},[46,6339,6340],{"class":62}," \"userID\"\n",[46,6342,6343,6346,6348,6350],{"class":48,"line":101},[46,6344,6345],{"class":122},"    ctxUsername",[46,6347,6317],{"class":59},[46,6349,3139],{"class":312},[46,6351,6352],{"class":62}," \"username\"\n",[46,6354,6355],{"class":48,"line":108},[46,6356,3803],{"class":319},[46,6358,6359],{"class":48,"line":114},[46,6360,105],{"emptyLinePlaceholder":104},[46,6362,6363,6365,6368,6371,6373,6375,6377],{"class":48,"line":337},[46,6364,5123],{"class":312},[46,6366,6367],{"class":59}," WithUserContext",[46,6369,6370],{"class":319},"() ",[46,6372,5750],{"class":59},[46,6374,3615],{"class":319},[46,6376,5968],{"class":59},[46,6378,3142],{"class":319},[46,6380,6381,6383,6385,6387,6389,6391,6393,6395,6397],{"class":48,"line":369},[46,6382,5179],{"class":312},[46,6384,5977],{"class":312},[46,6386,3494],{"class":319},[46,6388,5744],{"class":3497},[46,6390,5747],{"class":312},[46,6392,5750],{"class":59},[46,6394,3615],{"class":319},[46,6396,5136],{"class":59},[46,6398,4745],{"class":319},[46,6400,6401,6404,6406,6409,6411],{"class":48,"line":374},[46,6402,6403],{"class":319},"        ctx ",[46,6405,4949],{"class":312},[46,6407,6408],{"class":319}," c.Request.",[46,6410,5136],{"class":59},[46,6412,3688],{"class":319},[46,6414,6415,6417,6419,6422,6425,6428,6431,6433,6435],{"class":48,"line":380},[46,6416,6403],{"class":319},[46,6418,1611],{"class":312},[46,6420,6421],{"class":319}," context.",[46,6423,6424],{"class":59},"WithValue",[46,6426,6427],{"class":319},"(ctx, ctxUserID, c.",[46,6429,6430],{"class":59},"GetUint",[46,6432,3494],{"class":319},[46,6434,6265],{"class":62},[46,6436,3656],{"class":319},[46,6438,6439,6441,6443,6445,6447,6450,6453,6455,6457],{"class":48,"line":484},[46,6440,6403],{"class":319},[46,6442,1611],{"class":312},[46,6444,6421],{"class":319},[46,6446,6424],{"class":59},[46,6448,6449],{"class":319},"(ctx, ctxUsername, c.",[46,6451,6452],{"class":59},"GetString",[46,6454,3494],{"class":319},[46,6456,6279],{"class":62},[46,6458,3656],{"class":319},[46,6460,6461,6464,6466,6468,6471],{"class":48,"line":498},[46,6462,6463],{"class":319},"        c.Request ",[46,6465,1611],{"class":312},[46,6467,6408],{"class":319},[46,6469,6470],{"class":59},"WithContext",[46,6472,6473],{"class":319},"(ctx)\n",[46,6475,6476,6478,6480],{"class":48,"line":503},[46,6477,5761],{"class":319},[46,6479,6289],{"class":59},[46,6481,3688],{"class":319},[46,6483,6484],{"class":48,"line":509},[46,6485,670],{"class":319},[46,6487,6488],{"class":48,"line":1711},[46,6489,680],{"class":319},[262,6491,6492],{"id":6492},"组合使用",[36,6494,6496],{"className":4922,"code":6495,"language":230,"meta":41,"style":41},"r.Any(\"\u002Fmcp\",\n    AuthRequired(authService),  \u002F\u002F 1. 验证 Token\n    WithUserContext(),           \u002F\u002F 2. 桥接用户信息到 request.Context\n    gin.WrapH(mcpHTTP),         \u002F\u002F 3. MCP 处理\n)\n",[43,6497,6498,6510,6521,6532,6545],{"__ignoreMap":41},[46,6499,6500,6502,6504,6506,6508],{"class":48,"line":49},[46,6501,5517],{"class":319},[46,6503,5520],{"class":59},[46,6505,3494],{"class":319},[46,6507,5480],{"class":62},[46,6509,645],{"class":319},[46,6511,6512,6515,6518],{"class":48,"line":56},[46,6513,6514],{"class":59},"    AuthRequired",[46,6516,6517],{"class":319},"(authService),  ",[46,6519,6520],{"class":52},"\u002F\u002F 1. 验证 Token\n",[46,6522,6523,6526,6529],{"class":48,"line":72},[46,6524,6525],{"class":59},"    WithUserContext",[46,6527,6528],{"class":319},"(),           ",[46,6530,6531],{"class":52},"\u002F\u002F 2. 桥接用户信息到 request.Context\n",[46,6533,6534,6537,6539,6542],{"class":48,"line":85},[46,6535,6536],{"class":319},"    gin.",[46,6538,5530],{"class":59},[46,6540,6541],{"class":319},"(mcpHTTP),         ",[46,6543,6544],{"class":52},"\u002F\u002F 3. MCP 处理\n",[46,6546,6547],{"class":48,"line":101},[46,6548,3803],{"class":319},[16,6550,6551],{},"在工具处理器里就能拿到用户信息了：",[36,6553,6555],{"className":4922,"code":6554,"language":230,"meta":41,"style":41},"func (h *Handler) handleListItems(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {\n    userID, ok := ctx.Value(ctxUserID).(uint)\n    if !ok {\n        return mcp.NewToolResultError(\"未认证\"), nil\n    }\n\n    items, _ := h.service.ListByUser(userID)\n    \u002F\u002F ...\n    return mcp.NewToolResultText(result), nil\n}\n",[43,6556,6557,6612,6633,6642,6659,6663,6667,6683,6688,6701],{"__ignoreMap":41},[46,6558,6559,6561,6563,6566,6568,6571,6573,6576,6578,6580,6582,6584,6586,6588,6590,6592,6594,6596,6598,6600,6602,6604,6606,6608,6610],{"class":48,"line":49},[46,6560,5123],{"class":312},[46,6562,3536],{"class":319},[46,6564,6565],{"class":3497},"h ",[46,6567,5154],{"class":312},[46,6569,6570],{"class":59},"Handler",[46,6572,3957],{"class":319},[46,6574,6575],{"class":59},"handleListItems",[46,6577,3494],{"class":319},[46,6579,5128],{"class":3497},[46,6581,5131],{"class":59},[46,6583,3615],{"class":319},[46,6585,5136],{"class":59},[46,6587,659],{"class":319},[46,6589,5141],{"class":3497},[46,6591,119],{"class":59},[46,6593,3615],{"class":319},[46,6595,5148],{"class":59},[46,6597,5151],{"class":319},[46,6599,5154],{"class":312},[46,6601,5157],{"class":59},[46,6603,3615],{"class":319},[46,6605,5162],{"class":59},[46,6607,659],{"class":319},[46,6609,5167],{"class":312},[46,6611,4745],{"class":319},[46,6613,6614,6617,6619,6622,6625,6628,6631],{"class":48,"line":56},[46,6615,6616],{"class":319},"    userID, ok ",[46,6618,4949],{"class":312},[46,6620,6621],{"class":319}," ctx.",[46,6623,6624],{"class":59},"Value",[46,6626,6627],{"class":319},"(ctxUserID).(",[46,6629,6630],{"class":312},"uint",[46,6632,3803],{"class":319},[46,6634,6635,6637,6639],{"class":48,"line":72},[46,6636,3570],{"class":312},[46,6638,6120],{"class":312},[46,6640,6641],{"class":319},"ok {\n",[46,6643,6644,6646,6648,6650,6652,6655,6657],{"class":48,"line":85},[46,6645,5375],{"class":312},[46,6647,5071],{"class":319},[46,6649,5380],{"class":59},[46,6651,3494],{"class":319},[46,6653,6654],{"class":62},"\"未认证\"",[46,6656,5192],{"class":319},[46,6658,5195],{"class":122},[46,6660,6661],{"class":48,"line":101},[46,6662,670],{"class":319},[46,6664,6665],{"class":48,"line":108},[46,6666,105],{"emptyLinePlaceholder":104},[46,6668,6669,6672,6674,6677,6680],{"class":48,"line":114},[46,6670,6671],{"class":319},"    items, _ ",[46,6673,4949],{"class":312},[46,6675,6676],{"class":319}," h.service.",[46,6678,6679],{"class":59},"ListByUser",[46,6681,6682],{"class":319},"(userID)\n",[46,6684,6685],{"class":48,"line":337},[46,6686,6687],{"class":52},"    \u002F\u002F ...\n",[46,6689,6690,6692,6694,6696,6699],{"class":48,"line":369},[46,6691,5179],{"class":312},[46,6693,5071],{"class":319},[46,6695,5184],{"class":59},[46,6697,6698],{"class":319},"(result), ",[46,6700,5195],{"class":122},[46,6702,6703],{"class":48,"line":374},[46,6704,680],{"class":319},[262,6706,6707],{"id":6707},"客户端配置",[16,6709,6710],{},"AI 客户端（如 Claude Desktop）配置 MCP 服务器时带上 Authorization 头：",[36,6712,6714],{"className":607,"code":6713,"language":609,"meta":41,"style":41},"{\n  \"mcpServers\": {\n    \"my-app\": {\n      \"url\": \"http:\u002F\u002Flocalhost:8080\u002Fmcp\",\n      \"headers\": {\n        \"Authorization\": \"Bearer your-token-here\"\n      }\n    }\n  }\n}\n",[43,6715,6716,6720,6726,6732,6744,6751,6761,6766,6770,6774],{"__ignoreMap":41},[46,6717,6718],{"class":48,"line":49},[46,6719,616],{"class":319},[46,6721,6722,6724],{"class":48,"line":56},[46,6723,621],{"class":122},[46,6725,624],{"class":319},[46,6727,6728,6730],{"class":48,"line":72},[46,6729,4963],{"class":122},[46,6731,624],{"class":319},[46,6733,6734,6737,6739,6742],{"class":48,"line":85},[46,6735,6736],{"class":122},"      \"url\"",[46,6738,639],{"class":319},[46,6740,6741],{"class":62},"\"http:\u002F\u002Flocalhost:8080\u002Fmcp\"",[46,6743,645],{"class":319},[46,6745,6746,6749],{"class":48,"line":101},[46,6747,6748],{"class":122},"      \"headers\"",[46,6750,624],{"class":319},[46,6752,6753,6756,6758],{"class":48,"line":108},[46,6754,6755],{"class":122},"        \"Authorization\"",[46,6757,639],{"class":319},[46,6759,6760],{"class":62},"\"Bearer your-token-here\"\n",[46,6762,6763],{"class":48,"line":114},[46,6764,6765],{"class":319},"      }\n",[46,6767,6768],{"class":48,"line":337},[46,6769,670],{"class":319},[46,6771,6772],{"class":48,"line":369},[46,6773,675],{"class":319},[46,6775,6776],{"class":48,"line":374},[46,6777,680],{"class":319},[11,6779,1430],{"id":1430},[131,6781,6782,6792],{},[134,6783,6784],{},[137,6785,6786,6789],{},[140,6787,6788],{},"步骤",[140,6790,6791],{},"说明",[147,6793,6794,6803,6814,6823,6832],{},[137,6795,6796,6800],{},[152,6797,6798],{},[43,6799,4955],{},[152,6801,6802],{},"创建 MCP 协议实例",[137,6804,6805,6811],{},[152,6806,6807,2680,6809],{},[43,6808,5074],{},[43,6810,5117],{},[152,6812,6813],{},"定义并注册工具",[137,6815,6816,6820],{},[152,6817,6818],{},[43,6819,5465],{},[152,6821,6822],{},"转为 HTTP Handler",[137,6824,6825,6829],{},[152,6826,6827],{},[43,6828,5433],{},[152,6830,6831],{},"挂载到 Gin 路由",[137,6833,6834,6842],{},[152,6835,6836,2680,6839],{},[43,6837,6838],{},"AuthRequired",[43,6840,6841],{},"WithUserContext",[152,6843,6844],{},"鉴权 + 用户信息桥接",[16,6846,6847,6848,6851,6852,3388],{},"核心就一句话：",[27,6849,6850],{},"MCP 是 HTTP Handler，挂上去就行。"," 鉴权复用你已有的中间件体系，只需要多写一个桥接函数把用户信息传进 ",[43,6853,5932],{},[1460,6855,6856],{},"html pre.shiki code .sw2iP, html code.shiki .sw2iP{--shiki-light:#6F42C1;--shiki-dark:#B392F0}html pre.shiki code .scXbn, html code.shiki .scXbn{--shiki-light:#032F62;--shiki-dark:#9ECBFF}html .light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html.light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-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 .siTax, html code.shiki .siTax{--shiki-light:#D73A49;--shiki-dark:#F97583}html pre.shiki code .sIX_F, html code.shiki .sIX_F{--shiki-light:#24292E;--shiki-dark:#E1E4E8}html pre.shiki code .sHbNN, html code.shiki .sHbNN{--shiki-light:#6A737D;--shiki-dark:#6A737D}html pre.shiki code .sPK6S, html code.shiki .sPK6S{--shiki-light:#E36209;--shiki-dark:#FFAB70}html pre.shiki code .suQ91, html code.shiki .suQ91{--shiki-light:#005CC5;--shiki-dark:#79B8FF}",{"title":41,"searchDepth":56,"depth":72,"links":6858},[6859,6860,6861,6862,6869,6875],{"id":4835,"depth":56,"text":4836},{"id":4845,"depth":56,"text":4846},{"id":4862,"depth":56,"text":4862},{"id":4888,"depth":56,"text":4888,"children":6863},[6864,6865,6866,6867,6868],{"id":4891,"depth":72,"text":4892},{"id":4918,"depth":72,"text":4919},{"id":5035,"depth":72,"text":5036},{"id":5422,"depth":72,"text":5423},{"id":5543,"depth":72,"text":5544},{"id":5905,"depth":56,"text":5906,"children":6870},[6871,6872,6873,6874],{"id":4468,"depth":72,"text":4468},{"id":5925,"depth":72,"text":5926},{"id":6492,"depth":72,"text":6492},{"id":6707,"depth":72,"text":6707},{"id":1430,"depth":56,"text":1430},"介绍如何使用 mark3labs\u002Fmcp-go 与 Gin 框架集成，搭建一个带鉴权的 MCP（Model Context Protocol）服务器，让 AI Agent 能直接调用你的后端服务。","\u002Fposts\u002Fgo-mcp-gin\u002Fimg\u002Fcover.svg",{},"\u002Fposts\u002Fgo-mcp-gin","2026-06-01T00:50:00",{"title":4830,"description":6876},"Go 工程实践","posts\u002Fgo-mcp-gin\u002Findex",[1498,1497,6885,1499,6886],"Gin","后端","2026-06-01","5fZl1Vigs2lHb5fHPz-0YZFyFD38ws1heOouZUG1g6M",{"id":6890,"title":6891,"author":6,"body":6892,"description":7654,"difficulty":6,"draft":1486,"extension":1487,"image":7655,"meta":7656,"navigation":104,"path":7657,"pinned":1486,"published":7658,"seo":7659,"series":6,"seriesOrder":6,"stem":7660,"tags":7661,"updated":6,"__hash__":7663},"posts\u002Fposts\u002Fdws-dingtalk-cli\u002Findex.md","DWS — 钉钉官方出品的 AI 友好命令行工具",{"type":8,"value":6893,"toc":7640},[6894,6898,6901,6908,7032,7034,7037,7093,7096,7110,7113,7116,7123,7355,7362,7366,7369,7389,7392,7407,7410,7430,7433,7436,7443,7509,7512,7514,7518,7521,7573,7577,7598,7602,7605,7619,7622,7626,7629,7631,7634,7637],[11,6895,6897],{"id":6896},"dws-是什么","dws 是什么",[16,6899,6900],{},"dws（DingTalk Workspace CLI）是阿里巴巴 2026 年开源的钉钉命令行工具，Apache-2.0 协议，Go 语言编写。",[16,6902,6903,6904,6907],{},"它的定位很明确：",[27,6905,6906],{},"把钉钉的所有产品能力暴露给 AI Agent 和命令行用户","。不用打开钉钉 App，不用写 API 调用代码，一条命令搞定。",[36,6909,6911],{"className":38,"code":6910,"language":40,"meta":41,"style":41},"# 查看今天有什么会\ndws calendar event list --start 2026-06-01 --end 2026-06-01\n\n# 给群里发条消息\ndws chat send --group-id xxx --content \"今天发布推迟到下午3点\"\n\n# 创建一个待办\ndws todo task create --subject \"Review PR #123\" --due-date 2026-06-02\n\n# 读取钉钉文档\ndws doc read --url \"https:\u002F\u002Falidocs.dingtalk.com\u002Fi\u002Fnodes\u002Fxxx\"\n",[43,6912,6913,6918,6943,6947,6952,6974,6978,6983,7007,7011,7016],{"__ignoreMap":41},[46,6914,6915],{"class":48,"line":49},[46,6916,6917],{"class":52},"# 查看今天有什么会\n",[46,6919,6920,6923,6926,6929,6931,6934,6937,6940],{"class":48,"line":56},[46,6921,6922],{"class":59},"dws",[46,6924,6925],{"class":62}," calendar",[46,6927,6928],{"class":62}," event",[46,6930,1077],{"class":62},[46,6932,6933],{"class":122}," --start",[46,6935,6936],{"class":62}," 2026-06-01",[46,6938,6939],{"class":122}," --end",[46,6941,6942],{"class":62}," 2026-06-01\n",[46,6944,6945],{"class":48,"line":72},[46,6946,105],{"emptyLinePlaceholder":104},[46,6948,6949],{"class":48,"line":85},[46,6950,6951],{"class":52},"# 给群里发条消息\n",[46,6953,6954,6956,6959,6962,6965,6968,6971],{"class":48,"line":101},[46,6955,6922],{"class":59},[46,6957,6958],{"class":62}," chat",[46,6960,6961],{"class":62}," send",[46,6963,6964],{"class":122}," --group-id",[46,6966,6967],{"class":62}," xxx",[46,6969,6970],{"class":122}," --content",[46,6972,6973],{"class":62}," \"今天发布推迟到下午3点\"\n",[46,6975,6976],{"class":48,"line":108},[46,6977,105],{"emptyLinePlaceholder":104},[46,6979,6980],{"class":48,"line":114},[46,6981,6982],{"class":52},"# 创建一个待办\n",[46,6984,6985,6987,6990,6993,6995,6998,7001,7004],{"class":48,"line":337},[46,6986,6922],{"class":59},[46,6988,6989],{"class":62}," todo",[46,6991,6992],{"class":62}," task",[46,6994,516],{"class":62},[46,6996,6997],{"class":122}," --subject",[46,6999,7000],{"class":62}," \"Review PR #123\"",[46,7002,7003],{"class":122}," --due-date",[46,7005,7006],{"class":62}," 2026-06-02\n",[46,7008,7009],{"class":48,"line":369},[46,7010,105],{"emptyLinePlaceholder":104},[46,7012,7013],{"class":48,"line":374},[46,7014,7015],{"class":52},"# 读取钉钉文档\n",[46,7017,7018,7020,7023,7026,7029],{"class":48,"line":380},[46,7019,6922],{"class":59},[46,7021,7022],{"class":62}," doc",[46,7024,7025],{"class":62}," read",[46,7027,7028],{"class":122}," --url",[46,7030,7031],{"class":62}," \"https:\u002F\u002Falidocs.dingtalk.com\u002Fi\u002Fnodes\u002Fxxx\"\n",[11,7033,215],{"id":215},[16,7035,7036],{},"三种方式，选你喜欢的：",[36,7038,7040],{"className":38,"code":7039,"language":40,"meta":41,"style":41},"# npm（推荐，跨平台自动下载二进制）\nnpm install -g dingtalk-workspace-cli\n\n# Homebrew（macOS\u002FLinux）\nbrew install dws\n\n# 直接下载二进制\n# https:\u002F\u002Fgithub.com\u002Fopen-dingtalk\u002Fdingtalk-workspace-cli\u002Freleases\n",[43,7041,7042,7047,7060,7064,7069,7079,7083,7088],{"__ignoreMap":41},[46,7043,7044],{"class":48,"line":49},[46,7045,7046],{"class":52},"# npm（推荐，跨平台自动下载二进制）\n",[46,7048,7049,7052,7054,7057],{"class":48,"line":56},[46,7050,7051],{"class":59},"npm",[46,7053,233],{"class":62},[46,7055,7056],{"class":122}," -g",[46,7058,7059],{"class":62}," dingtalk-workspace-cli\n",[46,7061,7062],{"class":48,"line":72},[46,7063,105],{"emptyLinePlaceholder":104},[46,7065,7066],{"class":48,"line":85},[46,7067,7068],{"class":52},"# Homebrew（macOS\u002FLinux）\n",[46,7070,7071,7074,7076],{"class":48,"line":101},[46,7072,7073],{"class":59},"brew",[46,7075,233],{"class":62},[46,7077,7078],{"class":62}," dws\n",[46,7080,7081],{"class":48,"line":108},[46,7082,105],{"emptyLinePlaceholder":104},[46,7084,7085],{"class":48,"line":114},[46,7086,7087],{"class":52},"# 直接下载二进制\n",[46,7089,7090],{"class":48,"line":337},[46,7091,7092],{"class":52},"# https:\u002F\u002Fgithub.com\u002Fopen-dingtalk\u002Fdingtalk-workspace-cli\u002Freleases\n",[16,7094,7095],{},"安装后认证：",[36,7097,7099],{"className":38,"code":7098,"language":40,"meta":41,"style":41},"dws auth login\n",[43,7100,7101],{"__ignoreMap":41},[46,7102,7103,7105,7107],{"class":48,"line":49},[46,7104,6922],{"class":59},[46,7106,344],{"class":62},[46,7108,7109],{"class":62}," login\n",[16,7111,7112],{},"会打开浏览器走 OAuth 设备流认证，token 加密存储在系统钥匙串里（PBKDF2 + AES-256-GCM）。",[11,7114,7115],{"id":7115},"覆盖的钉钉产品",[16,7117,7118,7119,7122],{},"这是最夸张的部分 — ",[27,7120,7121],{},"19 个产品，213 个命令","，基本把钉钉全家桶都搬到了命令行：",[131,7124,7125,7137],{},[134,7126,7127],{},[137,7128,7129,7132,7135],{},[140,7130,7131],{},"产品",[140,7133,7134],{},"命令数",[140,7136,6791],{},[147,7138,7139,7153,7167,7181,7195,7209,7223,7237,7250,7264,7277,7291,7305,7318,7331,7344],{},[137,7140,7141,7147,7150],{},[152,7142,7143,7144],{},"聊天 ",[43,7145,7146],{},"chat",[152,7148,7149],{},"57",[152,7151,7152],{},"消息收发、群管理、机器人、已读状态、Webhook",[137,7154,7155,7161,7164],{},[152,7156,7157,7158],{},"AI 表格 ",[43,7159,7160],{},"aitable",[152,7162,7163],{},"42",[152,7165,7166],{},"Base\u002F数据表\u002F字段\u002F记录\u002F视图\u002F图表\u002F仪表盘，完整 CRUD",[137,7168,7169,7175,7178],{},[152,7170,7171,7172],{},"电子表格 ",[43,7173,7174],{},"sheet",[152,7176,7177],{},"34",[152,7179,7180],{},"工作表读写、行列操作、合并、筛选视图、导出",[137,7182,7183,7189,7192],{},[152,7184,7185,7186],{},"文档 ",[43,7187,7188],{},"doc",[152,7190,7191],{},"21",[152,7193,7194],{},"搜索、读写、块级编辑、评论、复制移动",[137,7196,7197,7203,7206],{},[152,7198,7199,7200],{},"会议纪要 ",[43,7201,7202],{},"minutes",[152,7204,7205],{},"19",[152,7207,7208],{},"列表、摘要、转写、思维导图、发言人、热词",[137,7210,7211,7217,7220],{},[152,7212,7213,7214],{},"日历 ",[43,7215,7216],{},"calendar",[152,7218,7219],{},"14",[152,7221,7222],{},"事件 CRUD、会议室、忙闲查询、时间建议",[137,7224,7225,7231,7234],{},[152,7226,7227,7228],{},"审批 ",[43,7229,7230],{},"oa",[152,7232,7233],{},"9",[152,7235,7236],{},"待办、发起、同意\u002F拒绝\u002F撤销",[137,7238,7239,7245,7247],{},[152,7240,7241,7242],{},"云盘 ",[43,7243,7244],{},"drive",[152,7246,7233],{},[152,7248,7249],{},"列表、下载、上传、创建目录",[137,7251,7252,7258,7261],{},[152,7253,7254,7255],{},"知识库 ",[43,7256,7257],{},"wiki",[152,7259,7260],{},"7",[152,7262,7263],{},"空间管理、成员管理",[137,7265,7266,7272,7274],{},[152,7267,7268,7269],{},"汇报 ",[43,7270,7271],{},"report",[152,7273,7260],{},[152,7275,7276],{},"日志\u002F日报\u002F周报，按模板创建、统计",[137,7278,7279,7285,7288],{},[152,7280,7281,7282],{},"待办 ",[43,7283,7284],{},"todo",[152,7286,7287],{},"6",[152,7289,7290],{},"创建、完成、删除，支持优先级和循环",[137,7292,7293,7299,7302],{},[152,7294,7295,7296],{},"邮件 ",[43,7297,7298],{},"mail",[152,7300,7301],{},"4",[152,7303,7304],{},"邮箱查询、邮件搜索、发送",[137,7306,7307,7313,7315],{},[152,7308,7309,7310],{},"考勤 ",[43,7311,7312],{},"attendance",[152,7314,7301],{},[152,7316,7317],{},"打卡记录、排班、汇总",[137,7319,7320,7326,7328],{},[152,7321,7322,7323],{},"通讯录 ",[43,7324,7325],{},"contact",[152,7327,3445],{},[152,7329,7330],{},"用户搜索、部门查询",[137,7332,7333,7339,7341],{},[152,7334,7335,7336],{},"DING 消息 ",[43,7337,7338],{},"ding",[152,7340,3679],{},[152,7342,7343],{},"发送\u002F撤回（应用内、短信、电话）",[137,7345,7346,7349,7352],{},[152,7347,7348],{},"其他",[152,7350,7351],{},"若干",[152,7353,7354],{},"AI 搜索、AI 应用、直播、开放平台文档",[16,7356,7357,7358,7361],{},"还有一个万能命令 ",[43,7359,7360],{},"dws api","，可以直接调用任意钉钉 OpenAPI，不受这 213 个命令的限制。",[11,7363,7365],{"id":7364},"agent-skill-系统","Agent Skill 系统",[16,7367,7368],{},"dws 内置了一套完整的 Agent Skill，可以直接被 AI 编程工具发现和使用：",[718,7370,7371,7377,7383],{},[721,7372,7373,7376],{},[27,7374,7375],{},"Claude Code"," — 通过 SKILL.md 自动加载",[721,7378,7379,7382],{},[27,7380,7381],{},"Cursor"," — 支持 MCP 协议接入",[721,7384,7385,7388],{},[27,7386,7387],{},"Codex \u002F OpenCode \u002F Qoder"," — 通过 skill 安装命令接入",[16,7390,7391],{},"安装 skill：",[36,7393,7395],{"className":38,"code":7394,"language":40,"meta":41,"style":41},"dws skill setup\n",[43,7396,7397],{"__ignoreMap":41},[46,7398,7399,7401,7404],{"class":48,"line":49},[46,7400,6922],{"class":59},[46,7402,7403],{"class":62}," skill",[46,7405,7406],{"class":62}," setup\n",[16,7408,7409],{},"SKILL.md 里定义了：",[718,7411,7412,7421,7424,7427],{},[721,7413,7414,7415,7417,7418,7420],{},"16 个产品的意图判断决策树（用户说\"表格\"→ ",[43,7416,7160],{},"，说\"打卡\"→ ",[43,7419,7312],{},"）",[721,7422,7423],{},"危险操作确认流程（删除、撤回等必须先确认）",[721,7425,7426],{},"错误处理规范（verbose 重试、recovery 事件 ID）",[721,7428,7429],{},"URL 分流规则（alidocs.dingtalk.com 的多种路径格式自动识别）",[16,7431,7432],{},"Agent 读到这份 SKILL.md 就知道什么时候该调什么命令，不需要额外的 prompt 工程。",[11,7434,7435],{"id":7435},"自动升级",[16,7437,7438,7439,7442],{},"内置 ",[43,7440,7441],{},"dws upgrade","，不需要重新下载安装包：",[36,7444,7446],{"className":38,"code":7445,"language":40,"meta":41,"style":41},"dws upgrade              # 交互式升级到最新版\ndws upgrade --check      # 仅检查是否有新版本\ndws upgrade --list       # 列出最近版本\ndws upgrade --version v1.0.5  # 升级到指定版本\ndws upgrade --rollback   # 回滚到上一版本\n",[43,7447,7448,7458,7470,7482,7497],{"__ignoreMap":41},[46,7449,7450,7452,7455],{"class":48,"line":49},[46,7451,6922],{"class":59},[46,7453,7454],{"class":62}," upgrade",[46,7456,7457],{"class":52},"              # 交互式升级到最新版\n",[46,7459,7460,7462,7464,7467],{"class":48,"line":56},[46,7461,6922],{"class":59},[46,7463,7454],{"class":62},[46,7465,7466],{"class":122}," --check",[46,7468,7469],{"class":52},"      # 仅检查是否有新版本\n",[46,7471,7472,7474,7476,7479],{"class":48,"line":72},[46,7473,6922],{"class":59},[46,7475,7454],{"class":62},[46,7477,7478],{"class":122}," --list",[46,7480,7481],{"class":52},"       # 列出最近版本\n",[46,7483,7484,7486,7488,7491,7494],{"class":48,"line":85},[46,7485,6922],{"class":59},[46,7487,7454],{"class":62},[46,7489,7490],{"class":122}," --version",[46,7492,7493],{"class":62}," v1.0.5",[46,7495,7496],{"class":52},"  # 升级到指定版本\n",[46,7498,7499,7501,7503,7506],{"class":48,"line":101},[46,7500,6922],{"class":59},[46,7502,7454],{"class":62},[46,7504,7505],{"class":122}," --rollback",[46,7507,7508],{"class":52},"   # 回滚到上一版本\n",[16,7510,7511],{},"升级流程：下载 → SHA256 校验 → 备份当前版本 → 原子替换 → 更新 Skill。失败可一键回滚。",[11,7513,686],{"id":686},[262,7515,7517],{"id":7516},"_1-为-ai-agent-专门优化","1. 为 AI Agent 专门优化",[16,7519,7520],{},"这可能是目前对 AI 最友好的 CLI 工具：",[718,7522,7523,7544,7554,7563],{},[721,7524,7525,7528,7529,7532,7533,7536,7537,7532,7540,7543],{},[27,7526,7527],{},"智能纠错"," — AI 模型经常生成错误的参数格式（",[43,7530,7531],{},"--userId"," 应该是 ",[43,7534,7535],{},"--user-id","，",[43,7538,7539],{},"--limit100",[43,7541,7542],{},"--limit 100","），dws 的 5 阶段 Pipeline 会自动修正",[721,7545,7546,7549,7550,7553],{},[27,7547,7548],{},"Schema 发现"," — ",[43,7551,7552],{},"dws schema"," 让 Agent 动态查询可用工具和参数，不需要硬编码",[721,7555,7556,7549,7559,7562],{},[27,7557,7558],{},"jq 过滤",[43,7560,7561],{},"--jq '.data[] | {name, id}'"," 直接过滤输出，减少 Agent 消耗的 token",[721,7564,7565,7568,7569,7572],{},[27,7566,7567],{},"JSON 输出"," — 所有命令支持 ",[43,7570,7571],{},"--format json","，方便程序解析",[262,7574,7576],{"id":7575},"_2-安全设计","2. 安全设计",[718,7578,7579,7582,7585,7588,7595],{},[721,7580,7581],{},"token 用 PBKDF2 + AES-256-GCM 加密，密钥绑定设备 MAC 地址",[721,7583,7584],{},"域名白名单，只能访问钉钉官方 API",[721,7586,7587],{},"强制 HTTPS",[721,7589,7590,7591,7594],{},"写操作必须显式 ",[43,7592,7593],{},"--yes"," 确认",[721,7596,7597],{},"完整审计链",[262,7599,7601],{"id":7600},"_3-发现驱动架构","3. 发现驱动架构",[16,7603,7604],{},"不是把所有命令硬编码在代码里，而是：",[1747,7606,7607,7610,7613,7616],{},[721,7608,7609],{},"从钉钉 MCP 服务器注册中心拉取元数据",[721,7611,7612],{},"运行时发现服务端能力",[721,7614,7615],{},"转换成统一的中间表示（IR）",[721,7617,7618],{},"动态挂载到命令树",[16,7620,7621],{},"这意味着钉钉新增产品能力时，dws 不需要发版就能支持。",[262,7623,7625],{"id":7624},"_4-多平台分发","4. 多平台分发",[16,7627,7628],{},"GoReleaser 交叉编译 + Homebrew formula + npm 包 + shell\u002FPowerShell 安装脚本，覆盖 Windows、macOS、Linux。",[11,7630,1430],{"id":1430},[16,7632,7633],{},"dws 把钉钉从一个\"必须打开 App 才能用\"的工具变成了一个\"命令行 + AI Agent 随时调用\"的平台。213 个命令覆盖日常办公的方方面面，Agent Skill 系统让 AI 编程工具可以直接理解和使用这些能力。",[16,7635,7636],{},"如果你是钉钉重度用户，或者在做企业内部的 AI Agent 集成，dws 值得一试。",[1460,7638,7639],{},"html pre.shiki code .sHbNN, html code.shiki .sHbNN{--shiki-light:#6A737D;--shiki-dark:#6A737D}html pre.shiki code .sw2iP, html code.shiki .sw2iP{--shiki-light:#6F42C1;--shiki-dark:#B392F0}html pre.shiki code .scXbn, html code.shiki .scXbn{--shiki-light:#032F62;--shiki-dark:#9ECBFF}html pre.shiki code .suQ91, html code.shiki .suQ91{--shiki-light:#005CC5;--shiki-dark:#79B8FF}html .light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html.light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-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":41,"searchDepth":56,"depth":72,"links":7641},[7642,7643,7644,7645,7646,7647,7653],{"id":6896,"depth":56,"text":6897},{"id":215,"depth":56,"text":215},{"id":7115,"depth":56,"text":7115},{"id":7364,"depth":56,"text":7365},{"id":7435,"depth":56,"text":7435},{"id":686,"depth":56,"text":686,"children":7648},[7649,7650,7651,7652],{"id":7516,"depth":72,"text":7517},{"id":7575,"depth":72,"text":7576},{"id":7600,"depth":72,"text":7601},{"id":7624,"depth":72,"text":7625},{"id":1430,"depth":56,"text":1430},"介绍阿里巴巴官方的 DingTalk Workspace CLI（dws），一个覆盖钉钉 19 个产品、213 个命令的 AI 友好命令行工具，支持自动升级和 Agent Skill 系统。","\u002Fposts\u002Fdws-dingtalk-cli\u002Fimg\u002Fcover.svg",{},"\u002Fposts\u002Fdws-dingtalk-cli","2026-06-01T00:51:00",{"title":6891,"description":7654},"posts\u002Fdws-dingtalk-cli\u002Findex",[7662,1496,1499,1498,1500],"钉钉","4ca65VUZ2Sz9yG8dNtftrBDDHE6QowA3ed9g4egQkKI",{"id":7665,"title":7666,"author":6,"body":7667,"description":8628,"difficulty":2295,"draft":1486,"extension":1487,"image":8629,"meta":8630,"navigation":104,"path":8631,"pinned":1486,"published":8632,"seo":8633,"series":6882,"seriesOrder":56,"stem":8634,"tags":8635,"updated":8639,"__hash__":8640},"posts\u002Fposts\u002Fgo-embed-frontend\u002Findex.md","Go 单文件打包实战：把前端塞进二进制",{"type":8,"value":7668,"toc":8611},[7669,7671,7678,7681,7684,7695,7698,7702,8035,8039,8147,8151,8284,8287,8291,8300,8317,8328,8346,8360,8366,8374,8473,8479,8483,8490,8499,8502,8538,8541,8546,8585,8587,8593,8605,8608],[11,7670,3364],{"id":3364},[16,7672,7673,7674,7677],{},"一个 Gin + Vue 3 的全栈项目，之前的发布方式是：一个 Go 二进制 + 一个 ",[43,7675,7676],{},"web\u002F"," 前端静态目录，必须放一起才能运行。",[16,7679,7680],{},"为了让部署更丝滑——一个 exe 拖哪都能跑——决定把前端构建产物嵌入到 Go 二进制里。",[11,7682,7683],{"id":7683},"核心思路",[16,7685,7686,7687,7690,7691,7694],{},"利用 Go 1.16 引入的 ",[43,7688,7689],{},"\u002F\u002Fgo:embed"," 指令，在编译时将前端 ",[43,7692,7693],{},"dist\u002F"," 目录嵌入二进制，运行时通过嵌入的文件系统提供 SPA 服务。",[11,7696,7697],{"id":7697},"逐步实现",[262,7699,7701],{"id":7700},"_1-创建嵌入模块","1. 创建嵌入模块",[36,7703,7705],{"className":4922,"code":7704,"language":230,"meta":41,"style":41},"\u002F\u002F service\u002Fweb\u002Fembed.go\npackage web\n\nimport (\n    \"embed\"\n    \"io\u002Ffs\"\n    \"mime\"\n    \"net\u002Fhttp\"\n    \"path\u002Ffilepath\"\n    \"strings\"\n\n    \"github.com\u002Fgin-gonic\u002Fgin\"\n)\n\n\u002F\u002Fgo:embed dist\nvar embedFS embed.FS\n\nfunc SPAHandler() gin.HandlerFunc {\n    sub, _ := fs.Sub(embedFS, \"dist\")\n\n    return func(c *gin.Context) {\n        p := strings.TrimPrefix(c.Request.URL.Path, \"\u002F\")\n        if p == \"\" {\n            p = \"index.html\"\n        }\n\n        data, err := fs.ReadFile(sub, p)\n        if err != nil {\n            \u002F\u002F SPA fallback：文件不存在则返回 index.html\n            data, _ = fs.ReadFile(sub, \"index.html\")\n        }\n\n        c.Data(http.StatusOK, mime.TypeByExtension(filepath.Ext(p)), data)\n    }\n}\n",[43,7706,7707,7712,7720,7724,7730,7740,7749,7758,7767,7776,7785,7789,7798,7802,7806,7811,7826,7830,7847,7868,7872,7892,7912,7925,7935,7939,7943,7958,7970,7975,7994,7998,8002,8025,8030],{"__ignoreMap":41},[46,7708,7709],{"class":48,"line":49},[46,7710,7711],{"class":52},"\u002F\u002F service\u002Fweb\u002Fembed.go\n",[46,7713,7714,7717],{"class":48,"line":56},[46,7715,7716],{"class":312},"package",[46,7718,7719],{"class":59}," web\n",[46,7721,7722],{"class":48,"line":72},[46,7723,105],{"emptyLinePlaceholder":104},[46,7725,7726,7728],{"class":48,"line":85},[46,7727,4930],{"class":312},[46,7729,6327],{"class":319},[46,7731,7732,7735,7738],{"class":48,"line":101},[46,7733,7734],{"class":62},"    \"",[46,7736,7737],{"class":59},"embed",[46,7739,1742],{"class":62},[46,7741,7742,7744,7747],{"class":48,"line":108},[46,7743,7734],{"class":62},[46,7745,7746],{"class":59},"io\u002Ffs",[46,7748,1742],{"class":62},[46,7750,7751,7753,7756],{"class":48,"line":114},[46,7752,7734],{"class":62},[46,7754,7755],{"class":59},"mime",[46,7757,1742],{"class":62},[46,7759,7760,7762,7765],{"class":48,"line":337},[46,7761,7734],{"class":62},[46,7763,7764],{"class":59},"net\u002Fhttp",[46,7766,1742],{"class":62},[46,7768,7769,7771,7774],{"class":48,"line":369},[46,7770,7734],{"class":62},[46,7772,7773],{"class":59},"path\u002Ffilepath",[46,7775,1742],{"class":62},[46,7777,7778,7780,7783],{"class":48,"line":374},[46,7779,7734],{"class":62},[46,7781,7782],{"class":59},"strings",[46,7784,1742],{"class":62},[46,7786,7787],{"class":48,"line":380},[46,7788,105],{"emptyLinePlaceholder":104},[46,7790,7791,7793,7796],{"class":48,"line":484},[46,7792,7734],{"class":62},[46,7794,7795],{"class":59},"github.com\u002Fgin-gonic\u002Fgin",[46,7797,1742],{"class":62},[46,7799,7800],{"class":48,"line":498},[46,7801,3803],{"class":319},[46,7803,7804],{"class":48,"line":503},[46,7805,105],{"emptyLinePlaceholder":104},[46,7807,7808],{"class":48,"line":509},[46,7809,7810],{"class":52},"\u002F\u002Fgo:embed dist\n",[46,7812,7813,7816,7819,7821,7823],{"class":48,"line":1711},[46,7814,7815],{"class":312},"var",[46,7817,7818],{"class":319}," embedFS ",[46,7820,7737],{"class":59},[46,7822,3615],{"class":319},[46,7824,7825],{"class":59},"FS\n",[46,7827,7828],{"class":48,"line":1716},[46,7829,105],{"emptyLinePlaceholder":104},[46,7831,7832,7834,7837,7839,7841,7843,7845],{"class":48,"line":1725},[46,7833,5123],{"class":312},[46,7835,7836],{"class":59}," SPAHandler",[46,7838,6370],{"class":319},[46,7840,5750],{"class":59},[46,7842,3615],{"class":319},[46,7844,5968],{"class":59},[46,7846,3142],{"class":319},[46,7848,7849,7852,7854,7857,7860,7863,7866],{"class":48,"line":1730},[46,7850,7851],{"class":319},"    sub, _ ",[46,7853,4949],{"class":312},[46,7855,7856],{"class":319}," fs.",[46,7858,7859],{"class":59},"Sub",[46,7861,7862],{"class":319},"(embedFS, ",[46,7864,7865],{"class":62},"\"dist\"",[46,7867,3803],{"class":319},[46,7869,7870],{"class":48,"line":1938},[46,7871,105],{"emptyLinePlaceholder":104},[46,7873,7874,7876,7878,7880,7882,7884,7886,7888,7890],{"class":48,"line":1946},[46,7875,5179],{"class":312},[46,7877,5977],{"class":312},[46,7879,3494],{"class":319},[46,7881,5744],{"class":3497},[46,7883,5747],{"class":312},[46,7885,5750],{"class":59},[46,7887,3615],{"class":319},[46,7889,5136],{"class":59},[46,7891,4745],{"class":319},[46,7893,7894,7897,7899,7901,7904,7907,7910],{"class":48,"line":1951},[46,7895,7896],{"class":319},"        p ",[46,7898,4949],{"class":312},[46,7900,6084],{"class":319},[46,7902,7903],{"class":59},"TrimPrefix",[46,7905,7906],{"class":319},"(c.Request.URL.Path, ",[46,7908,7909],{"class":62},"\"\u002F\"",[46,7911,3803],{"class":319},[46,7913,7914,7916,7919,7921,7923],{"class":48,"line":2575},[46,7915,6016],{"class":312},[46,7917,7918],{"class":319}," p ",[46,7920,6022],{"class":312},[46,7922,6025],{"class":62},[46,7924,3142],{"class":319},[46,7926,7927,7930,7932],{"class":48,"line":2589},[46,7928,7929],{"class":319},"            p ",[46,7931,1611],{"class":312},[46,7933,7934],{"class":62}," \"index.html\"\n",[46,7936,7937],{"class":48,"line":2596},[46,7938,6070],{"class":319},[46,7940,7941],{"class":48,"line":2601},[46,7942,105],{"emptyLinePlaceholder":104},[46,7944,7945,7948,7950,7952,7955],{"class":48,"line":2607},[46,7946,7947],{"class":319},"        data, err ",[46,7949,4949],{"class":312},[46,7951,7856],{"class":319},[46,7953,7954],{"class":59},"ReadFile",[46,7956,7957],{"class":319},"(sub, p)\n",[46,7959,7960,7962,7964,7966,7968],{"class":48,"line":2615},[46,7961,6016],{"class":312},[46,7963,5362],{"class":319},[46,7965,5365],{"class":312},[46,7967,5368],{"class":122},[46,7969,3142],{"class":319},[46,7971,7972],{"class":48,"line":2623},[46,7973,7974],{"class":52},"            \u002F\u002F SPA fallback：文件不存在则返回 index.html\n",[46,7976,7977,7980,7982,7984,7986,7989,7992],{"class":48,"line":2628},[46,7978,7979],{"class":319},"            data, _ ",[46,7981,1611],{"class":312},[46,7983,7856],{"class":319},[46,7985,7954],{"class":59},[46,7987,7988],{"class":319},"(sub, ",[46,7990,7991],{"class":62},"\"index.html\"",[46,7993,3803],{"class":319},[46,7995,7996],{"class":48,"line":2634},[46,7997,6070],{"class":319},[46,7999,8000],{"class":48,"line":2643},[46,8001,105],{"emptyLinePlaceholder":104},[46,8003,8005,8007,8010,8013,8016,8019,8022],{"class":48,"line":8004},33,[46,8006,5761],{"class":319},[46,8008,8009],{"class":59},"Data",[46,8011,8012],{"class":319},"(http.StatusOK, mime.",[46,8014,8015],{"class":59},"TypeByExtension",[46,8017,8018],{"class":319},"(filepath.",[46,8020,8021],{"class":59},"Ext",[46,8023,8024],{"class":319},"(p)), data)\n",[46,8026,8028],{"class":48,"line":8027},34,[46,8029,670],{"class":319},[46,8031,8033],{"class":48,"line":8032},35,[46,8034,680],{"class":319},[262,8036,8038],{"id":8037},"_2-改造路由","2. 改造路由",[36,8040,8042],{"className":4922,"code":8041,"language":230,"meta":41,"style":41},"\u002F\u002F router.go\nimport \"myproject\u002Fweb\"\n\n\u002F\u002F 替换原来的磁盘静态文件服务\nwebHandler := web.SPAHandler()\nrouter.GET(\"\u002F\", webHandler)\nrouter.GET(\"\u002Fassets\u002F*filepath\", webHandler)\nrouter.GET(\"\u002Ffavicon.ico\", webHandler)\nrouter.GET(\"\u002Ffavicon.svg\", webHandler)\nrouter.NoRoute(webHandler)\n",[43,8043,8044,8049,8060,8064,8069,8084,8098,8111,8124,8137],{"__ignoreMap":41},[46,8045,8046],{"class":48,"line":49},[46,8047,8048],{"class":52},"\u002F\u002F router.go\n",[46,8050,8051,8053,8055,8058],{"class":48,"line":56},[46,8052,4930],{"class":312},[46,8054,1546],{"class":62},[46,8056,8057],{"class":59},"myproject\u002Fweb",[46,8059,1742],{"class":62},[46,8061,8062],{"class":48,"line":72},[46,8063,105],{"emptyLinePlaceholder":104},[46,8065,8066],{"class":48,"line":85},[46,8067,8068],{"class":52},"\u002F\u002F 替换原来的磁盘静态文件服务\n",[46,8070,8071,8074,8076,8079,8082],{"class":48,"line":101},[46,8072,8073],{"class":319},"webHandler ",[46,8075,4949],{"class":312},[46,8077,8078],{"class":319}," web.",[46,8080,8081],{"class":59},"SPAHandler",[46,8083,3688],{"class":319},[46,8085,8086,8089,8091,8093,8095],{"class":48,"line":108},[46,8087,8088],{"class":319},"router.",[46,8090,5730],{"class":59},[46,8092,3494],{"class":319},[46,8094,7909],{"class":62},[46,8096,8097],{"class":319},", webHandler)\n",[46,8099,8100,8102,8104,8106,8109],{"class":48,"line":114},[46,8101,8088],{"class":319},[46,8103,5730],{"class":59},[46,8105,3494],{"class":319},[46,8107,8108],{"class":62},"\"\u002Fassets\u002F*filepath\"",[46,8110,8097],{"class":319},[46,8112,8113,8115,8117,8119,8122],{"class":48,"line":337},[46,8114,8088],{"class":319},[46,8116,5730],{"class":59},[46,8118,3494],{"class":319},[46,8120,8121],{"class":62},"\"\u002Ffavicon.ico\"",[46,8123,8097],{"class":319},[46,8125,8126,8128,8130,8132,8135],{"class":48,"line":369},[46,8127,8088],{"class":319},[46,8129,5730],{"class":59},[46,8131,3494],{"class":319},[46,8133,8134],{"class":62},"\"\u002Ffavicon.svg\"",[46,8136,8097],{"class":319},[46,8138,8139,8141,8144],{"class":48,"line":374},[46,8140,8088],{"class":319},[46,8142,8143],{"class":59},"NoRoute",[46,8145,8146],{"class":319},"(webHandler)\n",[262,8148,8150],{"id":8149},"_3-一键构建脚本","3. 一键构建脚本",[36,8152,8154],{"className":38,"code":8153,"language":40,"meta":41,"style":41},"#!\u002Fbin\u002Fbash\n# build-standalone.sh\n\nset -e\n\n# 1. 构建前端\npnpm install --no-frozen-lockfile\nnpx vite build\n\n# 2. 复制到嵌入目录\nrm -rf service\u002Fweb\u002Fdist\ncp -r dist service\u002Fweb\u002Fdist\n\n# 3. 编译 Go 二进制\ncd service\ngo build -o myapp.exe \\\n    -ldflags=\"-X myproject\u002Fglobal.RUNCODE=release\" \\\n    main.go\n",[43,8155,8156,8161,8166,8170,8178,8182,8187,8197,8207,8211,8216,8227,8240,8244,8249,8256,8269,8279],{"__ignoreMap":41},[46,8157,8158],{"class":48,"line":49},[46,8159,8160],{"class":52},"#!\u002Fbin\u002Fbash\n",[46,8162,8163],{"class":48,"line":56},[46,8164,8165],{"class":52},"# build-standalone.sh\n",[46,8167,8168],{"class":48,"line":72},[46,8169,105],{"emptyLinePlaceholder":104},[46,8171,8172,8175],{"class":48,"line":85},[46,8173,8174],{"class":122},"set",[46,8176,8177],{"class":122}," -e\n",[46,8179,8180],{"class":48,"line":101},[46,8181,105],{"emptyLinePlaceholder":104},[46,8183,8184],{"class":48,"line":108},[46,8185,8186],{"class":52},"# 1. 构建前端\n",[46,8188,8189,8192,8194],{"class":48,"line":114},[46,8190,8191],{"class":59},"pnpm",[46,8193,233],{"class":62},[46,8195,8196],{"class":122}," --no-frozen-lockfile\n",[46,8198,8199,8201,8204],{"class":48,"line":337},[46,8200,3264],{"class":59},[46,8202,8203],{"class":62}," vite",[46,8205,8206],{"class":62}," build\n",[46,8208,8209],{"class":48,"line":369},[46,8210,105],{"emptyLinePlaceholder":104},[46,8212,8213],{"class":48,"line":374},[46,8214,8215],{"class":52},"# 2. 复制到嵌入目录\n",[46,8217,8218,8221,8224],{"class":48,"line":380},[46,8219,8220],{"class":59},"rm",[46,8222,8223],{"class":122}," -rf",[46,8225,8226],{"class":62}," service\u002Fweb\u002Fdist\n",[46,8228,8229,8232,8235,8238],{"class":48,"line":484},[46,8230,8231],{"class":59},"cp",[46,8233,8234],{"class":122}," -r",[46,8236,8237],{"class":62}," dist",[46,8239,8226],{"class":62},[46,8241,8242],{"class":48,"line":498},[46,8243,105],{"emptyLinePlaceholder":104},[46,8245,8246],{"class":48,"line":503},[46,8247,8248],{"class":52},"# 3. 编译 Go 二进制\n",[46,8250,8251,8253],{"class":48,"line":509},[46,8252,911],{"class":122},[46,8254,8255],{"class":62}," service\n",[46,8257,8258,8260,8262,8264,8267],{"class":48,"line":1711},[46,8259,230],{"class":59},[46,8261,1656],{"class":62},[46,8263,1665],{"class":122},[46,8265,8266],{"class":62}," myapp.exe",[46,8268,5848],{"class":122},[46,8270,8271,8274,8277],{"class":48,"line":1716},[46,8272,8273],{"class":122},"    -ldflags=",[46,8275,8276],{"class":62},"\"-X myproject\u002Fglobal.RUNCODE=release\"",[46,8278,5848],{"class":122},[46,8280,8281],{"class":48,"line":1725},[46,8282,8283],{"class":62},"    main.go\n",[11,8285,8286],{"id":8286},"踩坑记录",[262,8288,8290],{"id":8289},"坑一路由不匹配静态资源-404","坑一：路由不匹配，静态资源 404",[16,8292,8293,8294,8296,8297,8299],{},"首次实现时用了 Gin 的 ",[43,8295,8143],{}," 兜底，但部分静态资源返回 404。排查发现 ",[43,8298,8143],{}," 在某些路由未显式注册时不会按预期触发。",[16,8301,8302,8305,8306,8309,8310,8313,8314,8316],{},[27,8303,8304],{},"解决","：显式注册 ",[43,8307,8308],{},"\u002Fassets\u002F*filepath","、",[43,8311,8312],{},"\u002Ffavicon.ico"," 等路由，",[43,8315,8143],{}," 仅作为兜底。",[262,8318,8320,8321,8323,8324,8327],{"id":8319},"坑二goembed-排除-_-开头文件","坑二：",[43,8322,7689],{}," 排除 ",[43,8325,8326],{},"_"," 开头文件",[16,8329,8330,8331,8334,8335,8337,8338,3388],{},"这是最坑的。Vite 构建产出了一个 ",[43,8332,8333],{},"_plugin-vue_export-helper-xxx.js","，Go 的 ",[43,8336,7689],{}," 当模式为目录时，",[27,8339,8340,8341,30,8343,8345],{},"硬编码排除了 ",[43,8342,8326],{},[43,8344,3615],{}," 开头的文件",[36,8347,8349],{"className":4922,"code":8348,"language":230,"meta":41,"style":41},"\u002F\u002F 这行会跳过 dist\u002Fassets\u002F_plugin-xxx.js\n\u002F\u002Fgo:embed dist\n",[43,8350,8351,8356],{"__ignoreMap":41},[46,8352,8353],{"class":48,"line":49},[46,8354,8355],{"class":52},"\u002F\u002F 这行会跳过 dist\u002Fassets\u002F_plugin-xxx.js\n",[46,8357,8358],{"class":48,"line":56},[46,8359,7810],{"class":52},[16,8361,8362,8363,3388],{},"错误表现：JS 文件返回的是 HTML 内容（SPA fallback），浏览器报 ",[43,8364,8365],{},"Unexpected token '\u003C'",[16,8367,8368,8370,8371,8373],{},[27,8369,8304],{},"：改 Vite 配置，去除了 ",[43,8372,8326],{}," 前缀：",[36,8375,8379],{"className":8376,"code":8377,"language":8378,"meta":41,"style":41},"language-ts shiki shiki-themes github-light github-dark","\u002F\u002F vite.config.ts\nbuild: {\n  rollupOptions: {\n    output: {\n      chunkFileNames(chunkInfo) {\n        return `assets\u002F${chunkInfo.name.replace(\u002F^_\u002F, '')}-[hash].js`\n      },\n    },\n  },\n},\n","ts",[43,8380,8381,8386,8393,8400,8407,8415,8453,8458,8463,8468],{"__ignoreMap":41},[46,8382,8383],{"class":48,"line":49},[46,8384,8385],{"class":52},"\u002F\u002F vite.config.ts\n",[46,8387,8388,8391],{"class":48,"line":56},[46,8389,8390],{"class":59},"build",[46,8392,624],{"class":319},[46,8394,8395,8398],{"class":48,"line":72},[46,8396,8397],{"class":59},"  rollupOptions",[46,8399,624],{"class":319},[46,8401,8402,8405],{"class":48,"line":85},[46,8403,8404],{"class":59},"    output",[46,8406,624],{"class":319},[46,8408,8409,8412],{"class":48,"line":101},[46,8410,8411],{"class":59},"      chunkFileNames",[46,8413,8414],{"class":319},"(chunkInfo) {\n",[46,8416,8417,8419,8422,8425,8427,8430,8432,8434,8436,8438,8440,8442,8444,8446,8448,8450],{"class":48,"line":108},[46,8418,5375],{"class":312},[46,8420,8421],{"class":62}," `assets\u002F${",[46,8423,8424],{"class":319},"chunkInfo",[46,8426,3615],{"class":62},[46,8428,8429],{"class":319},"name",[46,8431,3615],{"class":62},[46,8433,3993],{"class":59},[46,8435,3494],{"class":62},[46,8437,3612],{"class":62},[46,8439,3599],{"class":312},[46,8441,8326],{"class":3602},[46,8443,3612],{"class":62},[46,8445,659],{"class":62},[46,8447,4010],{"class":62},[46,8449,4313],{"class":62},[46,8451,8452],{"class":62},"}-[hash].js`\n",[46,8454,8455],{"class":48,"line":114},[46,8456,8457],{"class":319},"      },\n",[46,8459,8460],{"class":48,"line":337},[46,8461,8462],{"class":319},"    },\n",[46,8464,8465],{"class":48,"line":369},[46,8466,8467],{"class":319},"  },\n",[46,8469,8470],{"class":48,"line":374},[46,8471,8472],{"class":319},"},\n",[16,8474,8475,8476,8478],{},"这样就避免产生 ",[43,8477,8326],{}," 前缀文件名的同时，保持构建产物的一致性和引用关系的正确性。",[262,8480,8482],{"id":8481},"坑三构建缓存导致-embed-内容未更新","坑三：构建缓存导致 embed 内容未更新",[16,8484,8485,8486,8489],{},"多次 ",[43,8487,8488],{},"go build"," 后，Go 的构建缓存可能使用了旧版本的 embed 内容，导致实际运行的文件不是最新的。",[16,8491,8492,8494,8495,8498],{},[27,8493,8304],{},"：使用 ",[43,8496,8497],{},"go build -a"," 强制重新编译所有包。",[11,8500,8501],{"id":8501},"最终效果",[36,8503,8505],{"className":38,"code":8504,"language":40,"meta":41,"style":41},"$ bash build-standalone.sh\n=== 构建成功 ===\n输出: service\u002Fmyapp.exe  41M\n",[43,8506,8507,8517,8527],{"__ignoreMap":41},[46,8508,8509,8511,8514],{"class":48,"line":49},[46,8510,3609],{"class":59},[46,8512,8513],{"class":62}," bash",[46,8515,8516],{"class":62}," build-standalone.sh\n",[46,8518,8519,8521,8524],{"class":48,"line":56},[46,8520,3587],{"class":62},[46,8522,8523],{"class":62}," 构建成功",[46,8525,8526],{"class":62}," ===\n",[46,8528,8529,8532,8535],{"class":48,"line":72},[46,8530,8531],{"class":59},"输出:",[46,8533,8534],{"class":62}," service\u002Fmyapp.exe",[46,8536,8537],{"class":62},"  41M\n",[16,8539,8540],{},"一个 41MB 的 exe，内置前端页面、API 后端、SQLite 数据库（运行时初始化），拖到哪都能跑。",[16,8542,8543],{},[27,8544,8545],{},"部署对比：",[131,8547,8548,8561],{},[134,8549,8550],{},[137,8551,8552,8555,8558],{},[140,8553,8554],{},"方式",[140,8556,8557],{},"文件数",[140,8559,8560],{},"操作",[147,8562,8563,8574],{},[137,8564,8565,8568,8571],{},[152,8566,8567],{},"旧方案",[152,8569,8570],{},"1 exe + 1 web 目录",[152,8572,8573],{},"解压→保持目录结构",[137,8575,8576,8579,8582],{},[152,8577,8578],{},"新方案",[152,8580,8581],{},"1 个 exe",[152,8583,8584],{},"直接扔到服务器运行",[11,8586,1430],{"id":1430},[16,8588,8589,8590,8592],{},"Go 的 ",[43,8591,7689],{}," 是一个强大的工具，但有两个隐藏行为需要注意：",[1747,8594,8595,8602],{},[721,8596,8597,8598,30,8600,8345],{},"嵌入目录时自动排除 ",[43,8599,8326],{},[43,8601,3615],{},[721,8603,8604],{},"构建缓存可能导致 embed 不是最新的",[16,8606,8607],{},"对于前端+后端合体的场景，这是性价比最高的方案——不需要额外的打包工具，纯标准库搞定。",[1460,8609,8610],{},"html pre.shiki code .sHbNN, html code.shiki .sHbNN{--shiki-light:#6A737D;--shiki-dark:#6A737D}html pre.shiki code .suQ91, html code.shiki .suQ91{--shiki-light:#005CC5;--shiki-dark:#79B8FF}html pre.shiki code .sw2iP, html code.shiki .sw2iP{--shiki-light:#6F42C1;--shiki-dark:#B392F0}html pre.shiki code .scXbn, html code.shiki .scXbn{--shiki-light:#032F62;--shiki-dark:#9ECBFF}html .light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html.light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-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 .siTax, html code.shiki .siTax{--shiki-light:#D73A49;--shiki-dark:#F97583}html pre.shiki code .sIX_F, html code.shiki .sIX_F{--shiki-light:#24292E;--shiki-dark:#E1E4E8}html pre.shiki code .sPK6S, html code.shiki .sPK6S{--shiki-light:#E36209;--shiki-dark:#FFAB70}html pre.shiki code .sPKmS, html code.shiki .sPKmS{--shiki-light:#032F62;--shiki-dark:#DBEDFF}",{"title":41,"searchDepth":56,"depth":72,"links":8612},[8613,8614,8615,8620,8626,8627],{"id":3364,"depth":56,"text":3364},{"id":7683,"depth":56,"text":7683},{"id":7697,"depth":56,"text":7697,"children":8616},[8617,8618,8619],{"id":7700,"depth":72,"text":7701},{"id":8037,"depth":72,"text":8038},{"id":8149,"depth":72,"text":8150},{"id":8286,"depth":56,"text":8286,"children":8621},[8622,8623,8625],{"id":8289,"depth":72,"text":8290},{"id":8319,"depth":72,"text":8624},"坑二：\u002F\u002Fgo:embed 排除 _ 开头文件",{"id":8481,"depth":72,"text":8482},{"id":8501,"depth":56,"text":8501},{"id":1430,"depth":56,"text":1430},"记录将 Vue 3 前端嵌入 Go 二进制实现单 exe 发布的完整流程，以及遇到的坑和解决方案。","\u002Fposts\u002Fgo-embed-frontend\u002Fimg\u002Fcover.jpg",{},"\u002Fposts\u002Fgo-embed-frontend","2026-06-06T16:00:00",{"title":7666,"description":8628},"posts\u002Fgo-embed-frontend\u002Findex",[1498,8636,8637,8638],"Vue","编译","埋坑","2026-06-06","SIQNL24wVAFb18wIGWSB3lNVZUQ9B76jWgQ55TASTYk",{"id":4,"title":5,"author":6,"body":8642,"description":1484,"difficulty":1485,"draft":1486,"extension":1487,"image":1488,"meta":9790,"navigation":104,"path":1490,"pinned":1486,"published":1491,"seo":9791,"series":6,"seriesOrder":6,"stem":1493,"tags":9792,"updated":1491,"__hash__":1501},{"type":8,"value":8643,"toc":9769},[8644,8646,8655,8711,8713,8715,8771,8773,8801,8805,8807,8809,8905,8907,9021,9023,9085,9087,9143,9145,9147,9149,9151,9169,9171,9181,9183,9185,9189,9313,9319,9345,9347,9349,9433,9435,9437,9539,9541,9543,9595,9597,9599,9601,9659,9661,9666,9668,9694,9698,9700,9746,9748,9750,9762,9767],[11,8645,14],{"id":13},[16,8647,8648,25,8651,30,8653,34],{},[19,8649,24],{"href":21,"rel":8650},[23],[27,8652,29],{},[27,8654,33],{},[36,8656,8657],{"className":38,"code":39,"language":40,"meta":41,"style":41},[43,8658,8659,8663,8673,8683,8695,8699,8703],{"__ignoreMap":41},[46,8660,8661],{"class":48,"line":49},[46,8662,53],{"class":52},[46,8664,8665,8667,8669,8671],{"class":48,"line":56},[46,8666,24],{"class":59},[46,8668,63],{"class":62},[46,8670,66],{"class":62},[46,8672,69],{"class":62},[46,8674,8675,8677,8679,8681],{"class":48,"line":72},[46,8676,24],{"class":59},[46,8678,63],{"class":62},[46,8680,79],{"class":62},[46,8682,82],{"class":62},[46,8684,8685,8687,8689,8691,8693],{"class":48,"line":85},[46,8686,24],{"class":59},[46,8688,63],{"class":62},[46,8690,92],{"class":62},[46,8692,95],{"class":62},[46,8694,98],{"class":62},[46,8696,8697],{"class":48,"line":101},[46,8698,105],{"emptyLinePlaceholder":104},[46,8700,8701],{"class":48,"line":108},[46,8702,111],{"class":52},[46,8704,8705,8707,8709],{"class":48,"line":114},[46,8706,24],{"class":59},[46,8708,119],{"class":62},[46,8710,123],{"class":122},[11,8712,126],{"id":126},[16,8714,129],{},[131,8716,8717,8725],{},[134,8718,8719],{},[137,8720,8721,8723],{},[140,8722,142],{},[140,8724,145],{},[147,8726,8727,8733,8739,8747,8755,8763],{},[137,8728,8729,8731],{},[152,8730,154],{},[152,8732,157],{},[137,8734,8735,8737],{},[152,8736,162],{},[152,8738,165],{},[137,8740,8741,8743],{},[152,8742,170],{},[152,8744,173,8745,177],{},[43,8746,176],{},[137,8748,8749,8751],{},[152,8750,182],{},[152,8752,185,8753,189],{},[43,8754,188],{},[137,8756,8757,8759],{},[152,8758,194],{},[152,8760,197,8761],{},[43,8762,200],{},[137,8764,8765,8767],{},[152,8766,205],{},[152,8768,208,8769,212],{},[43,8770,211],{},[11,8772,215],{"id":215},[36,8774,8775],{"className":38,"code":218,"language":40,"meta":41,"style":41},[43,8776,8777,8781,8789,8793,8797],{"__ignoreMap":41},[46,8778,8779],{"class":48,"line":49},[46,8780,225],{"class":52},[46,8782,8783,8785,8787],{"class":48,"line":56},[46,8784,230],{"class":59},[46,8786,233],{"class":62},[46,8788,236],{"class":62},[46,8790,8791],{"class":48,"line":72},[46,8792,105],{"emptyLinePlaceholder":104},[46,8794,8795],{"class":48,"line":85},[46,8796,245],{"class":52},[46,8798,8799],{"class":48,"line":101},[46,8800,250],{"class":52},[16,8802,253,8803,257],{},[43,8804,256],{},[11,8806,260],{"id":260},[262,8808,265],{"id":264},[36,8810,8811],{"className":38,"code":268,"language":40,"meta":41,"style":41},[43,8812,8813,8817,8829,8833,8837,8855,8859,8863,8889,8893,8897],{"__ignoreMap":41},[46,8814,8815],{"class":48,"line":49},[46,8816,275],{"class":52},[46,8818,8819,8821,8823,8825,8827],{"class":48,"line":56},[46,8820,24],{"class":59},[46,8822,282],{"class":62},[46,8824,285],{"class":62},[46,8826,288],{"class":62},[46,8828,291],{"class":122},[46,8830,8831],{"class":48,"line":72},[46,8832,105],{"emptyLinePlaceholder":104},[46,8834,8835],{"class":48,"line":85},[46,8836,300],{"class":52},[46,8838,8839,8841,8843,8845,8847,8849,8851,8853],{"class":48,"line":101},[46,8840,24],{"class":59},[46,8842,282],{"class":62},[46,8844,309],{"class":62},[46,8846,313],{"class":312},[46,8848,316],{"class":62},[46,8850,320],{"class":319},[46,8852,323],{"class":312},[46,8854,291],{"class":122},[46,8856,8857],{"class":48,"line":108},[46,8858,105],{"emptyLinePlaceholder":104},[46,8860,8861],{"class":48,"line":114},[46,8862,334],{"class":52},[46,8864,8865,8867,8869,8871,8873,8875,8877,8879,8881,8883,8885,8887],{"class":48,"line":337},[46,8866,24],{"class":59},[46,8868,282],{"class":62},[46,8870,344],{"class":62},[46,8872,313],{"class":312},[46,8874,349],{"class":62},[46,8876,352],{"class":319},[46,8878,323],{"class":312},[46,8880,313],{"class":312},[46,8882,359],{"class":62},[46,8884,362],{"class":319},[46,8886,323],{"class":312},[46,8888,291],{"class":122},[46,8890,8891],{"class":48,"line":369},[46,8892,105],{"emptyLinePlaceholder":104},[46,8894,8895],{"class":48,"line":374},[46,8896,377],{"class":52},[46,8898,8899,8901,8903],{"class":48,"line":380},[46,8900,24],{"class":59},[46,8902,385],{"class":62},[46,8904,388],{"class":62},[262,8906,392],{"id":391},[36,8908,8909],{"className":38,"code":395,"language":40,"meta":41,"style":41},[43,8910,8911,8915,8925,8929,8933,8943,8947,8951,8963,8977,8981,8985,8997,9001,9005],{"__ignoreMap":41},[46,8912,8913],{"class":48,"line":49},[46,8914,402],{"class":52},[46,8916,8917,8919,8921,8923],{"class":48,"line":56},[46,8918,24],{"class":59},[46,8920,63],{"class":62},[46,8922,66],{"class":62},[46,8924,413],{"class":62},[46,8926,8927],{"class":48,"line":72},[46,8928,105],{"emptyLinePlaceholder":104},[46,8930,8931],{"class":48,"line":85},[46,8932,422],{"class":52},[46,8934,8935,8937,8939,8941],{"class":48,"line":101},[46,8936,24],{"class":59},[46,8938,63],{"class":62},[46,8940,79],{"class":62},[46,8942,82],{"class":62},[46,8944,8945],{"class":48,"line":108},[46,8946,105],{"emptyLinePlaceholder":104},[46,8948,8949],{"class":48,"line":114},[46,8950,441],{"class":52},[46,8952,8953,8955,8957,8959,8961],{"class":48,"line":337},[46,8954,24],{"class":59},[46,8956,63],{"class":62},[46,8958,450],{"class":62},[46,8960,95],{"class":62},[46,8962,455],{"class":52},[46,8964,8965,8967,8969,8971,8973,8975],{"class":48,"line":369},[46,8966,24],{"class":59},[46,8968,63],{"class":62},[46,8970,464],{"class":62},[46,8972,95],{"class":62},[46,8974,469],{"class":122},[46,8976,472],{"class":52},[46,8978,8979],{"class":48,"line":374},[46,8980,105],{"emptyLinePlaceholder":104},[46,8982,8983],{"class":48,"line":380},[46,8984,481],{"class":52},[46,8986,8987,8989,8991,8993,8995],{"class":48,"line":484},[46,8988,24],{"class":59},[46,8990,63],{"class":62},[46,8992,92],{"class":62},[46,8994,95],{"class":62},[46,8996,495],{"class":62},[46,8998,8999],{"class":48,"line":498},[46,9000,105],{"emptyLinePlaceholder":104},[46,9002,9003],{"class":48,"line":503},[46,9004,506],{"class":52},[46,9006,9007,9009,9011,9013,9015,9017,9019],{"class":48,"line":509},[46,9008,24],{"class":59},[46,9010,63],{"class":62},[46,9012,516],{"class":62},[46,9014,519],{"class":62},[46,9016,522],{"class":62},[46,9018,525],{"class":122},[46,9020,528],{"class":62},[262,9022,532],{"id":531},[36,9024,9025],{"className":38,"code":535,"language":40,"meta":41,"style":41},[43,9026,9027,9031,9047,9069],{"__ignoreMap":41},[46,9028,9029],{"class":48,"line":49},[46,9030,542],{"class":52},[46,9032,9033,9035,9037,9039,9041,9043,9045],{"class":48,"line":56},[46,9034,24],{"class":59},[46,9036,282],{"class":62},[46,9038,285],{"class":62},[46,9040,288],{"class":62},[46,9042,555],{"class":122},[46,9044,558],{"class":62},[46,9046,291],{"class":122},[46,9048,9049,9051,9053,9055,9057,9059,9061,9063,9065,9067],{"class":48,"line":72},[46,9050,24],{"class":59},[46,9052,282],{"class":62},[46,9054,309],{"class":62},[46,9056,313],{"class":312},[46,9058,573],{"class":62},[46,9060,320],{"class":319},[46,9062,323],{"class":312},[46,9064,555],{"class":122},[46,9066,558],{"class":62},[46,9068,291],{"class":122},[46,9070,9071,9073,9075,9077,9079,9081,9083],{"class":48,"line":85},[46,9072,24],{"class":59},[46,9074,282],{"class":62},[46,9076,592],{"class":62},[46,9078,595],{"class":62},[46,9080,555],{"class":122},[46,9082,558],{"class":62},[46,9084,291],{"class":122},[16,9086,604],{},[36,9088,9089],{"className":607,"code":608,"language":609,"meta":41,"style":41},[43,9090,9091,9095,9101,9107,9117,9131,9135,9139],{"__ignoreMap":41},[46,9092,9093],{"class":48,"line":49},[46,9094,616],{"class":319},[46,9096,9097,9099],{"class":48,"line":56},[46,9098,621],{"class":122},[46,9100,624],{"class":319},[46,9102,9103,9105],{"class":48,"line":72},[46,9104,629],{"class":122},[46,9106,624],{"class":319},[46,9108,9109,9111,9113,9115],{"class":48,"line":85},[46,9110,636],{"class":122},[46,9112,639],{"class":319},[46,9114,642],{"class":62},[46,9116,645],{"class":319},[46,9118,9119,9121,9123,9125,9127,9129],{"class":48,"line":101},[46,9120,650],{"class":122},[46,9122,653],{"class":319},[46,9124,656],{"class":62},[46,9126,659],{"class":319},[46,9128,662],{"class":62},[46,9130,665],{"class":319},[46,9132,9133],{"class":48,"line":108},[46,9134,670],{"class":319},[46,9136,9137],{"class":48,"line":114},[46,9138,675],{"class":319},[46,9140,9141],{"class":48,"line":337},[46,9142,680],{"class":319},[16,9144,683],{},[11,9146,686],{"id":686},[262,9148,690],{"id":689},[16,9150,693],{},[36,9152,9153],{"className":38,"code":696,"language":40,"meta":41,"style":41},[43,9154,9155],{"__ignoreMap":41},[46,9156,9157,9159,9161,9163,9165,9167],{"class":48,"line":49},[46,9158,24],{"class":59},[46,9160,282],{"class":62},[46,9162,592],{"class":62},[46,9164,595],{"class":62},[46,9166,555],{"class":122},[46,9168,713],{"class":62},[16,9170,716],{},[718,9172,9173,9175,9177],{},[721,9174,723],{},[721,9176,726],{},[721,9178,729,9179,733],{},[43,9180,732],{},[16,9182,736],{},[262,9184,740],{"id":739},[16,9186,743,9187,747],{},[43,9188,746],{},[36,9190,9191],{"className":38,"code":750,"language":40,"meta":41,"style":41},[43,9192,9193,9197,9213,9235,9239,9243,9259,9281,9285,9289,9301],{"__ignoreMap":41},[46,9194,9195],{"class":48,"line":49},[46,9196,757],{"class":52},[46,9198,9199,9201,9203,9205,9207,9209,9211],{"class":48,"line":56},[46,9200,24],{"class":59},[46,9202,282],{"class":62},[46,9204,285],{"class":62},[46,9206,768],{"class":62},[46,9208,555],{"class":122},[46,9210,773],{"class":62},[46,9212,291],{"class":122},[46,9214,9215,9217,9219,9221,9223,9225,9227,9229,9231,9233],{"class":48,"line":72},[46,9216,24],{"class":59},[46,9218,282],{"class":62},[46,9220,309],{"class":62},[46,9222,313],{"class":312},[46,9224,788],{"class":62},[46,9226,320],{"class":319},[46,9228,323],{"class":312},[46,9230,555],{"class":122},[46,9232,773],{"class":62},[46,9234,291],{"class":122},[46,9236,9237],{"class":48,"line":85},[46,9238,105],{"emptyLinePlaceholder":104},[46,9240,9241],{"class":48,"line":101},[46,9242,807],{"class":52},[46,9244,9245,9247,9249,9251,9253,9255,9257],{"class":48,"line":108},[46,9246,24],{"class":59},[46,9248,282],{"class":62},[46,9250,285],{"class":62},[46,9252,818],{"class":62},[46,9254,555],{"class":122},[46,9256,823],{"class":62},[46,9258,291],{"class":122},[46,9260,9261,9263,9265,9267,9269,9271,9273,9275,9277,9279],{"class":48,"line":114},[46,9262,24],{"class":59},[46,9264,282],{"class":62},[46,9266,309],{"class":62},[46,9268,313],{"class":312},[46,9270,838],{"class":62},[46,9272,320],{"class":319},[46,9274,323],{"class":312},[46,9276,555],{"class":122},[46,9278,823],{"class":62},[46,9280,291],{"class":122},[46,9282,9283],{"class":48,"line":337},[46,9284,105],{"emptyLinePlaceholder":104},[46,9286,9287],{"class":48,"line":369},[46,9288,857],{"class":52},[46,9290,9291,9293,9295,9297,9299],{"class":48,"line":374},[46,9292,24],{"class":59},[46,9294,864],{"class":122},[46,9296,63],{"class":62},[46,9298,66],{"class":62},[46,9300,871],{"class":62},[46,9302,9303,9305,9307,9309,9311],{"class":48,"line":380},[46,9304,24],{"class":59},[46,9306,878],{"class":122},[46,9308,63],{"class":62},[46,9310,66],{"class":62},[46,9312,885],{"class":62},[16,9314,888,9315,892,9317,896],{},[27,9316,891],{},[43,9318,895],{},[36,9320,9321],{"className":38,"code":899,"language":40,"meta":41,"style":41},[43,9322,9323,9327,9333],{"__ignoreMap":41},[46,9324,9325],{"class":48,"line":49},[46,9326,906],{"class":52},[46,9328,9329,9331],{"class":48,"line":56},[46,9330,911],{"class":122},[46,9332,914],{"class":62},[46,9334,9335,9337,9339,9341,9343],{"class":48,"line":72},[46,9336,24],{"class":59},[46,9338,282],{"class":62},[46,9340,285],{"class":62},[46,9342,925],{"class":62},[46,9344,928],{"class":122},[262,9346,932],{"id":931},[16,9348,935],{},[36,9350,9351],{"className":38,"code":938,"language":40,"meta":41,"style":41},[43,9352,9353,9357,9371,9375,9379,9393,9397,9401,9415,9419,9423],{"__ignoreMap":41},[46,9354,9355],{"class":48,"line":49},[46,9356,945],{"class":52},[46,9358,9359,9361,9363,9365,9367,9369],{"class":48,"line":56},[46,9360,24],{"class":59},[46,9362,63],{"class":62},[46,9364,79],{"class":62},[46,9366,95],{"class":62},[46,9368,958],{"class":122},[46,9370,961],{"class":62},[46,9372,9373],{"class":48,"line":72},[46,9374,105],{"emptyLinePlaceholder":104},[46,9376,9377],{"class":48,"line":85},[46,9378,970],{"class":52},[46,9380,9381,9383,9385,9387,9389,9391],{"class":48,"line":101},[46,9382,24],{"class":59},[46,9384,63],{"class":62},[46,9386,66],{"class":62},[46,9388,981],{"class":62},[46,9390,984],{"class":122},[46,9392,987],{"class":62},[46,9394,9395],{"class":48,"line":108},[46,9396,105],{"emptyLinePlaceholder":104},[46,9398,9399],{"class":48,"line":114},[46,9400,996],{"class":52},[46,9402,9403,9405,9407,9409,9411,9413],{"class":48,"line":337},[46,9404,24],{"class":59},[46,9406,984],{"class":122},[46,9408,1005],{"class":62},[46,9410,63],{"class":62},[46,9412,79],{"class":62},[46,9414,82],{"class":62},[46,9416,9417],{"class":48,"line":369},[46,9418,105],{"emptyLinePlaceholder":104},[46,9420,9421],{"class":48,"line":374},[46,9422,1020],{"class":52},[46,9424,9425,9427,9429,9431],{"class":48,"line":380},[46,9426,24],{"class":59},[46,9428,282],{"class":62},[46,9430,1029],{"class":62},[46,9432,1032],{"class":62},[262,9434,1036],{"id":1035},[16,9436,1039],{},[36,9438,9439],{"className":38,"code":1042,"language":40,"meta":41,"style":41},[43,9440,9441,9445,9453,9457,9461,9473,9477,9481,9491,9495,9499,9513,9517,9521],{"__ignoreMap":41},[46,9442,9443],{"class":48,"line":49},[46,9444,1049],{"class":52},[46,9446,9447,9449,9451],{"class":48,"line":56},[46,9448,24],{"class":59},[46,9450,1056],{"class":62},[46,9452,1059],{"class":62},[46,9454,9455],{"class":48,"line":72},[46,9456,105],{"emptyLinePlaceholder":104},[46,9458,9459],{"class":48,"line":85},[46,9460,1068],{"class":52},[46,9462,9463,9465,9467,9469,9471],{"class":48,"line":101},[46,9464,24],{"class":59},[46,9466,1056],{"class":62},[46,9468,1077],{"class":62},[46,9470,1080],{"class":122},[46,9472,1083],{"class":62},[46,9474,9475],{"class":48,"line":108},[46,9476,105],{"emptyLinePlaceholder":104},[46,9478,9479],{"class":48,"line":114},[46,9480,1092],{"class":52},[46,9482,9483,9485,9487,9489],{"class":48,"line":337},[46,9484,24],{"class":59},[46,9486,1056],{"class":62},[46,9488,1077],{"class":62},[46,9490,1103],{"class":122},[46,9492,9493],{"class":48,"line":369},[46,9494,105],{"emptyLinePlaceholder":104},[46,9496,9497],{"class":48,"line":374},[46,9498,1112],{"class":52},[46,9500,9501,9503,9505,9507,9509,9511],{"class":48,"line":380},[46,9502,24],{"class":59},[46,9504,63],{"class":62},[46,9506,1121],{"class":62},[46,9508,519],{"class":62},[46,9510,1126],{"class":122},[46,9512,1129],{"class":62},[46,9514,9515],{"class":48,"line":484},[46,9516,105],{"emptyLinePlaceholder":104},[46,9518,9519],{"class":48,"line":498},[46,9520,1138],{"class":52},[46,9522,9523,9525,9527,9529,9531,9533,9535,9537],{"class":48,"line":503},[46,9524,24],{"class":59},[46,9526,63],{"class":62},[46,9528,1147],{"class":62},[46,9530,519],{"class":62},[46,9532,522],{"class":62},[46,9534,1154],{"class":62},[46,9536,1080],{"class":122},[46,9538,1159],{"class":62},[262,9540,1163],{"id":1162},[16,9542,1166],{},[131,9544,9545,9553],{},[134,9546,9547],{},[137,9548,9549,9551],{},[140,9550,1175],{},[140,9552,1178],{},[147,9554,9555,9563,9571,9579,9587],{},[137,9556,9557,9561],{},[152,9558,9559],{},[43,9560,1187],{},[152,9562,1190],{},[137,9564,9565,9569],{},[152,9566,9567],{},[43,9568,1197],{},[152,9570,1200],{},[137,9572,9573,9577],{},[152,9574,9575],{},[43,9576,1207],{},[152,9578,1210],{},[137,9580,9581,9585],{},[152,9582,9583],{},[43,9584,1217],{},[152,9586,1220],{},[137,9588,9589,9593],{},[152,9590,9591],{},[43,9592,1227],{},[152,9594,1230],{},[16,9596,1233],{},[262,9598,1237],{"id":1236},[16,9600,1240],{},[36,9602,9603],{"className":38,"code":1243,"language":40,"meta":41,"style":41},[43,9604,9605,9609,9619,9623,9627,9637,9641,9645],{"__ignoreMap":41},[46,9606,9607],{"class":48,"line":49},[46,9608,1250],{"class":52},[46,9610,9611,9613,9615,9617],{"class":48,"line":56},[46,9612,24],{"class":59},[46,9614,282],{"class":62},[46,9616,1259],{"class":62},[46,9618,1262],{"class":62},[46,9620,9621],{"class":48,"line":72},[46,9622,105],{"emptyLinePlaceholder":104},[46,9624,9625],{"class":48,"line":85},[46,9626,1271],{"class":52},[46,9628,9629,9631,9633,9635],{"class":48,"line":101},[46,9630,24],{"class":59},[46,9632,282],{"class":62},[46,9634,1259],{"class":62},[46,9636,1282],{"class":62},[46,9638,9639],{"class":48,"line":108},[46,9640,105],{"emptyLinePlaceholder":104},[46,9642,9643],{"class":48,"line":114},[46,9644,1291],{"class":52},[46,9646,9647,9649,9651,9653,9655,9657],{"class":48,"line":337},[46,9648,24],{"class":59},[46,9650,1298],{"class":122},[46,9652,1301],{"class":62},[46,9654,63],{"class":62},[46,9656,66],{"class":62},[46,9658,1308],{"class":122},[11,9660,1311],{"id":1311},[36,9662,9664],{"className":9663,"code":1316,"language":1317},[1315],[43,9665,1316],{"__ignoreMap":41},[16,9667,1322],{},[718,9669,9670,9676,9682,9688],{},[721,9671,9672,1332],{},[27,9673,9674],{},[43,9675,1331],{},[721,9677,9678,1340],{},[27,9679,9680],{},[43,9681,1339],{},[721,9683,9684,1348],{},[27,9685,9686],{},[43,9687,1347],{},[721,9689,9690,1356],{},[27,9691,9692],{},[43,9693,1355],{},[16,9695,1359,9696,1363],{},[43,9697,1362],{},[11,9699,1366],{"id":1366},[131,9701,9702,9712],{},[134,9703,9704],{},[137,9705,9706,9708,9710],{},[140,9707,1375],{},[140,9709,1378],{},[140,9711,1381],{},[147,9713,9714,9722,9730,9738],{},[137,9715,9716,9718,9720],{},[152,9717,1388],{},[152,9719,1391],{},[152,9721,1394],{},[137,9723,9724,9726,9728],{},[152,9725,1399],{},[152,9727,1402],{},[152,9729,1405],{},[137,9731,9732,9734,9736],{},[152,9733,1410],{},[152,9735,1413],{},[152,9737,1416],{},[137,9739,9740,9742,9744],{},[152,9741,1421],{},[152,9743,1424],{},[152,9745,1427],{},[11,9747,1430],{"id":1430},[16,9749,1433],{},[718,9751,9752,9754,9756,9758,9760],{},[721,9753,1438],{},[721,9755,1441],{},[721,9757,1444],{},[721,9759,1447],{},[721,9761,1450],{},[16,9763,1453,9764,1458],{},[19,9765,1457],{"href":21,"rel":9766},[23],[1460,9768,1462],{},{"title":41,"searchDepth":56,"depth":72,"links":9770},[9771,9772,9773,9774,9779,9787,9788,9789],{"id":13,"depth":56,"text":14},{"id":126,"depth":56,"text":126},{"id":215,"depth":56,"text":215},{"id":260,"depth":56,"text":260,"children":9775},[9776,9777,9778],{"id":264,"depth":72,"text":265},{"id":391,"depth":72,"text":392},{"id":531,"depth":72,"text":532},{"id":686,"depth":56,"text":686,"children":9780},[9781,9782,9783,9784,9785,9786],{"id":689,"depth":72,"text":690},{"id":739,"depth":72,"text":740},{"id":931,"depth":72,"text":932},{"id":1035,"depth":72,"text":1036},{"id":1162,"depth":72,"text":1163},{"id":1236,"depth":72,"text":1237},{"id":1311,"depth":56,"text":1311},{"id":1366,"depth":56,"text":1366},{"id":1430,"depth":56,"text":1430},{},{"title":5,"description":1484},[1495,1496,1497,1498,1499,1500],1784303541089]