[{"data":1,"prerenderedAt":10368},["ShallowReactive",2],{"post-go-mcp-gin":3,"series-source-go-mcp-gin":2165},{"id":4,"title":5,"author":6,"body":7,"description":2146,"difficulty":2147,"draft":2148,"extension":2149,"image":2150,"meta":2151,"navigation":141,"path":2152,"pinned":2148,"published":2153,"seo":2154,"series":2155,"seriesOrder":90,"stem":2156,"tags":2157,"updated":2163,"__hash__":2164},"posts\u002Fposts\u002Fgo-mcp-gin\u002Findex.md","Go-MCP 接入 Gin：从零搭建 MCP 服务器",null,{"type":8,"value":9,"toc":2127},"minimark",[10,15,19,22,26,34,41,44,47,69,72,77,112,116,232,245,249,423,426,650,654,665,764,771,775,1063,1066,1147,1151,1154,1157,1168,1172,1179,1550,1744,1747,1803,1806,1960,1963,1966,2039,2042,2114,2123],[11,12,14],"h2",{"id":13},"什么是-mcp","什么是 MCP",[16,17,18],"p",{},"MCP（Model Context Protocol）是 Anthropic 提出的开放协议，用于让 AI 助手（如 Claude）与外部服务进行标准化交互。简单来说：你提供一个 MCP 服务器，AI 就能发现并调用你定义的工具（Tools），就像给 AI 装了一双手。",[16,20,21],{},"传统做法是写 REST API 然后让 AI 通过函数调用（Function Calling）间接使用，但 MCP 统一了协议层，AI 客户端只需要配置一个 URL 就能接入所有工具。",[11,23,25],{"id":24},"为什么选-gin-mcp-go","为什么选 Gin + mcp-go",[16,27,28,29,33],{},"Go 生态里做 MCP 服务器最成熟的库是 ",[30,31,32],"code",{},"mark3labs\u002Fmcp-go","，它支持 StreamableHTTP 传输模式，可以直接挂载到任意 HTTP 框架上。Gin 作为 Go 最流行的 HTTP 框架，天然适配。",[16,35,36,37],{},"核心思路：",[38,39,40],"strong",{},"MCP 服务器本质上就是一个 HTTP Handler，挂到 Gin 路由上即可。",[11,42,43],{"id":43},"需求分析",[16,45,46],{},"我们要搭建一个具备以下能力的 MCP 服务器：",[48,49,50,57,63],"ol",{},[51,52,53,56],"li",{},[38,54,55],{},"工具注册"," — 向 AI 暴露可调用的工具（列出资源、查看详情、创建\u002F删除）",[51,58,59,62],{},[38,60,61],{},"HTTP 集成"," — MCP 端点作为 Gin 路由的一部分，与业务 API 共存",[51,64,65,68],{},[38,66,67],{},"鉴权"," — MCP 请求必须经过身份验证，工具处理器能获取当前用户信息",[11,70,71],{"id":71},"实现步骤",[73,74,76],"h3",{"id":75},"_1-安装依赖","1. 安装依赖",[78,79,84],"pre",{"className":80,"code":81,"language":82,"meta":83,"style":83},"language-bash shiki shiki-themes github-light github-dark","go get github.com\u002Fmark3labs\u002Fmcp-go@v0.54.0\ngo get github.com\u002Fgin-gonic\u002Fgin@v1.12.0\n","bash","",[30,85,86,102],{"__ignoreMap":83},[87,88,91,95,99],"span",{"class":89,"line":90},"line",1,[87,92,94],{"class":93},"sw2iP","go",[87,96,98],{"class":97},"scXbn"," get",[87,100,101],{"class":97}," github.com\u002Fmark3labs\u002Fmcp-go@v0.54.0\n",[87,103,105,107,109],{"class":89,"line":104},2,[87,106,94],{"class":93},[87,108,98],{"class":97},[87,110,111],{"class":97}," github.com\u002Fgin-gonic\u002Fgin@v1.12.0\n",[73,113,115],{"id":114},"_2-创建-mcp-服务器","2. 创建 MCP 服务器",[78,117,120],{"className":118,"code":119,"language":94,"meta":83,"style":83},"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",[30,121,122,137,143,162,175,187,199,209,226],{"__ignoreMap":83},[87,123,124,128,131,134],{"class":89,"line":90},[87,125,127],{"class":126},"siTax","import",[87,129,130],{"class":97}," \"",[87,132,133],{"class":93},"github.com\u002Fmark3labs\u002Fmcp-go\u002Fserver",[87,135,136],{"class":97},"\"\n",[87,138,139],{"class":89,"line":104},[87,140,142],{"emptyLinePlaceholder":141},true,"\n",[87,144,146,150,153,156,159],{"class":89,"line":145},3,[87,147,149],{"class":148},"sIX_F","mcpServer ",[87,151,152],{"class":126},":=",[87,154,155],{"class":148}," server.",[87,157,158],{"class":93},"NewMCPServer",[87,160,161],{"class":148},"(\n",[87,163,165,168,171],{"class":89,"line":164},4,[87,166,167],{"class":97},"    \"my-app\"",[87,169,170],{"class":148},",    ",[87,172,174],{"class":173},"sHbNN","\u002F\u002F 服务器名称\n",[87,176,178,181,184],{"class":89,"line":177},5,[87,179,180],{"class":97},"    \"1.0.0\"",[87,182,183],{"class":148},",     ",[87,185,186],{"class":173},"\u002F\u002F 版本\n",[87,188,190,193,196],{"class":89,"line":189},6,[87,191,192],{"class":148},"    server.",[87,194,195],{"class":93},"WithLogging",[87,197,198],{"class":148},"(),\n",[87,200,202,204,207],{"class":89,"line":201},7,[87,203,192],{"class":148},[87,205,206],{"class":93},"WithRecovery",[87,208,198],{"class":148},[87,210,212,214,217,220,223],{"class":89,"line":211},8,[87,213,192],{"class":148},[87,215,216],{"class":93},"WithInstructions",[87,218,219],{"class":148},"(",[87,221,222],{"class":97},"\"这是一个 MCP 服务器示例\"",[87,224,225],{"class":148},"),\n",[87,227,229],{"class":89,"line":228},9,[87,230,231],{"class":148},")\n",[16,233,234,236,237,240,241,244],{},[30,235,158],{}," 创建一个 MCP 协议实例，它负责处理工具发现（",[30,238,239],{},"tools\u002Flist","）和工具调用（",[30,242,243],{},"tools\u002Fcall","）的协议逻辑。",[73,246,248],{"id":247},"_3-注册工具","3. 注册工具",[78,250,252],{"className":118,"code":251,"language":94,"meta":83,"style":83},"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",[30,253,254,265,269,274,295,310,314,318,323,388,394,417],{"__ignoreMap":83},[87,255,256,258,260,263],{"class":89,"line":90},[87,257,127],{"class":126},[87,259,130],{"class":97},[87,261,262],{"class":93},"github.com\u002Fmark3labs\u002Fmcp-go\u002Fmcp",[87,264,136],{"class":97},[87,266,267],{"class":89,"line":104},[87,268,142],{"emptyLinePlaceholder":141},[87,270,271],{"class":89,"line":145},[87,272,273],{"class":173},"\u002F\u002F 定义工具\n",[87,275,276,279,281,284,287,289,292],{"class":89,"line":164},[87,277,278],{"class":148},"listTool ",[87,280,152],{"class":126},[87,282,283],{"class":148}," mcp.",[87,285,286],{"class":93},"NewTool",[87,288,219],{"class":148},[87,290,291],{"class":97},"\"list_items\"",[87,293,294],{"class":148},",\n",[87,296,297,300,303,305,308],{"class":89,"line":177},[87,298,299],{"class":148},"    mcp.",[87,301,302],{"class":93},"WithDescription",[87,304,219],{"class":148},[87,306,307],{"class":97},"\"列出所有项目\"",[87,309,225],{"class":148},[87,311,312],{"class":89,"line":189},[87,313,231],{"class":148},[87,315,316],{"class":89,"line":201},[87,317,142],{"emptyLinePlaceholder":141},[87,319,320],{"class":89,"line":211},[87,321,322],{"class":173},"\u002F\u002F 注册处理器\n",[87,324,325,328,331,334,337,339,343,346,349,352,355,358,361,363,366,369,372,375,377,380,382,385],{"class":89,"line":228},[87,326,327],{"class":148},"mcpServer.",[87,329,330],{"class":93},"AddTool",[87,332,333],{"class":148},"(listTool, ",[87,335,336],{"class":126},"func",[87,338,219],{"class":148},[87,340,342],{"class":341},"sPK6S","ctx",[87,344,345],{"class":93}," context",[87,347,348],{"class":148},".",[87,350,351],{"class":93},"Context",[87,353,354],{"class":148},", ",[87,356,357],{"class":341},"req",[87,359,360],{"class":93}," mcp",[87,362,348],{"class":148},[87,364,365],{"class":93},"CallToolRequest",[87,367,368],{"class":148},") (",[87,370,371],{"class":126},"*",[87,373,374],{"class":93},"mcp",[87,376,348],{"class":148},[87,378,379],{"class":93},"CallToolResult",[87,381,354],{"class":148},[87,383,384],{"class":126},"error",[87,386,387],{"class":148},") {\n",[87,389,391],{"class":89,"line":390},10,[87,392,393],{"class":173},"    \u002F\u002F 业务逻辑\n",[87,395,397,400,402,405,407,410,413],{"class":89,"line":396},11,[87,398,399],{"class":126},"    return",[87,401,283],{"class":148},[87,403,404],{"class":93},"NewToolResultText",[87,406,219],{"class":148},[87,408,409],{"class":97},"\"返回结果\"",[87,411,412],{"class":148},"), ",[87,414,416],{"class":415},"suQ91","nil\n",[87,418,420],{"class":89,"line":419},12,[87,421,422],{"class":148},"})\n",[16,424,425],{},"带参数的工具：",[78,427,429],{"className":118,"code":428,"language":94,"meta":83,"style":83},"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",[30,430,431,449,462,476,486,500,505,509,513,560,579,596,615,621,627,645],{"__ignoreMap":83},[87,432,433,436,438,440,442,444,447],{"class":89,"line":90},[87,434,435],{"class":148},"getTool ",[87,437,152],{"class":126},[87,439,283],{"class":148},[87,441,286],{"class":93},[87,443,219],{"class":148},[87,445,446],{"class":97},"\"get_item\"",[87,448,294],{"class":148},[87,450,451,453,455,457,460],{"class":89,"line":104},[87,452,299],{"class":148},[87,454,302],{"class":93},[87,456,219],{"class":148},[87,458,459],{"class":97},"\"获取指定项目的详情\"",[87,461,225],{"class":148},[87,463,464,466,469,471,474],{"class":89,"line":145},[87,465,299],{"class":148},[87,467,468],{"class":93},"WithString",[87,470,219],{"class":148},[87,472,473],{"class":97},"\"item_id\"",[87,475,294],{"class":148},[87,477,478,481,484],{"class":89,"line":164},[87,479,480],{"class":148},"        mcp.",[87,482,483],{"class":93},"Required",[87,485,198],{"class":148},[87,487,488,490,493,495,498],{"class":89,"line":177},[87,489,480],{"class":148},[87,491,492],{"class":93},"Description",[87,494,219],{"class":148},[87,496,497],{"class":97},"\"项目 ID\"",[87,499,225],{"class":148},[87,501,502],{"class":89,"line":189},[87,503,504],{"class":148},"    ),\n",[87,506,507],{"class":89,"line":201},[87,508,231],{"class":148},[87,510,511],{"class":89,"line":211},[87,512,142],{"emptyLinePlaceholder":141},[87,514,515,517,519,522,524,526,528,530,532,534,536,538,540,542,544,546,548,550,552,554,556,558],{"class":89,"line":228},[87,516,327],{"class":148},[87,518,330],{"class":93},[87,520,521],{"class":148},"(getTool, ",[87,523,336],{"class":126},[87,525,219],{"class":148},[87,527,342],{"class":341},[87,529,345],{"class":93},[87,531,348],{"class":148},[87,533,351],{"class":93},[87,535,354],{"class":148},[87,537,357],{"class":341},[87,539,360],{"class":93},[87,541,348],{"class":148},[87,543,365],{"class":93},[87,545,368],{"class":148},[87,547,371],{"class":126},[87,549,374],{"class":93},[87,551,348],{"class":148},[87,553,379],{"class":93},[87,555,354],{"class":148},[87,557,384],{"class":126},[87,559,387],{"class":148},[87,561,562,565,567,570,573,575,577],{"class":89,"line":390},[87,563,564],{"class":148},"    itemID, err ",[87,566,152],{"class":126},[87,568,569],{"class":148}," req.",[87,571,572],{"class":93},"RequireString",[87,574,219],{"class":148},[87,576,473],{"class":97},[87,578,231],{"class":148},[87,580,581,584,587,590,593],{"class":89,"line":396},[87,582,583],{"class":126},"    if",[87,585,586],{"class":148}," err ",[87,588,589],{"class":126},"!=",[87,591,592],{"class":415}," nil",[87,594,595],{"class":148}," {\n",[87,597,598,601,603,606,608,611,613],{"class":89,"line":419},[87,599,600],{"class":126},"        return",[87,602,283],{"class":148},[87,604,605],{"class":93},"NewToolResultError",[87,607,219],{"class":148},[87,609,610],{"class":97},"\"缺少 item_id 参数\"",[87,612,412],{"class":148},[87,614,416],{"class":415},[87,616,618],{"class":89,"line":617},13,[87,619,620],{"class":148},"    }\n",[87,622,624],{"class":89,"line":623},14,[87,625,626],{"class":173},"    \u002F\u002F 查询并返回\n",[87,628,630,632,634,636,638,641,643],{"class":89,"line":629},15,[87,631,399],{"class":126},[87,633,283],{"class":148},[87,635,404],{"class":93},[87,637,219],{"class":148},[87,639,640],{"class":97},"\"项目详情...\"",[87,642,412],{"class":148},[87,644,416],{"class":415},[87,646,648],{"class":89,"line":647},16,[87,649,422],{"class":148},[73,651,653],{"id":652},"_4-挂载到-gin-路由","4. 挂载到 Gin 路由",[16,655,656,657,660,661,664],{},"关键一步：用 ",[30,658,659],{},"server.NewStreamableHTTPServer"," 把 MCP 实例转成 HTTP Handler，再用 ",[30,662,663],{},"gin.WrapH"," 适配 Gin。",[78,666,668],{"className":118,"code":667,"language":94,"meta":83,"style":83},"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",[30,669,670,680,684,699,713,730,734,738,743],{"__ignoreMap":83},[87,671,672,674,676,678],{"class":89,"line":90},[87,673,127],{"class":126},[87,675,130],{"class":97},[87,677,133],{"class":93},[87,679,136],{"class":97},[87,681,682],{"class":89,"line":104},[87,683,142],{"emptyLinePlaceholder":141},[87,685,686,689,691,693,696],{"class":89,"line":145},[87,687,688],{"class":148},"mcpHTTPServer ",[87,690,152],{"class":126},[87,692,155],{"class":148},[87,694,695],{"class":93},"NewStreamableHTTPServer",[87,697,698],{"class":148},"(mcpServer,\n",[87,700,701,703,706,708,711],{"class":89,"line":164},[87,702,192],{"class":148},[87,704,705],{"class":93},"WithEndpointPath",[87,707,219],{"class":148},[87,709,710],{"class":97},"\"\u002Fmcp\"",[87,712,225],{"class":148},[87,714,715,717,720,722,725,727],{"class":89,"line":177},[87,716,192],{"class":148},[87,718,719],{"class":93},"WithHeartbeatInterval",[87,721,219],{"class":148},[87,723,724],{"class":415},"30",[87,726,371],{"class":126},[87,728,729],{"class":148},"time.Second),\n",[87,731,732],{"class":89,"line":189},[87,733,231],{"class":148},[87,735,736],{"class":89,"line":201},[87,737,142],{"emptyLinePlaceholder":141},[87,739,740],{"class":89,"line":211},[87,741,742],{"class":173},"\u002F\u002F 挂载到 Gin\n",[87,744,745,748,751,753,755,758,761],{"class":89,"line":228},[87,746,747],{"class":148},"r.",[87,749,750],{"class":93},"Any",[87,752,219],{"class":148},[87,754,710],{"class":97},[87,756,757],{"class":148},", gin.",[87,759,760],{"class":93},"WrapH",[87,762,763],{"class":148},"(mcpHTTPServer))\n",[16,765,766,767,770],{},"就这样，",[30,768,769],{},"POST \u002Fmcp"," 端点就生效了。AI 客户端通过这个 URL 发送 JSON-RPC 请求，MCP 服务器自动处理协议握手、工具列表返回、工具调用路由。",[73,772,774],{"id":773},"_5-完整的服务启动代码","5. 完整的服务启动代码",[78,776,778],{"className":118,"code":777,"language":94,"meta":83,"style":83},"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",[30,779,780,790,806,810,815,838,847,855,860,864,869,877,881,886,899,911,925,930,949,954,960,994,1031,1037,1042,1057],{"__ignoreMap":83},[87,781,782,784,787],{"class":89,"line":90},[87,783,336],{"class":126},[87,785,786],{"class":93}," main",[87,788,789],{"class":148},"() {\n",[87,791,792,795,797,800,803],{"class":89,"line":104},[87,793,794],{"class":148},"    r ",[87,796,152],{"class":126},[87,798,799],{"class":148}," gin.",[87,801,802],{"class":93},"Default",[87,804,805],{"class":148},"()\n",[87,807,808],{"class":89,"line":145},[87,809,142],{"emptyLinePlaceholder":141},[87,811,812],{"class":89,"line":164},[87,813,814],{"class":173},"    \u002F\u002F 创建 MCP 服务器\n",[87,816,817,820,822,824,826,828,831,833,836],{"class":89,"line":177},[87,818,819],{"class":148},"    mcpServer ",[87,821,152],{"class":126},[87,823,155],{"class":148},[87,825,158],{"class":93},[87,827,219],{"class":148},[87,829,830],{"class":97},"\"my-app\"",[87,832,354],{"class":148},[87,834,835],{"class":97},"\"1.0.0\"",[87,837,294],{"class":148},[87,839,840,843,845],{"class":89,"line":189},[87,841,842],{"class":148},"        server.",[87,844,195],{"class":93},[87,846,198],{"class":148},[87,848,849,851,853],{"class":89,"line":201},[87,850,842],{"class":148},[87,852,206],{"class":93},[87,854,198],{"class":148},[87,856,857],{"class":89,"line":211},[87,858,859],{"class":148},"    )\n",[87,861,862],{"class":89,"line":228},[87,863,142],{"emptyLinePlaceholder":141},[87,865,866],{"class":89,"line":390},[87,867,868],{"class":173},"    \u002F\u002F 注册工具\n",[87,870,871,874],{"class":89,"line":396},[87,872,873],{"class":93},"    registerTools",[87,875,876],{"class":148},"(mcpServer)\n",[87,878,879],{"class":89,"line":419},[87,880,142],{"emptyLinePlaceholder":141},[87,882,883],{"class":89,"line":617},[87,884,885],{"class":173},"    \u002F\u002F 挂载到 Gin\n",[87,887,888,891,893,895,897],{"class":89,"line":623},[87,889,890],{"class":148},"    mcpHTTP ",[87,892,152],{"class":126},[87,894,155],{"class":148},[87,896,695],{"class":93},[87,898,698],{"class":148},[87,900,901,903,905,907,909],{"class":89,"line":629},[87,902,842],{"class":148},[87,904,705],{"class":93},[87,906,219],{"class":148},[87,908,710],{"class":97},[87,910,225],{"class":148},[87,912,913,915,917,919,921,923],{"class":89,"line":647},[87,914,842],{"class":148},[87,916,719],{"class":93},[87,918,219],{"class":148},[87,920,724],{"class":415},[87,922,371],{"class":126},[87,924,729],{"class":148},[87,926,928],{"class":89,"line":927},17,[87,929,859],{"class":148},[87,931,933,936,938,940,942,944,946],{"class":89,"line":932},18,[87,934,935],{"class":148},"    r.",[87,937,750],{"class":93},[87,939,219],{"class":148},[87,941,710],{"class":97},[87,943,757],{"class":148},[87,945,760],{"class":93},[87,947,948],{"class":148},"(mcpHTTP))\n",[87,950,952],{"class":89,"line":951},19,[87,953,142],{"emptyLinePlaceholder":141},[87,955,957],{"class":89,"line":956},20,[87,958,959],{"class":173},"    \u002F\u002F 其他业务路由\n",[87,961,963,965,968,970,973,975,977,979,982,985,988,990,992],{"class":89,"line":962},21,[87,964,935],{"class":148},[87,966,967],{"class":93},"GET",[87,969,219],{"class":148},[87,971,972],{"class":97},"\"\u002Fapi\u002Fhealth\"",[87,974,354],{"class":148},[87,976,336],{"class":126},[87,978,219],{"class":148},[87,980,981],{"class":341},"c",[87,983,984],{"class":126}," *",[87,986,987],{"class":93},"gin",[87,989,348],{"class":148},[87,991,351],{"class":93},[87,993,387],{"class":148},[87,995,997,1000,1003,1005,1008,1010,1012,1014,1017,1020,1023,1026,1029],{"class":89,"line":996},22,[87,998,999],{"class":148},"        c.",[87,1001,1002],{"class":93},"JSON",[87,1004,219],{"class":148},[87,1006,1007],{"class":415},"200",[87,1009,354],{"class":148},[87,1011,987],{"class":93},[87,1013,348],{"class":148},[87,1015,1016],{"class":93},"H",[87,1018,1019],{"class":148},"{",[87,1021,1022],{"class":97},"\"status\"",[87,1024,1025],{"class":148},": ",[87,1027,1028],{"class":97},"\"ok\"",[87,1030,422],{"class":148},[87,1032,1034],{"class":89,"line":1033},23,[87,1035,1036],{"class":148},"    })\n",[87,1038,1040],{"class":89,"line":1039},24,[87,1041,142],{"emptyLinePlaceholder":141},[87,1043,1045,1047,1050,1052,1055],{"class":89,"line":1044},25,[87,1046,935],{"class":148},[87,1048,1049],{"class":93},"Run",[87,1051,219],{"class":148},[87,1053,1054],{"class":97},"\":8080\"",[87,1056,231],{"class":148},[87,1058,1060],{"class":89,"line":1059},26,[87,1061,1062],{"class":148},"}\n",[16,1064,1065],{},"此时启动服务，用 curl 测试：",[78,1067,1069],{"className":80,"code":1068,"language":82,"meta":83,"style":83},"# 发现工具列表\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",[30,1070,1071,1076,1093,1103,1111,1115,1120,1132,1140],{"__ignoreMap":83},[87,1072,1073],{"class":89,"line":90},[87,1074,1075],{"class":173},"# 发现工具列表\n",[87,1077,1078,1081,1084,1087,1090],{"class":89,"line":104},[87,1079,1080],{"class":93},"curl",[87,1082,1083],{"class":415}," -X",[87,1085,1086],{"class":97}," POST",[87,1088,1089],{"class":97}," http:\u002F\u002Flocalhost:8080\u002Fmcp",[87,1091,1092],{"class":415}," \\\n",[87,1094,1095,1098,1101],{"class":89,"line":145},[87,1096,1097],{"class":415},"  -H",[87,1099,1100],{"class":97}," \"Content-Type: application\u002Fjson\"",[87,1102,1092],{"class":415},[87,1104,1105,1108],{"class":89,"line":164},[87,1106,1107],{"class":415},"  -d",[87,1109,1110],{"class":97}," '{\"jsonrpc\":\"2.0\",\"id\":1,\"method\":\"tools\u002Flist\"}'\n",[87,1112,1113],{"class":89,"line":177},[87,1114,142],{"emptyLinePlaceholder":141},[87,1116,1117],{"class":89,"line":189},[87,1118,1119],{"class":173},"# 调用工具\n",[87,1121,1122,1124,1126,1128,1130],{"class":89,"line":201},[87,1123,1080],{"class":93},[87,1125,1083],{"class":415},[87,1127,1086],{"class":97},[87,1129,1089],{"class":97},[87,1131,1092],{"class":415},[87,1133,1134,1136,1138],{"class":89,"line":211},[87,1135,1097],{"class":415},[87,1137,1100],{"class":97},[87,1139,1092],{"class":415},[87,1141,1142,1144],{"class":89,"line":228},[87,1143,1107],{"class":415},[87,1145,1146],{"class":97}," '{\"jsonrpc\":\"2.0\",\"id\":2,\"method\":\"tools\u002Fcall\",\"params\":{\"name\":\"list_items\",\"arguments\":{}}}'\n",[11,1148,1150],{"id":1149},"拓展接入鉴权","拓展：接入鉴权",[16,1152,1153],{},"裸奔的 MCP 端点任何人都能调用，生产环境必须加鉴权。思路和普通 API 鉴权一致：Bearer Token。",[73,1155,1156],{"id":1156},"问题",[16,1158,1159,1160,1163,1164,1167],{},"MCP 的工具处理器签名为 ",[30,1161,1162],{},"func(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error)","，它不接收 Gin 的 ",[30,1165,1166],{},"*gin.Context","，所以无法直接在工具处理器里读取请求头。",[73,1169,1171],{"id":1170},"解决方案中间件传递用户信息","解决方案：中间件传递用户信息",[16,1173,1174,1175,1178],{},"用两层中间件：第一层做 Gin 的 Token 验证，第二层把用户信息从 Gin Context 复制到 ",[30,1176,1177],{},"request.Context","。",[78,1180,1182],{"className":118,"code":1181,"language":94,"meta":83,"style":83},"\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",[30,1183,1184,1189,1218,1239,1259,1275,1308,1313,1318,1322,1348,1389,1418,1422,1426,1430,1451,1463,1492,1496,1500,1504,1519,1533,1542,1546],{"__ignoreMap":83},[87,1185,1186],{"class":89,"line":90},[87,1187,1188],{"class":173},"\u002F\u002F Gin 鉴权中间件\n",[87,1190,1191,1193,1196,1198,1201,1203,1206,1209,1211,1213,1216],{"class":89,"line":104},[87,1192,336],{"class":126},[87,1194,1195],{"class":93}," AuthRequired",[87,1197,219],{"class":148},[87,1199,1200],{"class":341},"authService",[87,1202,984],{"class":126},[87,1204,1205],{"class":93},"AuthService",[87,1207,1208],{"class":148},") ",[87,1210,987],{"class":93},[87,1212,348],{"class":148},[87,1214,1215],{"class":93},"HandlerFunc",[87,1217,595],{"class":148},[87,1219,1220,1222,1225,1227,1229,1231,1233,1235,1237],{"class":89,"line":145},[87,1221,399],{"class":126},[87,1223,1224],{"class":126}," func",[87,1226,219],{"class":148},[87,1228,981],{"class":341},[87,1230,984],{"class":126},[87,1232,987],{"class":93},[87,1234,348],{"class":148},[87,1236,351],{"class":93},[87,1238,387],{"class":148},[87,1240,1241,1244,1246,1249,1252,1254,1257],{"class":89,"line":164},[87,1242,1243],{"class":148},"        authHeader ",[87,1245,152],{"class":126},[87,1247,1248],{"class":148}," c.",[87,1250,1251],{"class":93},"GetHeader",[87,1253,219],{"class":148},[87,1255,1256],{"class":97},"\"Authorization\"",[87,1258,231],{"class":148},[87,1260,1261,1264,1267,1270,1273],{"class":89,"line":177},[87,1262,1263],{"class":126},"        if",[87,1265,1266],{"class":148}," authHeader ",[87,1268,1269],{"class":126},"==",[87,1271,1272],{"class":97}," \"\"",[87,1274,595],{"class":148},[87,1276,1277,1280,1283,1285,1288,1290,1292,1294,1296,1298,1301,1303,1306],{"class":89,"line":189},[87,1278,1279],{"class":148},"            c.",[87,1281,1282],{"class":93},"AbortWithStatusJSON",[87,1284,219],{"class":148},[87,1286,1287],{"class":415},"401",[87,1289,354],{"class":148},[87,1291,987],{"class":93},[87,1293,348],{"class":148},[87,1295,1016],{"class":93},[87,1297,1019],{"class":148},[87,1299,1300],{"class":97},"\"message\"",[87,1302,1025],{"class":148},[87,1304,1305],{"class":97},"\"缺少认证令牌\"",[87,1307,422],{"class":148},[87,1309,1310],{"class":89,"line":201},[87,1311,1312],{"class":126},"            return\n",[87,1314,1315],{"class":89,"line":211},[87,1316,1317],{"class":148},"        }\n",[87,1319,1320],{"class":89,"line":228},[87,1321,142],{"emptyLinePlaceholder":141},[87,1323,1324,1327,1329,1332,1335,1338,1341,1343,1346],{"class":89,"line":390},[87,1325,1326],{"class":148},"        parts ",[87,1328,152],{"class":126},[87,1330,1331],{"class":148}," strings.",[87,1333,1334],{"class":93},"SplitN",[87,1336,1337],{"class":148},"(authHeader, ",[87,1339,1340],{"class":97},"\" \"",[87,1342,354],{"class":148},[87,1344,1345],{"class":415},"2",[87,1347,231],{"class":148},[87,1349,1350,1352,1355,1358,1360,1363,1366,1369,1372,1375,1378,1381,1384,1387],{"class":89,"line":396},[87,1351,1263],{"class":126},[87,1353,1354],{"class":93}," len",[87,1356,1357],{"class":148},"(parts) ",[87,1359,589],{"class":126},[87,1361,1362],{"class":415}," 2",[87,1364,1365],{"class":126}," ||",[87,1367,1368],{"class":126}," !",[87,1370,1371],{"class":148},"strings.",[87,1373,1374],{"class":93},"EqualFold",[87,1376,1377],{"class":148},"(parts[",[87,1379,1380],{"class":415},"0",[87,1382,1383],{"class":148},"], ",[87,1385,1386],{"class":97},"\"Bearer\"",[87,1388,387],{"class":148},[87,1390,1391,1393,1395,1397,1399,1401,1403,1405,1407,1409,1411,1413,1416],{"class":89,"line":419},[87,1392,1279],{"class":148},[87,1394,1282],{"class":93},[87,1396,219],{"class":148},[87,1398,1287],{"class":415},[87,1400,354],{"class":148},[87,1402,987],{"class":93},[87,1404,348],{"class":148},[87,1406,1016],{"class":93},[87,1408,1019],{"class":148},[87,1410,1300],{"class":97},[87,1412,1025],{"class":148},[87,1414,1415],{"class":97},"\"格式错误，请使用 Bearer \u003Ctoken>\"",[87,1417,422],{"class":148},[87,1419,1420],{"class":89,"line":617},[87,1421,1312],{"class":126},[87,1423,1424],{"class":89,"line":623},[87,1425,1317],{"class":148},[87,1427,1428],{"class":89,"line":629},[87,1429,142],{"emptyLinePlaceholder":141},[87,1431,1432,1435,1437,1440,1443,1445,1448],{"class":89,"line":647},[87,1433,1434],{"class":148},"        user, err ",[87,1436,152],{"class":126},[87,1438,1439],{"class":148}," authService.",[87,1441,1442],{"class":93},"GetUserByToken",[87,1444,1377],{"class":148},[87,1446,1447],{"class":415},"1",[87,1449,1450],{"class":148},"])\n",[87,1452,1453,1455,1457,1459,1461],{"class":89,"line":927},[87,1454,1263],{"class":126},[87,1456,586],{"class":148},[87,1458,589],{"class":126},[87,1460,592],{"class":415},[87,1462,595],{"class":148},[87,1464,1465,1467,1469,1471,1473,1475,1477,1479,1481,1483,1485,1487,1490],{"class":89,"line":932},[87,1466,1279],{"class":148},[87,1468,1282],{"class":93},[87,1470,219],{"class":148},[87,1472,1287],{"class":415},[87,1474,354],{"class":148},[87,1476,987],{"class":93},[87,1478,348],{"class":148},[87,1480,1016],{"class":93},[87,1482,1019],{"class":148},[87,1484,1300],{"class":97},[87,1486,1025],{"class":148},[87,1488,1489],{"class":97},"\"无效的认证令牌\"",[87,1491,422],{"class":148},[87,1493,1494],{"class":89,"line":951},[87,1495,1312],{"class":126},[87,1497,1498],{"class":89,"line":956},[87,1499,1317],{"class":148},[87,1501,1502],{"class":89,"line":962},[87,1503,142],{"emptyLinePlaceholder":141},[87,1505,1506,1508,1511,1513,1516],{"class":89,"line":996},[87,1507,999],{"class":148},[87,1509,1510],{"class":93},"Set",[87,1512,219],{"class":148},[87,1514,1515],{"class":97},"\"userID\"",[87,1517,1518],{"class":148},", user.ID)\n",[87,1520,1521,1523,1525,1527,1530],{"class":89,"line":1033},[87,1522,999],{"class":148},[87,1524,1510],{"class":93},[87,1526,219],{"class":148},[87,1528,1529],{"class":97},"\"username\"",[87,1531,1532],{"class":148},", user.Username)\n",[87,1534,1535,1537,1540],{"class":89,"line":1039},[87,1536,999],{"class":148},[87,1538,1539],{"class":93},"Next",[87,1541,805],{"class":148},[87,1543,1544],{"class":89,"line":1044},[87,1545,620],{"class":148},[87,1547,1548],{"class":89,"line":1059},[87,1549,1062],{"class":148},[78,1551,1553],{"className":118,"code":1552,"language":94,"meta":83,"style":83},"\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",[30,1554,1555,1560,1571,1579,1593,1605,1609,1613,1631,1651,1665,1691,1713,1728,1736,1740],{"__ignoreMap":83},[87,1556,1557],{"class":89,"line":90},[87,1558,1559],{"class":173},"\u002F\u002F 用户信息桥接中间件：Gin Context → request.Context\n",[87,1561,1562,1565,1568],{"class":89,"line":104},[87,1563,1564],{"class":126},"type",[87,1566,1567],{"class":93}," ctxKey",[87,1569,1570],{"class":126}," string\n",[87,1572,1573,1576],{"class":89,"line":145},[87,1574,1575],{"class":126},"const",[87,1577,1578],{"class":148}," (\n",[87,1580,1581,1584,1587,1590],{"class":89,"line":164},[87,1582,1583],{"class":415},"    ctxUserID",[87,1585,1586],{"class":93},"   ctxKey",[87,1588,1589],{"class":126}," =",[87,1591,1592],{"class":97}," \"userID\"\n",[87,1594,1595,1598,1600,1602],{"class":89,"line":177},[87,1596,1597],{"class":415},"    ctxUsername",[87,1599,1567],{"class":93},[87,1601,1589],{"class":126},[87,1603,1604],{"class":97}," \"username\"\n",[87,1606,1607],{"class":89,"line":189},[87,1608,231],{"class":148},[87,1610,1611],{"class":89,"line":201},[87,1612,142],{"emptyLinePlaceholder":141},[87,1614,1615,1617,1620,1623,1625,1627,1629],{"class":89,"line":211},[87,1616,336],{"class":126},[87,1618,1619],{"class":93}," WithUserContext",[87,1621,1622],{"class":148},"() ",[87,1624,987],{"class":93},[87,1626,348],{"class":148},[87,1628,1215],{"class":93},[87,1630,595],{"class":148},[87,1632,1633,1635,1637,1639,1641,1643,1645,1647,1649],{"class":89,"line":228},[87,1634,399],{"class":126},[87,1636,1224],{"class":126},[87,1638,219],{"class":148},[87,1640,981],{"class":341},[87,1642,984],{"class":126},[87,1644,987],{"class":93},[87,1646,348],{"class":148},[87,1648,351],{"class":93},[87,1650,387],{"class":148},[87,1652,1653,1656,1658,1661,1663],{"class":89,"line":390},[87,1654,1655],{"class":148},"        ctx ",[87,1657,152],{"class":126},[87,1659,1660],{"class":148}," c.Request.",[87,1662,351],{"class":93},[87,1664,805],{"class":148},[87,1666,1667,1669,1672,1675,1678,1681,1684,1686,1688],{"class":89,"line":396},[87,1668,1655],{"class":148},[87,1670,1671],{"class":126},"=",[87,1673,1674],{"class":148}," context.",[87,1676,1677],{"class":93},"WithValue",[87,1679,1680],{"class":148},"(ctx, ctxUserID, c.",[87,1682,1683],{"class":93},"GetUint",[87,1685,219],{"class":148},[87,1687,1515],{"class":97},[87,1689,1690],{"class":148},"))\n",[87,1692,1693,1695,1697,1699,1701,1704,1707,1709,1711],{"class":89,"line":419},[87,1694,1655],{"class":148},[87,1696,1671],{"class":126},[87,1698,1674],{"class":148},[87,1700,1677],{"class":93},[87,1702,1703],{"class":148},"(ctx, ctxUsername, c.",[87,1705,1706],{"class":93},"GetString",[87,1708,219],{"class":148},[87,1710,1529],{"class":97},[87,1712,1690],{"class":148},[87,1714,1715,1718,1720,1722,1725],{"class":89,"line":617},[87,1716,1717],{"class":148},"        c.Request ",[87,1719,1671],{"class":126},[87,1721,1660],{"class":148},[87,1723,1724],{"class":93},"WithContext",[87,1726,1727],{"class":148},"(ctx)\n",[87,1729,1730,1732,1734],{"class":89,"line":623},[87,1731,999],{"class":148},[87,1733,1539],{"class":93},[87,1735,805],{"class":148},[87,1737,1738],{"class":89,"line":629},[87,1739,620],{"class":148},[87,1741,1742],{"class":89,"line":647},[87,1743,1062],{"class":148},[73,1745,1746],{"id":1746},"组合使用",[78,1748,1750],{"className":118,"code":1749,"language":94,"meta":83,"style":83},"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",[30,1751,1752,1764,1775,1786,1799],{"__ignoreMap":83},[87,1753,1754,1756,1758,1760,1762],{"class":89,"line":90},[87,1755,747],{"class":148},[87,1757,750],{"class":93},[87,1759,219],{"class":148},[87,1761,710],{"class":97},[87,1763,294],{"class":148},[87,1765,1766,1769,1772],{"class":89,"line":104},[87,1767,1768],{"class":93},"    AuthRequired",[87,1770,1771],{"class":148},"(authService),  ",[87,1773,1774],{"class":173},"\u002F\u002F 1. 验证 Token\n",[87,1776,1777,1780,1783],{"class":89,"line":145},[87,1778,1779],{"class":93},"    WithUserContext",[87,1781,1782],{"class":148},"(),           ",[87,1784,1785],{"class":173},"\u002F\u002F 2. 桥接用户信息到 request.Context\n",[87,1787,1788,1791,1793,1796],{"class":89,"line":164},[87,1789,1790],{"class":148},"    gin.",[87,1792,760],{"class":93},[87,1794,1795],{"class":148},"(mcpHTTP),         ",[87,1797,1798],{"class":173},"\u002F\u002F 3. MCP 处理\n",[87,1800,1801],{"class":89,"line":177},[87,1802,231],{"class":148},[16,1804,1805],{},"在工具处理器里就能拿到用户信息了：",[78,1807,1809],{"className":118,"code":1808,"language":94,"meta":83,"style":83},"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",[30,1810,1811,1867,1888,1897,1914,1918,1922,1938,1943,1956],{"__ignoreMap":83},[87,1812,1813,1815,1818,1821,1823,1826,1828,1831,1833,1835,1837,1839,1841,1843,1845,1847,1849,1851,1853,1855,1857,1859,1861,1863,1865],{"class":89,"line":90},[87,1814,336],{"class":126},[87,1816,1817],{"class":148}," (",[87,1819,1820],{"class":341},"h ",[87,1822,371],{"class":126},[87,1824,1825],{"class":93},"Handler",[87,1827,1208],{"class":148},[87,1829,1830],{"class":93},"handleListItems",[87,1832,219],{"class":148},[87,1834,342],{"class":341},[87,1836,345],{"class":93},[87,1838,348],{"class":148},[87,1840,351],{"class":93},[87,1842,354],{"class":148},[87,1844,357],{"class":341},[87,1846,360],{"class":93},[87,1848,348],{"class":148},[87,1850,365],{"class":93},[87,1852,368],{"class":148},[87,1854,371],{"class":126},[87,1856,374],{"class":93},[87,1858,348],{"class":148},[87,1860,379],{"class":93},[87,1862,354],{"class":148},[87,1864,384],{"class":126},[87,1866,387],{"class":148},[87,1868,1869,1872,1874,1877,1880,1883,1886],{"class":89,"line":104},[87,1870,1871],{"class":148},"    userID, ok ",[87,1873,152],{"class":126},[87,1875,1876],{"class":148}," ctx.",[87,1878,1879],{"class":93},"Value",[87,1881,1882],{"class":148},"(ctxUserID).(",[87,1884,1885],{"class":126},"uint",[87,1887,231],{"class":148},[87,1889,1890,1892,1894],{"class":89,"line":145},[87,1891,583],{"class":126},[87,1893,1368],{"class":126},[87,1895,1896],{"class":148},"ok {\n",[87,1898,1899,1901,1903,1905,1907,1910,1912],{"class":89,"line":164},[87,1900,600],{"class":126},[87,1902,283],{"class":148},[87,1904,605],{"class":93},[87,1906,219],{"class":148},[87,1908,1909],{"class":97},"\"未认证\"",[87,1911,412],{"class":148},[87,1913,416],{"class":415},[87,1915,1916],{"class":89,"line":177},[87,1917,620],{"class":148},[87,1919,1920],{"class":89,"line":189},[87,1921,142],{"emptyLinePlaceholder":141},[87,1923,1924,1927,1929,1932,1935],{"class":89,"line":201},[87,1925,1926],{"class":148},"    items, _ ",[87,1928,152],{"class":126},[87,1930,1931],{"class":148}," h.service.",[87,1933,1934],{"class":93},"ListByUser",[87,1936,1937],{"class":148},"(userID)\n",[87,1939,1940],{"class":89,"line":211},[87,1941,1942],{"class":173},"    \u002F\u002F ...\n",[87,1944,1945,1947,1949,1951,1954],{"class":89,"line":228},[87,1946,399],{"class":126},[87,1948,283],{"class":148},[87,1950,404],{"class":93},[87,1952,1953],{"class":148},"(result), ",[87,1955,416],{"class":415},[87,1957,1958],{"class":89,"line":390},[87,1959,1062],{"class":148},[73,1961,1962],{"id":1962},"客户端配置",[16,1964,1965],{},"AI 客户端（如 Claude Desktop）配置 MCP 服务器时带上 Authorization 头：",[78,1967,1971],{"className":1968,"code":1969,"language":1970,"meta":83,"style":83},"language-json shiki shiki-themes github-light github-dark","{\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","json",[30,1972,1973,1978,1986,1992,2004,2011,2021,2026,2030,2035],{"__ignoreMap":83},[87,1974,1975],{"class":89,"line":90},[87,1976,1977],{"class":148},"{\n",[87,1979,1980,1983],{"class":89,"line":104},[87,1981,1982],{"class":415},"  \"mcpServers\"",[87,1984,1985],{"class":148},": {\n",[87,1987,1988,1990],{"class":89,"line":145},[87,1989,167],{"class":415},[87,1991,1985],{"class":148},[87,1993,1994,1997,1999,2002],{"class":89,"line":164},[87,1995,1996],{"class":415},"      \"url\"",[87,1998,1025],{"class":148},[87,2000,2001],{"class":97},"\"http:\u002F\u002Flocalhost:8080\u002Fmcp\"",[87,2003,294],{"class":148},[87,2005,2006,2009],{"class":89,"line":177},[87,2007,2008],{"class":415},"      \"headers\"",[87,2010,1985],{"class":148},[87,2012,2013,2016,2018],{"class":89,"line":189},[87,2014,2015],{"class":415},"        \"Authorization\"",[87,2017,1025],{"class":148},[87,2019,2020],{"class":97},"\"Bearer your-token-here\"\n",[87,2022,2023],{"class":89,"line":201},[87,2024,2025],{"class":148},"      }\n",[87,2027,2028],{"class":89,"line":211},[87,2029,620],{"class":148},[87,2031,2032],{"class":89,"line":228},[87,2033,2034],{"class":148},"  }\n",[87,2036,2037],{"class":89,"line":390},[87,2038,1062],{"class":148},[11,2040,2041],{"id":2041},"总结",[2043,2044,2045,2058],"table",{},[2046,2047,2048],"thead",{},[2049,2050,2051,2055],"tr",{},[2052,2053,2054],"th",{},"步骤",[2052,2056,2057],{},"说明",[2059,2060,2061,2071,2083,2092,2101],"tbody",{},[2049,2062,2063,2068],{},[2064,2065,2066],"td",{},[30,2067,158],{},[2064,2069,2070],{},"创建 MCP 协议实例",[2049,2072,2073,2080],{},[2064,2074,2075,2077,2078],{},[30,2076,286],{}," + ",[30,2079,330],{},[2064,2081,2082],{},"定义并注册工具",[2049,2084,2085,2089],{},[2064,2086,2087],{},[30,2088,695],{},[2064,2090,2091],{},"转为 HTTP Handler",[2049,2093,2094,2098],{},[2064,2095,2096],{},[30,2097,663],{},[2064,2099,2100],{},"挂载到 Gin 路由",[2049,2102,2103,2111],{},[2064,2104,2105,2077,2108],{},[30,2106,2107],{},"AuthRequired",[30,2109,2110],{},"WithUserContext",[2064,2112,2113],{},"鉴权 + 用户信息桥接",[16,2115,2116,2117,2120,2121,1178],{},"核心就一句话：",[38,2118,2119],{},"MCP 是 HTTP Handler，挂上去就行。"," 鉴权复用你已有的中间件体系，只需要多写一个桥接函数把用户信息传进 ",[30,2122,1177],{},[2124,2125,2126],"style",{},"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":83,"searchDepth":104,"depth":145,"links":2128},[2129,2130,2131,2132,2139,2145],{"id":13,"depth":104,"text":14},{"id":24,"depth":104,"text":25},{"id":43,"depth":104,"text":43},{"id":71,"depth":104,"text":71,"children":2133},[2134,2135,2136,2137,2138],{"id":75,"depth":145,"text":76},{"id":114,"depth":145,"text":115},{"id":247,"depth":145,"text":248},{"id":652,"depth":145,"text":653},{"id":773,"depth":145,"text":774},{"id":1149,"depth":104,"text":1150,"children":2140},[2141,2142,2143,2144],{"id":1156,"depth":145,"text":1156},{"id":1170,"depth":145,"text":1171},{"id":1746,"depth":145,"text":1746},{"id":1962,"depth":145,"text":1962},{"id":2041,"depth":104,"text":2041},"介绍如何使用 mark3labs\u002Fmcp-go 与 Gin 框架集成，搭建一个带鉴权的 MCP（Model Context Protocol）服务器，让 AI Agent 能直接调用你的后端服务。","进阶",false,"md","\u002Fposts\u002Fgo-mcp-gin\u002Fimg\u002Fcover.svg",{},"\u002Fposts\u002Fgo-mcp-gin","2026-06-01T00:50:00",{"title":5,"description":2146},"Go 工程实践","posts\u002Fgo-mcp-gin\u002Findex",[2158,2159,2160,2161,2162],"Go","MCP","Gin","AI Agent","后端","2026-06-01","5fZl1Vigs2lHb5fHPz-0YZFyFD38ws1heOouZUG1g6M",[2166,2962,3377,3599,4013,5186,5471,7198,7980,8959],{"id":2167,"title":2168,"author":6,"body":2169,"description":2950,"difficulty":2147,"draft":2148,"extension":2149,"image":2951,"meta":2952,"navigation":141,"path":2953,"pinned":2148,"published":2954,"seo":2955,"series":2956,"seriesOrder":90,"stem":2957,"tags":2958,"updated":2954,"__hash__":2961},"posts\u002Fposts\u002Fdocker-musl-build\u002Findex.md","Docker 编译 musl 可执行文件",{"type":8,"value":2170,"toc":2939},[2171,2174,2177,2180,2399,2402,2444,2447,2465,2468,2616,2619,2755,2758,2761,2819,2822,2936],[11,2172,2173],{"id":2173},"解决问题",[16,2175,2176],{},"用 Go 进行跨平台编译时往往步骤复杂，用 Docker 可以轻松解决。但 Docker 的当前最小镜像 Alpine 其动态链接库采用 musl，与主流的 glibc 不同，往往不能方便执行。",[11,2178,2179],{"id":2179},"查阅资料",[78,2181,2183],{"className":80,"code":2182,"language":82,"meta":83,"style":83},"# 启动容器并挂载当前目录到 `\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",[30,2184,2185,2190,2227,2231,2236,2264,2280,2294,2299,2311,2333,2341,2345,2350,2360,2370,2374,2382,2386],{"__ignoreMap":83},[87,2186,2187],{"class":89,"line":90},[87,2188,2189],{"class":173},"# 启动容器并挂载当前目录到 `\u002Fapp`\n",[87,2191,2192,2195,2198,2201,2204,2207,2209,2212,2215,2218,2221,2224],{"class":89,"line":104},[87,2193,2194],{"class":93},"docker",[87,2196,2197],{"class":97}," run",[87,2199,2200],{"class":415}," -it",[87,2202,2203],{"class":415}," --rm",[87,2205,2206],{"class":415}," -v",[87,2208,130],{"class":97},[87,2210,2211],{"class":148},"$PWD",[87,2213,2214],{"class":97},"\":\u002Fapp",[87,2216,2217],{"class":415}," -w",[87,2219,2220],{"class":97}," \u002Fapp",[87,2222,2223],{"class":97}," ubuntu:20.04",[87,2225,2226],{"class":97}," bash\n",[87,2228,2229],{"class":89,"line":145},[87,2230,142],{"emptyLinePlaceholder":141},[87,2232,2233],{"class":89,"line":164},[87,2234,2235],{"class":173},"# 在容器内执行以下操作：\n",[87,2237,2238,2241,2244,2247,2249,2252,2255,2258,2261],{"class":89,"line":177},[87,2239,2240],{"class":93},"apt-get",[87,2242,2243],{"class":97}," update",[87,2245,2246],{"class":148}," && ",[87,2248,2240],{"class":93},[87,2250,2251],{"class":97}," install",[87,2253,2254],{"class":415}," -y",[87,2256,2257],{"class":97}," musl-tools",[87,2259,2260],{"class":97}," golang-go",[87,2262,2263],{"class":173},"  # 安装 musl 和 Go\n",[87,2265,2266,2269,2272,2274,2277],{"class":89,"line":189},[87,2267,2268],{"class":126},"export",[87,2270,2271],{"class":148}," CC",[87,2273,1671],{"class":126},[87,2275,2276],{"class":148},"musl-gcc  ",[87,2278,2279],{"class":173},"# 指定 musl 编译器\n",[87,2281,2282,2284,2287,2289,2291],{"class":89,"line":201},[87,2283,2268],{"class":126},[87,2285,2286],{"class":148}," CGO_ENABLED",[87,2288,1671],{"class":126},[87,2290,1447],{"class":415},[87,2292,2293],{"class":173},"  # 启用 CGO\n",[87,2295,2296],{"class":89,"line":211},[87,2297,2298],{"class":173},"# 设置代理\n",[87,2300,2301,2303,2306,2308],{"class":89,"line":228},[87,2302,94],{"class":93},[87,2304,2305],{"class":97}," env",[87,2307,2217],{"class":415},[87,2309,2310],{"class":97}," GOPROXY=https:\u002F\u002Fgoproxy.cn,direct\n",[87,2312,2313,2315,2318,2321,2324,2327,2330],{"class":89,"line":390},[87,2314,94],{"class":93},[87,2316,2317],{"class":97}," build",[87,2319,2320],{"class":415}," -ldflags",[87,2322,2323],{"class":97}," '-linkmode external -extldflags \"-static\"'",[87,2325,2326],{"class":415}," -o",[87,2328,2329],{"class":97}," myapp",[87,2331,2332],{"class":173},"  # 静态编译\n",[87,2334,2335,2338],{"class":89,"line":396},[87,2336,2337],{"class":415},"exit",[87,2339,2340],{"class":173},"  # 退出容器\n",[87,2342,2343],{"class":89,"line":419},[87,2344,142],{"emptyLinePlaceholder":141},[87,2346,2347],{"class":89,"line":617},[87,2348,2349],{"class":173},"# 验证结果（在宿主机执行）\n",[87,2351,2352,2355,2357],{"class":89,"line":623},[87,2353,2354],{"class":93},"file",[87,2356,2329],{"class":97},[87,2358,2359],{"class":173},"  # 应显示 \"statically linked\"\n",[87,2361,2362,2365,2367],{"class":89,"line":629},[87,2363,2364],{"class":93},"ldd",[87,2366,2329],{"class":97},[87,2368,2369],{"class":173},"   # 应显示 \"not a dynamic executable\"\n",[87,2371,2372],{"class":89,"line":647},[87,2373,142],{"emptyLinePlaceholder":141},[87,2375,2376,2379],{"class":89,"line":927},[87,2377,2378],{"class":93},"wget",[87,2380,2381],{"class":97}," https:\u002F\u002Fgo.dev\u002Fdl\u002Fgo1.21.linux-amd64.tar.gz\n",[87,2383,2384],{"class":89,"line":932},[87,2385,142],{"emptyLinePlaceholder":141},[87,2387,2388,2391,2394,2397],{"class":89,"line":951},[87,2389,2390],{"class":93},"ENV",[87,2392,2393],{"class":97}," PATH=\"\u002Fdata\u002Fgo\u002Fbin:",[87,2395,2396],{"class":148},"$PATH",[87,2398,136],{"class":97},[73,2400,2401],{"id":2401},"遇到问题",[48,2403,2404,2430],{},[51,2405,2406,2409,2410,2413,2416,2417,2420,2422,2423,2425],{},[30,2407,2408],{},"go mod tidy"," 时报错 ",[30,2411,2412],{},"tls: failed to verify certificate: x509: certificate",[2414,2415],"br",{},"解决：",[30,2418,2419],{},"RUN apt-get update && apt-get install -y ca-certificates && rm -rf \u002Fvar\u002Flib\u002Fapt\u002Flists\u002F*",[2414,2421],{},"基础镜像的证书未更新，导致 https 的请求出现类似错误。",[2414,2424],{},[2426,2427],"img",{"alt":2428,"src":2429},"image-20250327100813512","\u002Fposts\u002Fdocker-musl-build\u002Fimg\u002Fimage-20250327100813512.png",[51,2431,2432,2433,2436,2437,2439,2440,2443],{},"直接安装 ",[30,2434,2435],{},"golang-go"," 其版本过低",[2414,2438],{},"解决: ",[30,2441,2442],{},"PATH=\"\u002Fdata\u002Fgo\u002Fbin:$PATH\""," 手动指定环境",[11,2445,2446],{"id":2446},"最终方案",[48,2448,2449,2456,2459],{},[51,2450,2451,2452,2455],{},"下载 ",[30,2453,2454],{},"go1.24.1.linux-amd64.tar.gz"," 解压",[51,2457,2458],{},"编写 Dockerfile",[51,2460,2461,2462],{},"执行 ",[30,2463,2464],{},"docker build -t go-build:v1 .",[16,2466,2467],{},"Dockerfile 内容：",[78,2469,2473],{"className":2470,"code":2471,"language":2472,"meta":83,"style":83},"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",[30,2474,2475,2483,2487,2498,2502,2510,2514,2524,2528,2536,2540,2545,2552,2556,2563,2568,2575,2579,2587,2591,2598,2602],{"__ignoreMap":83},[87,2476,2477,2480],{"class":89,"line":90},[87,2478,2479],{"class":126},"FROM",[87,2481,2482],{"class":148}," ubuntu:20.04\n",[87,2484,2485],{"class":89,"line":104},[87,2486,142],{"emptyLinePlaceholder":141},[87,2488,2489,2492,2495],{"class":89,"line":145},[87,2490,2491],{"class":126},"LABEL",[87,2493,2494],{"class":148}," org.opencontainers.image.authors=",[87,2496,2497],{"class":97},"\"sutong\"\n",[87,2499,2500],{"class":89,"line":164},[87,2501,142],{"emptyLinePlaceholder":141},[87,2503,2504,2507],{"class":89,"line":177},[87,2505,2506],{"class":126},"COPY",[87,2508,2509],{"class":148}," go\u002F \u002Fgo\u002F\n",[87,2511,2512],{"class":89,"line":189},[87,2513,142],{"emptyLinePlaceholder":141},[87,2515,2516,2518,2521],{"class":89,"line":201},[87,2517,2390],{"class":126},[87,2519,2520],{"class":148}," PATH=",[87,2522,2523],{"class":97},"\"\u002Fgo\u002Fbin:$PATH\"\n",[87,2525,2526],{"class":89,"line":211},[87,2527,142],{"emptyLinePlaceholder":141},[87,2529,2530,2533],{"class":89,"line":228},[87,2531,2532],{"class":126},"RUN",[87,2534,2535],{"class":148}," apt-get update && apt-get install -y musl-tools\n",[87,2537,2538],{"class":89,"line":390},[87,2539,142],{"emptyLinePlaceholder":141},[87,2541,2542],{"class":89,"line":396},[87,2543,2544],{"class":173},"## go mod tidy 时报错 tls: failed to verify certificate: x509: certificate\n",[87,2546,2547,2549],{"class":89,"line":419},[87,2548,2532],{"class":126},[87,2550,2551],{"class":148}," apt-get update && apt-get install -y ca-certificates && rm -rf \u002Fvar\u002Flib\u002Fapt\u002Flists\u002F*\n",[87,2553,2554],{"class":89,"line":617},[87,2555,2279],{"class":173},[87,2557,2558,2560],{"class":89,"line":623},[87,2559,2390],{"class":126},[87,2561,2562],{"class":148}," CC=musl-gcc\n",[87,2564,2565],{"class":89,"line":629},[87,2566,2567],{"class":173},"# 启用 CGO\n",[87,2569,2570,2572],{"class":89,"line":647},[87,2571,2390],{"class":126},[87,2573,2574],{"class":148}," CGO_ENABLED=1\n",[87,2576,2577],{"class":89,"line":927},[87,2578,142],{"emptyLinePlaceholder":141},[87,2580,2581,2584],{"class":89,"line":932},[87,2582,2583],{"class":126},"VOLUME",[87,2585,2586],{"class":148}," \u002Fdata\n",[87,2588,2589],{"class":89,"line":951},[87,2590,142],{"emptyLinePlaceholder":141},[87,2592,2593,2596],{"class":89,"line":956},[87,2594,2595],{"class":126},"WORKDIR",[87,2597,2586],{"class":148},[87,2599,2600],{"class":89,"line":962},[87,2601,142],{"emptyLinePlaceholder":141},[87,2603,2604,2607,2610,2613],{"class":89,"line":996},[87,2605,2606],{"class":126},"CMD",[87,2608,2609],{"class":148}," [",[87,2611,2612],{"class":97},"\"\u002Fbin\u002Fbash\"",[87,2614,2615],{"class":148},"]\n",[73,2617,2618],{"id":2618},"构建",[78,2620,2622],{"className":80,"code":2621,"language":82,"meta":83,"style":83},"# 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",[30,2623,2624,2629,2633,2638,2664,2668,2673,2708,2713,2717,2750],{"__ignoreMap":83},[87,2625,2626],{"class":89,"line":90},[87,2627,2628],{"class":173},"# docker build -t go-build:v1 .\n",[87,2630,2631],{"class":89,"line":104},[87,2632,142],{"emptyLinePlaceholder":141},[87,2634,2635],{"class":89,"line":145},[87,2636,2637],{"class":173},"# 直接执行编译 编译后删除\n",[87,2639,2640,2642,2644,2646,2648,2651,2654,2657,2659,2661],{"class":89,"line":164},[87,2641,2194],{"class":93},[87,2643,2197],{"class":97},[87,2645,2203],{"class":415},[87,2647,2206],{"class":415},[87,2649,2650],{"class":97}," .:\u002Fdata",[87,2652,2653],{"class":97}," go-build:v1",[87,2655,2656],{"class":97}," go",[87,2658,2317],{"class":97},[87,2660,2326],{"class":415},[87,2662,2663],{"class":97}," app_musl\n",[87,2665,2666],{"class":89,"line":177},[87,2667,142],{"emptyLinePlaceholder":141},[87,2669,2670],{"class":89,"line":189},[87,2671,2672],{"class":173},"# 构建编译环境\n",[87,2674,2675,2677,2679,2681,2684,2686,2688,2691,2694,2697,2700,2703,2705],{"class":89,"line":201},[87,2676,2194],{"class":93},[87,2678,2197],{"class":97},[87,2680,2200],{"class":415},[87,2682,2683],{"class":415}," -d",[87,2685,2206],{"class":415},[87,2687,2650],{"class":97},[87,2689,2690],{"class":415}," --name",[87,2692,2693],{"class":126}," \u003C",[87,2695,2696],{"class":97},"容器名",[87,2698,2699],{"class":148},"称",[87,2701,2702],{"class":126},">",[87,2704,2653],{"class":97},[87,2706,2707],{"class":97}," \u002Fbin\u002Fbash\n",[87,2709,2710],{"class":89,"line":211},[87,2711,2712],{"class":173},"# eg：docker run -it -d -v .:\u002Fdata --name logbus-build go-build:v1 \u002Fbin\u002Fbash\n",[87,2714,2715],{"class":89,"line":228},[87,2716,142],{"emptyLinePlaceholder":141},[87,2718,2719,2721,2724,2726,2728,2730,2732,2734,2736,2738,2740,2742,2745,2747],{"class":89,"line":390},[87,2720,2194],{"class":93},[87,2722,2723],{"class":97}," exec",[87,2725,2200],{"class":415},[87,2727,2693],{"class":126},[87,2729,2696],{"class":97},[87,2731,2699],{"class":148},[87,2733,2702],{"class":126},[87,2735,2656],{"class":97},[87,2737,2317],{"class":97},[87,2739,2326],{"class":415},[87,2741,2693],{"class":126},[87,2743,2744],{"class":97},"myap",[87,2746,16],{"class":148},[87,2748,2749],{"class":126},">\n",[87,2751,2752],{"class":89,"line":396},[87,2753,2754],{"class":173},"# eg：docker exec -it logbus-build go build -o logbus_musl_1\n",[73,2756,2757],{"id":2757},"额外注意",[16,2759,2760],{},"若没科学上网需要设置代理：",[78,2762,2764],{"className":80,"code":2763,"language":82,"meta":83,"style":83},"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",[30,2765,2766,2784,2794,2810],{"__ignoreMap":83},[87,2767,2768,2770,2772,2774,2776,2778,2780,2782],{"class":89,"line":90},[87,2769,2194],{"class":93},[87,2771,2723],{"class":97},[87,2773,2200],{"class":415},[87,2775,2693],{"class":126},[87,2777,2696],{"class":97},[87,2779,2699],{"class":148},[87,2781,2702],{"class":126},[87,2783,2707],{"class":97},[87,2785,2786,2788,2790,2792],{"class":89,"line":104},[87,2787,94],{"class":93},[87,2789,2305],{"class":97},[87,2791,2217],{"class":415},[87,2793,2310],{"class":97},[87,2795,2796,2798,2800,2802,2804,2806,2808],{"class":89,"line":145},[87,2797,94],{"class":93},[87,2799,2317],{"class":97},[87,2801,2320],{"class":415},[87,2803,2323],{"class":97},[87,2805,2326],{"class":415},[87,2807,2329],{"class":97},[87,2809,2332],{"class":173},[87,2811,2812,2814,2816],{"class":89,"line":164},[87,2813,94],{"class":93},[87,2815,2317],{"class":97},[87,2817,2818],{"class":173}," # musl 库编译\n",[73,2820,2821],{"id":2821},"运行环境示例",[78,2823,2825],{"className":2470,"code":2824,"language":2472,"meta":83,"style":83},"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",[30,2826,2827,2834,2838,2846,2850,2857,2861,2868,2875,2879,2886,2890,2897,2901,2922,2926,2931],{"__ignoreMap":83},[87,2828,2829,2831],{"class":89,"line":90},[87,2830,2479],{"class":126},[87,2832,2833],{"class":148}," alpine:latest\n",[87,2835,2836],{"class":89,"line":104},[87,2837,142],{"emptyLinePlaceholder":141},[87,2839,2840,2842,2844],{"class":89,"line":145},[87,2841,2491],{"class":126},[87,2843,2494],{"class":148},[87,2845,2497],{"class":97},[87,2847,2848],{"class":89,"line":164},[87,2849,142],{"emptyLinePlaceholder":141},[87,2851,2852,2854],{"class":89,"line":177},[87,2853,2506],{"class":126},[87,2855,2856],{"class":148}," glog_musl \u002Fapp\u002F\n",[87,2858,2859],{"class":89,"line":189},[87,2860,142],{"emptyLinePlaceholder":141},[87,2862,2863,2865],{"class":89,"line":201},[87,2864,2506],{"class":126},[87,2866,2867],{"class":148}," conf.ini \u002Fdata\u002F\n",[87,2869,2870,2872],{"class":89,"line":211},[87,2871,2506],{"class":126},[87,2873,2874],{"class":148}," ipname.ini \u002Fdata\u002F\n",[87,2876,2877],{"class":89,"line":228},[87,2878,142],{"emptyLinePlaceholder":141},[87,2880,2881,2883],{"class":89,"line":390},[87,2882,2595],{"class":126},[87,2884,2885],{"class":148}," \u002Fapp\n",[87,2887,2888],{"class":89,"line":396},[87,2889,142],{"emptyLinePlaceholder":141},[87,2891,2892,2894],{"class":89,"line":419},[87,2893,2532],{"class":126},[87,2895,2896],{"class":148}," chmod 777 \u002Fapp\u002Fglog_musl\n",[87,2898,2899],{"class":89,"line":617},[87,2900,142],{"emptyLinePlaceholder":141},[87,2902,2903,2905,2907,2910,2912,2915,2917,2920],{"class":89,"line":623},[87,2904,2606],{"class":126},[87,2906,2609],{"class":148},[87,2908,2909],{"class":97},"\".\u002Fglog_musl\"",[87,2911,354],{"class":148},[87,2913,2914],{"class":97},"\"logsweb\"",[87,2916,354],{"class":148},[87,2918,2919],{"class":97},"\"\u002Fdata\u002Fconf.ini\"",[87,2921,2615],{"class":148},[87,2923,2924],{"class":89,"line":629},[87,2925,142],{"emptyLinePlaceholder":141},[87,2927,2928],{"class":89,"line":647},[87,2929,2930],{"class":173},"# docker build -t glog:v1 .\n",[87,2932,2933],{"class":89,"line":927},[87,2934,2935],{"class":173},"# docker run -p 6801:6801 glog:v1\n",[2124,2937,2938],{},"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":83,"searchDepth":104,"depth":145,"links":2940},[2941,2942,2945],{"id":2173,"depth":104,"text":2173},{"id":2179,"depth":104,"text":2179,"children":2943},[2944],{"id":2401,"depth":145,"text":2401},{"id":2446,"depth":104,"text":2446,"children":2946},[2947,2948,2949],{"id":2618,"depth":145,"text":2618},{"id":2757,"depth":145,"text":2757},{"id":2821,"depth":145,"text":2821},"解决 Go 语言跨平台编译问题，使用 Docker + musl 编译静态链接的可执行文件。","\u002Fposts\u002Fdocker-musl-build\u002Fimg\u002Fcover.svg",{},"\u002Fposts\u002Fdocker-musl-build","2025-03-27",{"title":2168,"description":2950},"Docker 实践笔记","posts\u002Fdocker-musl-build\u002Findex",[2959,2158,2960],"Docker","运维","YLtdOOc-gMVDSNjE-ntKXz-7hff-VtvMB2dB5RXNi1o",{"id":2963,"title":2964,"author":6,"body":2965,"description":3366,"difficulty":3367,"draft":2148,"extension":2149,"image":3368,"meta":3369,"navigation":141,"path":3370,"pinned":2148,"published":3371,"seo":3372,"series":2956,"seriesOrder":104,"stem":3373,"tags":3374,"updated":3371,"__hash__":3376},"posts\u002Fposts\u002Fdocker-user-start\u002Findex.md","Docker 构建时指定用户启动",{"type":8,"value":2966,"toc":3356},[2967,2970,2973,2976,2980,2983,3000,3004,3037,3041,3048,3098,3101,3316,3318,3321,3350,3353],[11,2968,2969],{"id":2969},"前言",[16,2971,2972],{},"我们在 Linux 使用应用时，往往不会直接使用 root 用户作为应用启动项，但 Dockerfile 默认就是 root 用户，导致挂载生成的文件路径往往是 root 权限，非 root 用户访问就很不方便。",[11,2974,2975],{"id":2975},"解决方案",[73,2977,2979],{"id":2978},"_1-安装用户管理工具","1. 安装用户管理工具",[16,2981,2982],{},"若镜像非自带用户管理：",[78,2984,2986],{"className":2470,"code":2985,"language":2472,"meta":83,"style":83},"# 权限管理\nRUN apk add --no-cache shadow\n",[30,2987,2988,2993],{"__ignoreMap":83},[87,2989,2990],{"class":89,"line":90},[87,2991,2992],{"class":173},"# 权限管理\n",[87,2994,2995,2997],{"class":89,"line":104},[87,2996,2532],{"class":126},[87,2998,2999],{"class":148}," apk add --no-cache shadow\n",[73,3001,3003],{"id":3002},"_2-创建用户并修改权限","2. 创建用户并修改权限",[78,3005,3007],{"className":2470,"code":3006,"language":2472,"meta":83,"style":83},"# 创建用户组和用户\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",[30,3008,3009,3014,3021,3025,3030],{"__ignoreMap":83},[87,3010,3011],{"class":89,"line":90},[87,3012,3013],{"class":173},"# 创建用户组和用户\n",[87,3015,3016,3018],{"class":89,"line":104},[87,3017,2532],{"class":126},[87,3019,3020],{"class":148}," addgroup -S \u003Capp_user_group> && adduser -S -G \u003Capp_user_group> \u003Capp_user>\n",[87,3022,3023],{"class":89,"line":145},[87,3024,142],{"emptyLinePlaceholder":141},[87,3026,3027],{"class":89,"line":164},[87,3028,3029],{"class":173},"# 修改目录权限\n",[87,3031,3032,3034],{"class":89,"line":177},[87,3033,2532],{"class":126},[87,3035,3036],{"class":148}," chown -R \u003Capp_user_group>:\u003Capp_user> \u002Fdata\n",[73,3038,3040],{"id":3039},"_3-指定用户启动","3. 指定用户启动",[16,3042,3043,3044,3047],{},"执行 docker 时加 ",[30,3045,3046],{},"--user"," 参数：",[78,3049,3051],{"className":80,"code":3050,"language":82,"meta":83,"style":83},"docker run -it --user \u003Capp_user> \u003Capp_image>:\u003Cimage_version>\n",[30,3052,3053],{"__ignoreMap":83},[87,3054,3055,3057,3059,3061,3064,3066,3069,3072,3074,3076,3079,3082,3084,3087,3090,3093,3096],{"class":89,"line":90},[87,3056,2194],{"class":93},[87,3058,2197],{"class":97},[87,3060,2200],{"class":415},[87,3062,3063],{"class":415}," --user",[87,3065,2693],{"class":126},[87,3067,3068],{"class":97},"app_use",[87,3070,3071],{"class":148},"r",[87,3073,2702],{"class":126},[87,3075,2693],{"class":126},[87,3077,3078],{"class":97},"app_imag",[87,3080,3081],{"class":148},"e",[87,3083,2702],{"class":126},[87,3085,3086],{"class":97},":",[87,3088,3089],{"class":126},"\u003C",[87,3091,3092],{"class":97},"image_versio",[87,3094,3095],{"class":148},"n",[87,3097,2749],{"class":126},[11,3099,3100],{"id":3100},"完整示例",[78,3102,3104],{"className":2470,"code":3103,"language":2472,"meta":83,"style":83},"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",[30,3105,3106,3112,3116,3121,3134,3138,3145,3149,3155,3160,3167,3172,3179,3183,3190,3194,3198,3205,3209,3216,3220,3227,3231,3244,3250,3254,3259,3267,3275,3280,3286,3295],{"__ignoreMap":83},[87,3107,3108,3110],{"class":89,"line":90},[87,3109,2479],{"class":126},[87,3111,2833],{"class":148},[87,3113,3114],{"class":89,"line":104},[87,3115,142],{"emptyLinePlaceholder":141},[87,3117,3118],{"class":89,"line":145},[87,3119,3120],{"class":173},"# 国内源\n",[87,3122,3123,3125,3128,3131],{"class":89,"line":164},[87,3124,2532],{"class":126},[87,3126,3127],{"class":148}," sed -i ",[87,3129,3130],{"class":97},"'s\u002Fdl-cdn.alpinelinux.org\u002Fmirrors.ustc.edu.cn\u002Fg'",[87,3132,3133],{"class":148}," \u002Fetc\u002Fapk\u002Frepositories\n",[87,3135,3136],{"class":89,"line":177},[87,3137,142],{"emptyLinePlaceholder":141},[87,3139,3140,3142],{"class":89,"line":189},[87,3141,2532],{"class":126},[87,3143,3144],{"class":148}," apk update --no-cache\n",[87,3146,3147],{"class":89,"line":201},[87,3148,2992],{"class":173},[87,3150,3151,3153],{"class":89,"line":211},[87,3152,2532],{"class":126},[87,3154,2999],{"class":148},[87,3156,3157],{"class":89,"line":228},[87,3158,3159],{"class":173},"# 调试\n",[87,3161,3162,3164],{"class":89,"line":390},[87,3163,2532],{"class":126},[87,3165,3166],{"class":148}," apk add --no-cache bash\n",[87,3168,3169],{"class":89,"line":396},[87,3170,3171],{"class":173},"# 设置时区\n",[87,3173,3174,3176],{"class":89,"line":419},[87,3175,2532],{"class":126},[87,3177,3178],{"class":148}," apk add --no-cache tzdata\n",[87,3180,3181],{"class":89,"line":617},[87,3182,142],{"emptyLinePlaceholder":141},[87,3184,3185,3187],{"class":89,"line":623},[87,3186,2390],{"class":126},[87,3188,3189],{"class":148}," TZ=Asia\u002FShanghai\n",[87,3191,3192],{"class":89,"line":629},[87,3193,142],{"emptyLinePlaceholder":141},[87,3195,3196],{"class":89,"line":647},[87,3197,3013],{"class":173},[87,3199,3200,3202],{"class":89,"line":927},[87,3201,2532],{"class":126},[87,3203,3204],{"class":148}," addgroup -S glog && adduser -S -G glog glog\n",[87,3206,3207],{"class":89,"line":932},[87,3208,142],{"emptyLinePlaceholder":141},[87,3210,3211,3213],{"class":89,"line":951},[87,3212,2506],{"class":126},[87,3214,3215],{"class":148}," .\u002Fglog_static_musl_1 \u002Fapp\u002F\n",[87,3217,3218],{"class":89,"line":956},[87,3219,142],{"emptyLinePlaceholder":141},[87,3221,3222,3224],{"class":89,"line":962},[87,3223,2532],{"class":126},[87,3225,3226],{"class":148}," mkdir \u002Fdata\n",[87,3228,3229],{"class":89,"line":996},[87,3230,142],{"emptyLinePlaceholder":141},[87,3232,3233,3235,3238,3241],{"class":89,"line":1033},[87,3234,2583],{"class":126},[87,3236,3237],{"class":148}," [ ",[87,3239,3240],{"class":97},"\"\u002Fdata\"",[87,3242,3243],{"class":148}," ]\n",[87,3245,3246,3248],{"class":89,"line":1039},[87,3247,2595],{"class":126},[87,3249,2586],{"class":148},[87,3251,3252],{"class":89,"line":1044},[87,3253,142],{"emptyLinePlaceholder":141},[87,3255,3256],{"class":89,"line":1059},[87,3257,3258],{"class":173},"# 修改权限\n",[87,3260,3262,3264],{"class":89,"line":3261},27,[87,3263,2532],{"class":126},[87,3265,3266],{"class":148}," chown -R glog:glog \u002Fdata\n",[87,3268,3270,3272],{"class":89,"line":3269},28,[87,3271,2532],{"class":126},[87,3273,3274],{"class":148}," chown -R glog:glog \u002Fapp\n",[87,3276,3278],{"class":89,"line":3277},29,[87,3279,142],{"emptyLinePlaceholder":141},[87,3281,3283],{"class":89,"line":3282},30,[87,3284,3285],{"class":173},"# 指定用户启动\n",[87,3287,3289,3292],{"class":89,"line":3288},31,[87,3290,3291],{"class":126},"USER",[87,3293,3294],{"class":148}," glog\n",[87,3296,3298,3300,3302,3305,3307,3309,3311,3314],{"class":89,"line":3297},32,[87,3299,2606],{"class":126},[87,3301,3237],{"class":148},[87,3303,3304],{"class":97},"\"\u002Fapp\u002Fglog_static_musl_1\"",[87,3306,354],{"class":148},[87,3308,2914],{"class":97},[87,3310,354],{"class":148},[87,3312,3313],{"class":97},"\".\u002Fconf.ini\"",[87,3315,3243],{"class":148},[11,3317,2041],{"id":2041},[16,3319,3320],{},"关键步骤：",[48,3322,3323,3329,3338,3344],{},[51,3324,3325,3328],{},[30,3326,3327],{},"apk add --no-cache shadow"," - 安装用户管理工具",[51,3330,3331,2077,3334,3337],{},[30,3332,3333],{},"addgroup",[30,3335,3336],{},"adduser"," - 创建用户",[51,3339,3340,3343],{},[30,3341,3342],{},"chown -R"," - 修改目录权限",[51,3345,3346,3349],{},[30,3347,3348],{},"USER \u003Cusername>"," - 指定运行用户",[16,3351,3352],{},"这样容器内的应用就不再以 root 身份运行，更加安全。",[2124,3354,3355],{},"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":83,"searchDepth":104,"depth":145,"links":3357},[3358,3359,3364,3365],{"id":2969,"depth":104,"text":2969},{"id":2975,"depth":104,"text":2975,"children":3360},[3361,3362,3363],{"id":2978,"depth":145,"text":2979},{"id":3002,"depth":145,"text":3003},{"id":3039,"depth":145,"text":3040},{"id":3100,"depth":104,"text":3100},{"id":2041,"depth":104,"text":2041},"解决 Docker 容器默认以 root 用户运行导致的权限问题，指定非 root 用户启动应用。","入门","\u002Fposts\u002Fdocker-user-start\u002Fimg\u002Fcover.svg",{},"\u002Fposts\u002Fdocker-user-start","2025-04-01",{"title":2964,"description":3366},"posts\u002Fdocker-user-start\u002Findex",[2959,2960,3375],"Linux","b4Uyw5ms87xr7NhpIkavHnNbT20oaEPtXz8k1w6UPj0",{"id":3378,"title":3379,"author":6,"body":3380,"description":3589,"difficulty":6,"draft":2148,"extension":2149,"image":3590,"meta":3591,"navigation":141,"path":3592,"pinned":2148,"published":3593,"seo":3594,"series":6,"seriesOrder":6,"stem":3595,"tags":3596,"updated":6,"__hash__":3598},"posts\u002Fposts\u002Fsveltekit-intro\u002Findex.md","SvelteKit 入门：为什么它比你想象的更简单",{"type":8,"value":3381,"toc":3579},[3382,3386,3389,3395,3398,3402,3409,3459,3463,3469,3477,3481,3484,3487,3557,3559,3570,3576],[11,3383,3385],{"id":3384},"什么是-sveltekit","什么是 SvelteKit？",[16,3387,3388],{},"SvelteKit 是 Svelte 的官方全栈框架，类似于 React 之于 Next.js 的关系。它提供了路由、SSR、SSG 等能力，让开发者可以快速构建现代 Web 应用。",[16,3390,3391],{},[2426,3392],{"alt":3393,"src":3394},"SvelteKit Logo","https:\u002F\u002Fraw.githubusercontent.com\u002Fsveltejs\u002Fbranding\u002Fmaster\u002Fsvelte-logo.svg",[11,3396,3397],{"id":3397},"核心特点",[73,3399,3401],{"id":3400},"_1-编译时框架","1. 编译时框架",[16,3403,3404,3405,3408],{},"Svelte 和 React\u002FVue 最大的区别在于：",[38,3406,3407],{},"它在编译阶段就把组件转成原生 JS","，运行时没有虚拟 DOM。",[78,3410,3414],{"className":3411,"code":3412,"language":3413,"meta":83,"style":83},"language-typescript shiki shiki-themes github-light github-dark","\u002F\u002F Svelte 组件（编译后变成直接的 DOM 操作）\nlet count = 0\nfunction increment() {\n  count += 1\n}\n","typescript",[30,3415,3416,3421,3434,3444,3455],{"__ignoreMap":83},[87,3417,3418],{"class":89,"line":90},[87,3419,3420],{"class":173},"\u002F\u002F Svelte 组件（编译后变成直接的 DOM 操作）\n",[87,3422,3423,3426,3429,3431],{"class":89,"line":104},[87,3424,3425],{"class":126},"let",[87,3427,3428],{"class":148}," count ",[87,3430,1671],{"class":126},[87,3432,3433],{"class":415}," 0\n",[87,3435,3436,3439,3442],{"class":89,"line":145},[87,3437,3438],{"class":126},"function",[87,3440,3441],{"class":93}," increment",[87,3443,789],{"class":148},[87,3445,3446,3449,3452],{"class":89,"line":164},[87,3447,3448],{"class":148},"  count ",[87,3450,3451],{"class":126},"+=",[87,3453,3454],{"class":415}," 1\n",[87,3456,3457],{"class":89,"line":177},[87,3458,1062],{"class":148},[73,3460,3462],{"id":3461},"_2-文件路由","2. 文件路由",[16,3464,3465,3468],{},[30,3466,3467],{},"src\u002Froutes\u002F"," 目录结构即 URL，不需要手动配置路由表：",[78,3470,3475],{"className":3471,"code":3473,"language":3474},[3472],"language-text","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","text",[30,3476,3473],{"__ignoreMap":83},[73,3478,3480],{"id":3479},"_3-全栈能力","3. 全栈能力",[16,3482,3483],{},"SvelteKit 内置了服务端渲染（SSR）、静态站点生成（SSG）、API 路由等功能，开箱即用。",[11,3485,3486],{"id":3486},"和其他框架对比",[2043,3488,3489,3505],{},[2046,3490,3491],{},[2049,3492,3493,3496,3499,3502],{},[2052,3494,3495],{},"特性",[2052,3497,3498],{},"SvelteKit",[2052,3500,3501],{},"Next.js",[2052,3503,3504],{},"Nuxt",[2059,3506,3507,3520,3533,3546],{},[2049,3508,3509,3512,3515,3518],{},[2064,3510,3511],{},"学习曲线",[2064,3513,3514],{},"低",[2064,3516,3517],{},"中",[2064,3519,3517],{},[2049,3521,3522,3525,3528,3531],{},[2064,3523,3524],{},"包体积",[2064,3526,3527],{},"小",[2064,3529,3530],{},"大",[2064,3532,3517],{},[2049,3534,3535,3538,3541,3544],{},[2064,3536,3537],{},"运行时开销",[2064,3539,3540],{},"无",[2064,3542,3543],{},"有",[2064,3545,3543],{},[2049,3547,3548,3551,3553,3555],{},[2064,3549,3550],{},"生态",[2064,3552,3527],{},[2064,3554,3530],{},[2064,3556,3530],{},[11,3558,2041],{"id":2041},[16,3560,3561,3562,3565,3566,3569],{},"如果你追求",[38,3563,3564],{},"简洁的代码","和",[38,3567,3568],{},"极致的性能","，SvelteKit 是一个非常值得尝试的选择。它的学习曲线平缓，写起来就像在写原生 HTML\u002FCSS\u002FJS。",[16,3571,3572],{},[2426,3573],{"alt":3574,"src":3575},"Svelte 编译过程","https:\u002F\u002Fsvelte.dev\u002Fsvelte-logo-horizontal.svg",[2124,3577,3578],{},"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":83,"searchDepth":104,"depth":145,"links":3580},[3581,3582,3587,3588],{"id":3384,"depth":104,"text":3385},{"id":3397,"depth":104,"text":3397,"children":3583},[3584,3585,3586],{"id":3400,"depth":145,"text":3401},{"id":3461,"depth":145,"text":3462},{"id":3479,"depth":145,"text":3480},{"id":3486,"depth":104,"text":3486},{"id":2041,"depth":104,"text":2041},"SvelteKit 是一个全栈 Svelte 框架，这篇文章带你了解它的核心概念和优势。","\u002Fposts\u002Fsveltekit-intro\u002Fimg\u002Fcover.jpg",{},"\u002Fposts\u002Fsveltekit-intro","2026-05-30",{"title":3379,"description":3589},"posts\u002Fsveltekit-intro\u002Findex",[3498,3597],"前端框架","vsEGyunpCWR7DiJAdyV1SPDfa57xXLITBI-lKM8UeGQ",{"id":3600,"title":3601,"author":6,"body":3602,"description":4001,"difficulty":3367,"draft":2148,"extension":2149,"image":4002,"meta":4003,"navigation":141,"path":4004,"pinned":141,"published":4005,"seo":4006,"series":4007,"seriesOrder":90,"stem":4008,"tags":4009,"updated":4011,"__hash__":4012},"posts\u002Fposts\u002Fhello-world\u002Findex.md","Hello World - 欢迎来到尼克的小窝",{"type":8,"value":3603,"toc":3992},[3604,3608,3615,3618,3621,3624,3698,3701,3704,3767,3770,3773,3836,3839,3904,3907,3910,3926,3933,3936,3967,3970,3981,3984,3989],[11,3605,3607],{"id":3606},"博客上线啦","博客上线啦！",[16,3609,3610,3611,3614],{},"Hello World！欢迎来到",[38,3612,3613],{},"尼克的小窝","，这是我的第一篇博客文章。",[16,3616,3617],{},"经过一段时间的折腾，这个基于 Nuxt 3 的个人博客终于上线了。在这里记录一下这个项目的技术选型和功能特性。",[11,3619,3620],{"id":3620},"技术栈",[16,3622,3623],{},"本站使用了以下技术：",[2043,3625,3626,3636],{},[2046,3627,3628],{},[2049,3629,3630,3633],{},[2052,3631,3632],{},"技术",[2052,3634,3635],{},"用途",[2059,3637,3638,3648,3658,3668,3678,3688],{},[2049,3639,3640,3645],{},[2064,3641,3642],{},[38,3643,3644],{},"Nuxt 3",[2064,3646,3647],{},"Vue 3 全栈框架，提供 SSG、路由、API 等能力",[2049,3649,3650,3655],{},[2064,3651,3652],{},[38,3653,3654],{},"TypeScript",[2064,3656,3657],{},"类型安全，提升开发体验",[2049,3659,3660,3665],{},[2064,3661,3662],{},[38,3663,3664],{},"Tailwind CSS v4",[2064,3666,3667],{},"原子化 CSS，快速构建 UI",[2049,3669,3670,3675],{},[2064,3671,3672],{},[38,3673,3674],{},"@nuxt\u002Fcontent",[2064,3676,3677],{},"Markdown 驱动的内容管理",[2049,3679,3680,3685],{},[2064,3681,3682],{},[38,3683,3684],{},"Shiki",[2064,3686,3687],{},"代码语法高亮",[2049,3689,3690,3695],{},[2064,3691,3692],{},[38,3693,3694],{},"Giscus",[2064,3696,3697],{},"基于 GitHub Discussions 的评论系统",[11,3699,3700],{"id":3700},"功能特性",[16,3702,3703],{},"目前已实现的功能：",[3705,3706,3707,3713,3719,3725,3731,3737,3743,3749,3755,3761],"ul",{},[51,3708,3709,3712],{},[38,3710,3711],{},"博客系统"," - Markdown 文章，支持代码高亮、目录导航、搜索",[51,3714,3715,3718],{},[38,3716,3717],{},"暗色模式"," - 跟随系统或手动切换",[51,3720,3721,3724],{},[38,3722,3723],{},"文章搜索"," - 支持标题、描述、正文、标签多维度搜索",[51,3726,3727,3730],{},[38,3728,3729],{},"标签系统"," - 文章标签分类，高频标签快速筛选",[51,3732,3733,3736],{},[38,3734,3735],{},"友链页面"," - 展示友情链接，支持申请",[51,3738,3739,3742],{},[38,3740,3741],{},"赞助页面"," - 微信收款码展示",[51,3744,3745,3748],{},[38,3746,3747],{},"封面制作"," - 在线生成封面图片工具",[51,3750,3751,3754],{},[38,3752,3753],{},"网易云热评"," - 首页随机展示音乐评论",[51,3756,3757,3760],{},[38,3758,3759],{},"访客统计"," - 不蒜子计数器，带数字滚动动画",[51,3762,3763,3766],{},[38,3764,3765],{},"SEO 优化"," - 自动生成站点地图、Open Graph 标签",[11,3768,3769],{"id":3769},"代码示例",[16,3771,3772],{},"本站的配置非常集中，一个文件搞定全站定制：",[78,3774,3776],{"className":3411,"code":3775,"language":3413,"meta":83,"style":83},"\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",[30,3777,3778,3783,3797,3807,3817,3827,3832],{"__ignoreMap":83},[87,3779,3780],{"class":89,"line":90},[87,3781,3782],{"class":173},"\u002F\u002F config\u002Fsite.ts\n",[87,3784,3785,3787,3790,3793,3795],{"class":89,"line":104},[87,3786,2268],{"class":126},[87,3788,3789],{"class":126}," const",[87,3791,3792],{"class":415}," siteConfig",[87,3794,1589],{"class":126},[87,3796,595],{"class":148},[87,3798,3799,3802,3805],{"class":89,"line":145},[87,3800,3801],{"class":148},"  siteName: ",[87,3803,3804],{"class":97},"'尼克的小窝'",[87,3806,294],{"class":148},[87,3808,3809,3812,3815],{"class":89,"line":164},[87,3810,3811],{"class":148},"  url: ",[87,3813,3814],{"class":97},"'https:\u002F\u002Fnixus.top\u002F'",[87,3816,294],{"class":148},[87,3818,3819,3822,3825],{"class":89,"line":177},[87,3820,3821],{"class":148},"  avatar: ",[87,3823,3824],{"class":97},"'https:\u002F\u002Fq2.qlogo.cn\u002Fheadimg_dl?dst_uin=xxx&spec=100'",[87,3826,294],{"class":148},[87,3828,3829],{"class":89,"line":189},[87,3830,3831],{"class":173},"  \u002F\u002F ... 改这一个文件，全站生效\n",[87,3833,3834],{"class":89,"line":201},[87,3835,1062],{"class":148},[16,3837,3838],{},"文章的 frontmatter 也很简洁：",[78,3840,3844],{"className":3841,"code":3842,"language":3843,"meta":83,"style":83},"language-yaml shiki shiki-themes github-light github-dark","---\ntitle: 文章标题\npublished: 2026-05-31T08:00:00\ndescription: 文章简介\ntags: ['Nuxt', 'TypeScript']\n---\n","yaml",[30,3845,3846,3851,3862,3872,3882,3900],{"__ignoreMap":83},[87,3847,3848],{"class":89,"line":90},[87,3849,3850],{"class":93},"---\n",[87,3852,3853,3857,3859],{"class":89,"line":104},[87,3854,3856],{"class":3855},"sbB4o","title",[87,3858,1025],{"class":148},[87,3860,3861],{"class":97},"文章标题\n",[87,3863,3864,3867,3869],{"class":89,"line":145},[87,3865,3866],{"class":3855},"published",[87,3868,1025],{"class":148},[87,3870,3871],{"class":415},"2026-05-31T08:00:00\n",[87,3873,3874,3877,3879],{"class":89,"line":164},[87,3875,3876],{"class":3855},"description",[87,3878,1025],{"class":148},[87,3880,3881],{"class":97},"文章简介\n",[87,3883,3884,3887,3890,3893,3895,3898],{"class":89,"line":177},[87,3885,3886],{"class":3855},"tags",[87,3888,3889],{"class":148},": [",[87,3891,3892],{"class":97},"'Nuxt'",[87,3894,354],{"class":148},[87,3896,3897],{"class":97},"'TypeScript'",[87,3899,2615],{"class":148},[87,3901,3902],{"class":89,"line":189},[87,3903,3850],{"class":93},[11,3905,3906],{"id":3906},"部署",[16,3908,3909],{},"本站使用 SSG 静态生成，构建后直接部署到静态托管即可：",[78,3911,3913],{"className":80,"code":3912,"language":82,"meta":83,"style":83},"npx nuxi generate\n",[30,3914,3915],{"__ignoreMap":83},[87,3916,3917,3920,3923],{"class":89,"line":90},[87,3918,3919],{"class":93},"npx",[87,3921,3922],{"class":97}," nuxi",[87,3924,3925],{"class":97}," generate\n",[16,3927,3928,3929,3932],{},"构建产物在 ",[30,3930,3931],{},".output\u002Fpublic"," 目录下，可以直接丢到 Nginx、Vercel、Netlify 等平台。",[11,3934,3935],{"id":3935},"后续计划",[3705,3937,3940,3949,3955,3961],{"className":3938},[3939],"contains-task-list",[51,3941,3944,3948],{"className":3942},[3943],"task-list-item",[3945,3946],"input",{"disabled":141,"type":3947},"checkbox"," RSS 订阅",[51,3950,3952,3954],{"className":3951},[3943],[3945,3953],{"disabled":141,"type":3947}," 文章目录优化",[51,3956,3958,3960],{"className":3957},[3943],[3945,3959],{"disabled":141,"type":3947}," 更多小工具",[51,3962,3964,3966],{"className":3963},[3943],[3945,3965],{"disabled":141,"type":3947}," 持续输出内容",[11,3968,3969],{"id":3969},"致谢",[16,3971,3972,3973,3980],{},"感谢 ",[3974,3975,3979],"a",{"href":3976,"rel":3977},"https:\u002F\u002F2x.nz",[3978],"nofollow","二叉树树"," 的开源项目，本站的 UI 设计参考了他的项目。",[3982,3983],"hr",{},[16,3985,3986],{},[38,3987,3988],{},"感谢阅读！如果觉得不错，欢迎点个 Star 或者留个评论 (☞ﾟヮﾟ)☞",[2124,3990,3991],{},"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":83,"searchDepth":104,"depth":145,"links":3993},[3994,3995,3996,3997,3998,3999,4000],{"id":3606,"depth":104,"text":3607},{"id":3620,"depth":104,"text":3620},{"id":3700,"depth":104,"text":3700},{"id":3769,"depth":104,"text":3769},{"id":3906,"depth":104,"text":3906},{"id":3935,"depth":104,"text":3935},{"id":3969,"depth":104,"text":3969},"博客上线啦！这篇文章介绍本站的技术架构、功能特性和搭建过程中的一些思考。","\u002Fposts\u002Fhello-world\u002Fimg\u002Fcover.jpg",{},"\u002Fposts\u002Fhello-world","2026-05-31T08:00:00",{"title":3601,"description":4001},"Nuxt 博客开发","posts\u002Fhello-world\u002Findex",[4010,3504],"博客","2026-05-31","s0QPHV3aEea6nZra0FNTkxjNO-kLBsrYzuTPbNDtFvI",{"id":4014,"title":4015,"author":6,"body":4016,"description":5175,"difficulty":2147,"draft":2148,"extension":2149,"image":5176,"meta":5177,"navigation":141,"path":5178,"pinned":2148,"published":5179,"seo":5180,"series":4007,"seriesOrder":104,"stem":5181,"tags":5182,"updated":4011,"__hash__":5185},"posts\u002Fposts\u002Fnuxt-content-lessons\u002Findex.md","Nuxt Content v3 踩坑记录：目录、图片与那些反直觉的设计",{"type":8,"value":4017,"toc":5165},[4018,4022,4025,4028,4032,4035,4045,4048,4055,4120,4127,4129,4136,4405,4415,4419,4422,4425,4450,4464,4467,4490,4496,4499,4502,4505,4510,4517,4742,4747,4791,4795,4798,4805,4808,4858,4864,5093,5103,5105,5156,5159,5162],[4019,4020,4021],"h1",{"id":4021},"背景",[16,4023,4024],{},"最近在用 Nuxt 3 + Nuxt Content v3 搭建一个个人博客，参考了一个 SvelteKit 项目（svaf）的架构。原以为换个框架只是语法差异，没想到在几个关键功能上踩了不少坑。",[16,4026,4027],{},"这篇文章记录了三个核心问题的排查过程和最终方案。",[4019,4029,4031],{"id":4030},"问题一目录导航toc只有-h2","问题一：目录导航（TOC）只有 h2",[11,4033,4034],{"id":4034},"现象",[16,4036,4037,4038,4041,4042,1178],{},"Nuxt Content 内置了 TOC 数据，挂在 ",[30,4039,4040],{},"post.body.toc"," 上。直接用它渲染目录，发现",[38,4043,4044],{},"只有 h2 标题，h3 全部丢失",[11,4046,4047],{"id":4047},"排查",[16,4049,4050,4051,4054],{},"查了官方文档，发现 ",[30,4052,4053],{},"build.markdown.toc.depth"," 可以控制深度：",[78,4056,4058],{"className":3411,"code":4057,"language":3413,"meta":83,"style":83},"\u002F\u002F nuxt.config.ts\ncontent: {\n  build: {\n    markdown: {\n      toc: { depth: 3 }  \u002F\u002F 包含 h3\n    }\n  }\n}\n",[30,4059,4060,4065,4072,4079,4086,4108,4112,4116],{"__ignoreMap":83},[87,4061,4062],{"class":89,"line":90},[87,4063,4064],{"class":173},"\u002F\u002F nuxt.config.ts\n",[87,4066,4067,4070],{"class":89,"line":104},[87,4068,4069],{"class":93},"content",[87,4071,1985],{"class":148},[87,4073,4074,4077],{"class":89,"line":145},[87,4075,4076],{"class":93},"  build",[87,4078,1985],{"class":148},[87,4080,4081,4084],{"class":89,"line":164},[87,4082,4083],{"class":93},"    markdown",[87,4085,1985],{"class":148},[87,4087,4088,4091,4094,4097,4099,4102,4105],{"class":89,"line":177},[87,4089,4090],{"class":93},"      toc",[87,4092,4093],{"class":148},": { ",[87,4095,4096],{"class":93},"depth",[87,4098,1025],{"class":148},[87,4100,4101],{"class":415},"3",[87,4103,4104],{"class":148}," }  ",[87,4106,4107],{"class":173},"\u002F\u002F 包含 h3\n",[87,4109,4110],{"class":89,"line":189},[87,4111,620],{"class":148},[87,4113,4114],{"class":89,"line":201},[87,4115,2034],{"class":148},[87,4117,4118],{"class":89,"line":211},[87,4119,1062],{"class":148},[16,4121,4122,4123,4126],{},"配置改了，清了缓存重启，",[38,4124,4125],{},"依然只有 h2","。这个配置似乎不起作用。",[11,4128,2446],{"id":2446},[16,4130,4131,4132,4135],{},"放弃依赖 Nuxt Content 的 toc，直接从 ",[30,4133,4134],{},"post.body"," 的 AST 里递归提取所有标题：",[78,4137,4139],{"className":3411,"code":4138,"language":3413,"meta":83,"style":83},"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",[30,4140,4141,4183,4202,4215,4220,4278,4307,4337,4362,4366,4390,4394,4401],{"__ignoreMap":83},[87,4142,4143,4145,4148,4150,4153,4155,4158,4160,4163,4165,4168,4171,4173,4176,4178,4180],{"class":89,"line":90},[87,4144,3438],{"class":126},[87,4146,4147],{"class":93}," extractHeadings",[87,4149,219],{"class":148},[87,4151,4152],{"class":341},"node",[87,4154,3086],{"class":126},[87,4156,4157],{"class":415}," any",[87,4159,354],{"class":148},[87,4161,4162],{"class":341},"list",[87,4164,3086],{"class":126},[87,4166,4167],{"class":93}," Heading",[87,4169,4170],{"class":148},"[] ",[87,4172,1671],{"class":126},[87,4174,4175],{"class":148}," [])",[87,4177,3086],{"class":126},[87,4179,4167],{"class":93},[87,4181,4182],{"class":148},"[] {\n",[87,4184,4185,4188,4190,4193,4196,4199],{"class":89,"line":104},[87,4186,4187],{"class":126},"  if",[87,4189,1817],{"class":148},[87,4191,4192],{"class":126},"!",[87,4194,4195],{"class":148},"node) ",[87,4197,4198],{"class":126},"return",[87,4200,4201],{"class":148}," list\n",[87,4203,4204,4206,4209,4212],{"class":89,"line":145},[87,4205,4187],{"class":126},[87,4207,4208],{"class":148}," (Array.",[87,4210,4211],{"class":93},"isArray",[87,4213,4214],{"class":148},"(node)) {\n",[87,4216,4217],{"class":89,"line":164},[87,4218,4219],{"class":173},"    \u002F\u002F minimark 格式：[\"h2\", {id: \"xxx\"}, \"文本\"]\n",[87,4221,4222,4224,4226,4229,4232,4234,4237,4240,4243,4246,4249,4252,4256,4259,4262,4265,4267,4270,4273,4275],{"class":89,"line":177},[87,4223,583],{"class":126},[87,4225,1817],{"class":148},[87,4227,4228],{"class":126},"typeof",[87,4230,4231],{"class":148}," node[",[87,4233,1380],{"class":415},[87,4235,4236],{"class":148},"] ",[87,4238,4239],{"class":126},"===",[87,4241,4242],{"class":97}," 'string'",[87,4244,4245],{"class":126}," &&",[87,4247,4248],{"class":97}," \u002F",[87,4250,4251],{"class":126},"^",[87,4253,4255],{"class":4254},"sPKmS","h",[87,4257,4258],{"class":415},"[1-6]",[87,4260,4261],{"class":126},"$",[87,4263,4264],{"class":97},"\u002F",[87,4266,348],{"class":148},[87,4268,4269],{"class":93},"test",[87,4271,4272],{"class":148},"(node[",[87,4274,1380],{"class":415},[87,4276,4277],{"class":148},"])) {\n",[87,4279,4280,4283,4286,4288,4291,4293,4295,4298,4301,4303,4305],{"class":89,"line":189},[87,4281,4282],{"class":126},"      const",[87,4284,4285],{"class":415}," level",[87,4287,1589],{"class":126},[87,4289,4290],{"class":93}," Number",[87,4292,4272],{"class":148},[87,4294,1380],{"class":415},[87,4296,4297],{"class":148},"].",[87,4299,4300],{"class":93},"charAt",[87,4302,219],{"class":148},[87,4304,1447],{"class":415},[87,4306,1690],{"class":148},[87,4308,4309,4311,4314,4316,4319,4322,4325,4327,4329,4332,4335],{"class":89,"line":201},[87,4310,4282],{"class":126},[87,4312,4313],{"class":415}," text",[87,4315,1589],{"class":126},[87,4317,4318],{"class":93}," extractText",[87,4320,4321],{"class":148},"(node.",[87,4323,4324],{"class":93},"slice",[87,4326,219],{"class":148},[87,4328,1345],{"class":415},[87,4330,4331],{"class":148},")).",[87,4333,4334],{"class":93},"trim",[87,4336,805],{"class":148},[87,4338,4339,4342,4345,4348,4350,4353,4356,4359],{"class":89,"line":211},[87,4340,4341],{"class":148},"      list.",[87,4343,4344],{"class":93},"push",[87,4346,4347],{"class":148},"({ id: node[",[87,4349,1447],{"class":415},[87,4351,4352],{"class":148},"]?.id ",[87,4354,4355],{"class":126},"||",[87,4357,4358],{"class":93}," slugify",[87,4360,4361],{"class":148},"(text), text, level })\n",[87,4363,4364],{"class":89,"line":228},[87,4365,620],{"class":148},[87,4367,4368,4371,4373,4375,4378,4381,4384,4387],{"class":89,"line":390},[87,4369,4370],{"class":126},"    for",[87,4372,1817],{"class":148},[87,4374,1575],{"class":126},[87,4376,4377],{"class":415}," child",[87,4379,4380],{"class":126}," of",[87,4382,4383],{"class":148}," node) ",[87,4385,4386],{"class":93},"extractHeadings",[87,4388,4389],{"class":148},"(child, list)\n",[87,4391,4392],{"class":89,"line":396},[87,4393,2034],{"class":148},[87,4395,4396,4399],{"class":89,"line":419},[87,4397,4398],{"class":126},"  return",[87,4400,4201],{"class":148},[87,4402,4403],{"class":89,"line":617},[87,4404,1062],{"class":148},[16,4406,4407,4408,4410,4411,4414],{},"关键点：Nuxt Content v3 的 body 使用 ",[38,4409,8],{}," 格式，标题节点是数组 ",[30,4412,4413],{},"[\"h2\", {id: \"xxx\"}, \"标题文本\"]","，不是对象。",[4019,4416,4418],{"id":4417},"问题二图片路径解析错误","问题二：图片路径解析错误",[11,4420,4034],{"id":4421},"现象-1",[16,4423,4424],{},"markdown 里用相对路径引用图片：",[78,4426,4430],{"className":4427,"code":4428,"language":4429,"meta":83,"style":83},"language-markdown shiki shiki-themes github-light github-dark","![SSHFS挂载效果](img\u002Fsshfs.avif)\n","markdown",[30,4431,4432],{"__ignoreMap":83},[87,4433,4434,4437,4441,4444,4448],{"class":89,"line":90},[87,4435,4436],{"class":148},"![",[87,4438,4440],{"class":4439},"smiIW","SSHFS挂载效果",[87,4442,4443],{"class":148},"](",[87,4445,4447],{"class":4446},"szbl0","img\u002Fsshfs.avif",[87,4449,231],{"class":148},[16,4451,4452,4453,4456,4457,4460,4461,1178],{},"HTML 输出的 ",[30,4454,4455],{},"\u003Cimg src=\"img\u002Fsshfs.avif\">"," 看起来没问题，但浏览器请求的却是 ",[30,4458,4459],{},"\u002Fposts\u002Fimg\u002Fsshfs.avif"," 而不是 ",[30,4462,4463],{},"\u002Fposts\u002Fsshfs\u002Fimg\u002Fsshfs.avif",[11,4465,4047],{"id":4466},"排查-1",[48,4468,4469,4476,4479,4485],{},[51,4470,4471,4472,4475],{},"检查了 ",[30,4473,4474],{},"\u003Cbase>"," 标签 —— 没有",[51,4477,4478],{},"检查了重定向 —— 没有",[51,4480,4481,4482,4484],{},"直接访问 ",[30,4483,4463],{}," —— 返回 200 OK",[51,4486,4481,4487,4489],{},[30,4488,4459],{}," —— 404",[16,4491,4492,4493,1178],{},"服务器路由没问题，问题是",[38,4494,4495],{},"浏览器解析相对路径时基于了错误的基准 URL",[16,4497,4498],{},"这可能和 Nuxt 的客户端路由有关：SPA 导航时，浏览器的\"当前页面\"和 Vue Router 的\"当前路由\"不同步，导致相对路径解析出错。",[11,4500,2446],{"id":4501},"最终方案-1",[16,4503,4504],{},"两步解决：",[16,4506,4507],{},[38,4508,4509],{},"第一步：自定义 ProseImg 组件",[16,4511,4512,4513,4516],{},"创建 ",[30,4514,4515],{},"components\u002Fcontent\u002FProseImg.vue","，覆盖 Nuxt Content 默认的图片渲染：",[78,4518,4522],{"className":4519,"code":4520,"language":4521,"meta":83,"style":83},"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",[30,4523,4524,4536,4550,4570,4586,4623,4658,4682,4686,4695,4699,4708,4734],{"__ignoreMap":83},[87,4525,4526,4528,4531,4534],{"class":89,"line":90},[87,4527,3089],{"class":148},[87,4529,4530],{"class":3855},"script",[87,4532,4533],{"class":93}," setup",[87,4535,2749],{"class":148},[87,4537,4538,4540,4543,4545,4548],{"class":89,"line":104},[87,4539,1575],{"class":126},[87,4541,4542],{"class":415}," route",[87,4544,1589],{"class":126},[87,4546,4547],{"class":93}," useRoute",[87,4549,805],{"class":148},[87,4551,4552,4554,4557,4559,4562,4565,4568],{"class":89,"line":145},[87,4553,1575],{"class":126},[87,4555,4556],{"class":415}," resolvedSrc",[87,4558,1589],{"class":126},[87,4560,4561],{"class":93}," computed",[87,4563,4564],{"class":148},"(() ",[87,4566,4567],{"class":126},"=>",[87,4569,595],{"class":148},[87,4571,4572,4574,4576,4578,4581,4583],{"class":89,"line":164},[87,4573,4187],{"class":126},[87,4575,1817],{"class":148},[87,4577,4192],{"class":126},[87,4579,4580],{"class":148},"props.src) ",[87,4582,4198],{"class":126},[87,4584,4585],{"class":97}," ''\n",[87,4587,4588,4590,4593,4596,4598,4601,4603,4605,4608,4610,4612,4615,4618,4620],{"class":89,"line":177},[87,4589,4187],{"class":126},[87,4591,4592],{"class":148}," (props.src.",[87,4594,4595],{"class":93},"startsWith",[87,4597,219],{"class":148},[87,4599,4600],{"class":97},"'\u002F'",[87,4602,1208],{"class":148},[87,4604,4355],{"class":126},[87,4606,4607],{"class":148}," props.src.",[87,4609,4595],{"class":93},[87,4611,219],{"class":148},[87,4613,4614],{"class":97},"'http'",[87,4616,4617],{"class":148},")) ",[87,4619,4198],{"class":126},[87,4621,4622],{"class":148}," props.src\n",[87,4624,4625,4628,4631,4633,4636,4639,4641,4643,4647,4649,4651,4653,4656],{"class":89,"line":189},[87,4626,4627],{"class":126},"  const",[87,4629,4630],{"class":415}," base",[87,4632,1589],{"class":126},[87,4634,4635],{"class":148}," route.path.",[87,4637,4638],{"class":93},"replace",[87,4640,219],{"class":148},[87,4642,4264],{"class":97},[87,4644,4646],{"class":4645},"sCnZR","\\\u002F",[87,4648,4261],{"class":126},[87,4650,4264],{"class":97},[87,4652,354],{"class":148},[87,4654,4655],{"class":97},"''",[87,4657,231],{"class":148},[87,4659,4660,4662,4665,4668,4671,4674,4676,4679],{"class":89,"line":201},[87,4661,4398],{"class":126},[87,4663,4664],{"class":97}," `${",[87,4666,4667],{"class":148},"base",[87,4669,4670],{"class":97},"}\u002F${",[87,4672,4673],{"class":148},"props",[87,4675,348],{"class":97},[87,4677,4678],{"class":148},"src",[87,4680,4681],{"class":97},"}`\n",[87,4683,4684],{"class":89,"line":211},[87,4685,422],{"class":148},[87,4687,4688,4691,4693],{"class":89,"line":228},[87,4689,4690],{"class":148},"\u003C\u002F",[87,4692,4530],{"class":3855},[87,4694,2749],{"class":148},[87,4696,4697],{"class":89,"line":390},[87,4698,142],{"emptyLinePlaceholder":141},[87,4700,4701,4703,4706],{"class":89,"line":396},[87,4702,3089],{"class":148},[87,4704,4705],{"class":3855},"template",[87,4707,2749],{"class":148},[87,4709,4710,4713,4715,4718,4720,4723,4726,4728,4731],{"class":89,"line":419},[87,4711,4712],{"class":148},"  \u003C",[87,4714,2426],{"class":3855},[87,4716,4717],{"class":93}," :src",[87,4719,1671],{"class":148},[87,4721,4722],{"class":97},"\"resolvedSrc\"",[87,4724,4725],{"class":93}," :alt",[87,4727,1671],{"class":148},[87,4729,4730],{"class":97},"\"alt\"",[87,4732,4733],{"class":148}," \u002F>\n",[87,4735,4736,4738,4740],{"class":89,"line":617},[87,4737,4690],{"class":148},[87,4739,4705],{"class":3855},[87,4741,2749],{"class":148},[16,4743,4744],{},[38,4745,4746],{},"第二步：启用 MDC prose 组件",[78,4748,4750],{"className":3411,"code":4749,"language":3413,"meta":83,"style":83},"\u002F\u002F nuxt.config.ts\nmdc: {\n  components: {\n    prose: true  \u002F\u002F 让 Nuxt Content 使用自定义 ProseImg\n  }\n}\n",[30,4751,4752,4756,4763,4770,4783,4787],{"__ignoreMap":83},[87,4753,4754],{"class":89,"line":90},[87,4755,4064],{"class":173},[87,4757,4758,4761],{"class":89,"line":104},[87,4759,4760],{"class":93},"mdc",[87,4762,1985],{"class":148},[87,4764,4765,4768],{"class":89,"line":145},[87,4766,4767],{"class":93},"  components",[87,4769,1985],{"class":148},[87,4771,4772,4775,4777,4780],{"class":89,"line":164},[87,4773,4774],{"class":93},"    prose",[87,4776,1025],{"class":148},[87,4778,4779],{"class":415},"true",[87,4781,4782],{"class":173},"  \u002F\u002F 让 Nuxt Content 使用自定义 ProseImg\n",[87,4784,4785],{"class":89,"line":177},[87,4786,2034],{"class":148},[87,4788,4789],{"class":89,"line":189},[87,4790,1062],{"class":148},[4019,4792,4794],{"id":4793},"问题三content-目录的图片如何服务","问题三：content 目录的图片如何服务",[11,4796,4034],{"id":4797},"现象-2",[16,4799,4800,4801,4804],{},"原站（SvelteKit）的图片放在 ",[30,4802,4803],{},"content\u002Fposts\u002F{slug}\u002Fimg\u002F"," 下，markdown 用相对路径引用，一切正常。Nuxt 项目里，content 目录的文件不会被当成静态资源服务。",[11,4806,4807],{"id":4807},"方案对比",[2043,4809,4810,4823],{},[2046,4811,4812],{},[2049,4813,4814,4817,4820],{},[2052,4815,4816],{},"方案",[2052,4818,4819],{},"优点",[2052,4821,4822],{},"缺点",[2059,4824,4825,4836,4847],{},[2049,4826,4827,4830,4833],{},[2064,4828,4829],{},"复制到 public\u002F",[2064,4831,4832],{},"简单",[2064,4834,4835],{},"文件重复，需同步",[2049,4837,4838,4841,4844],{},[2064,4839,4840],{},"Vite 插件拦截",[2064,4842,4843],{},"原站方案",[2064,4845,4846],{},"Nuxt 不直接支持",[2049,4848,4849,4852,4855],{},[2064,4850,4851],{},"Server Route",[2064,4853,4854],{},"不复制文件",[2064,4856,4857],{},"需要手动写路由",[16,4859,4860,4861,4863],{},"最终选择 ",[38,4862,4851],{},"，和原站的 Vite 插件思路一致：",[78,4865,4867],{"className":3411,"code":4866,"language":3413,"meta":83,"style":83},"\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",[30,4868,4869,4874,4900,4915,4979,4993,4997,5019,5045,5063,5082,5089],{"__ignoreMap":83},[87,4870,4871],{"class":89,"line":90},[87,4872,4873],{"class":173},"\u002F\u002F server\u002Froutes\u002Fposts\u002F[slug]\u002Fimg\u002F[...file].ts\n",[87,4875,4876,4878,4881,4884,4886,4889,4891,4894,4896,4898],{"class":89,"line":104},[87,4877,2268],{"class":126},[87,4879,4880],{"class":126}," default",[87,4882,4883],{"class":93}," defineEventHandler",[87,4885,219],{"class":148},[87,4887,4888],{"class":126},"async",[87,4890,1817],{"class":148},[87,4892,4893],{"class":341},"event",[87,4895,1208],{"class":148},[87,4897,4567],{"class":126},[87,4899,595],{"class":148},[87,4901,4902,4904,4907,4909,4912],{"class":89,"line":145},[87,4903,4627],{"class":126},[87,4905,4906],{"class":415}," url",[87,4908,1589],{"class":126},[87,4910,4911],{"class":93}," getRequestURL",[87,4913,4914],{"class":148},"(event)\n",[87,4916,4917,4919,4922,4924,4927,4930,4932,4934,4936,4938,4941,4943,4945,4948,4950,4953,4956,4959,4961,4963,4965,4967,4969,4971,4973,4975,4977],{"class":89,"line":164},[87,4918,4627],{"class":126},[87,4920,4921],{"class":415}," match",[87,4923,1589],{"class":126},[87,4925,4926],{"class":148}," url.pathname.",[87,4928,4929],{"class":93},"match",[87,4931,219],{"class":148},[87,4933,4264],{"class":97},[87,4935,4251],{"class":126},[87,4937,4646],{"class":4645},[87,4939,4940],{"class":4254},"posts",[87,4942,4646],{"class":4645},[87,4944,219],{"class":4254},[87,4946,4947],{"class":415},"[",[87,4949,4251],{"class":126},[87,4951,4952],{"class":415},"\u002F]",[87,4954,4955],{"class":126},"+",[87,4957,4958],{"class":4254},")",[87,4960,4646],{"class":4645},[87,4962,2426],{"class":4254},[87,4964,4646],{"class":4645},[87,4966,219],{"class":4254},[87,4968,348],{"class":415},[87,4970,4955],{"class":126},[87,4972,4958],{"class":4254},[87,4974,4261],{"class":126},[87,4976,4264],{"class":97},[87,4978,231],{"class":148},[87,4980,4981,4983,4985,4987,4990],{"class":89,"line":177},[87,4982,4187],{"class":126},[87,4984,1817],{"class":148},[87,4986,4192],{"class":126},[87,4988,4989],{"class":148},"match) ",[87,4991,4992],{"class":126},"return\n",[87,4994,4995],{"class":89,"line":189},[87,4996,142],{"emptyLinePlaceholder":141},[87,4998,4999,5001,5004,5007,5009,5012,5014,5016],{"class":89,"line":201},[87,5000,4627],{"class":126},[87,5002,5003],{"class":148}," [, ",[87,5005,5006],{"class":415},"slug",[87,5008,354],{"class":148},[87,5010,5011],{"class":415},"filename",[87,5013,4236],{"class":148},[87,5015,1671],{"class":126},[87,5017,5018],{"class":148}," match\n",[87,5020,5021,5023,5026,5028,5031,5033,5036,5039,5042],{"class":89,"line":211},[87,5022,4627],{"class":126},[87,5024,5025],{"class":415}," filePath",[87,5027,1589],{"class":126},[87,5029,5030],{"class":93}," resolve",[87,5032,219],{"class":148},[87,5034,5035],{"class":97},"'content\u002Fposts'",[87,5037,5038],{"class":148},", slug, ",[87,5040,5041],{"class":97},"'img'",[87,5043,5044],{"class":148},", filename)\n",[87,5046,5047,5049,5052,5054,5057,5060],{"class":89,"line":228},[87,5048,4627],{"class":126},[87,5050,5051],{"class":415}," data",[87,5053,1589],{"class":126},[87,5055,5056],{"class":126}," await",[87,5058,5059],{"class":93}," readFile",[87,5061,5062],{"class":148},"(filePath)\n",[87,5064,5065,5068,5071,5074,5076,5079],{"class":89,"line":390},[87,5066,5067],{"class":93},"  setResponseHeader",[87,5069,5070],{"class":148},"(event, ",[87,5072,5073],{"class":97},"'content-type'",[87,5075,354],{"class":148},[87,5077,5078],{"class":415},"MIME",[87,5080,5081],{"class":148},"[ext])\n",[87,5083,5084,5086],{"class":89,"line":396},[87,5085,4398],{"class":126},[87,5087,5088],{"class":148}," data\n",[87,5090,5091],{"class":89,"line":419},[87,5092,422],{"class":148},[16,5094,5095,5096,5098,5099,5102],{},"请求 ",[30,5097,4463],{}," → 读取 ",[30,5100,5101],{},"content\u002Fposts\u002Fsshfs\u002Fimg\u002Fsshfs.avif"," → 返回图片。",[4019,5104,2041],{"id":2041},[2043,5106,5107,5118],{},[2046,5108,5109],{},[2049,5110,5111,5113,5116],{},[2052,5112,1156],{},[2052,5114,5115],{},"根因",[2052,5117,4816],{},[2059,5119,5120,5134,5145],{},[2049,5121,5122,5125,5131],{},[2064,5123,5124],{},"TOC 只有 h2",[2064,5126,5127,5130],{},[30,5128,5129],{},"toc.depth"," 配置不生效",[2064,5132,5133],{},"从 body AST 手动提取",[2049,5135,5136,5139,5142],{},[2064,5137,5138],{},"图片路径错误",[2064,5140,5141],{},"SPA 路由导致相对路径解析基准错误",[2064,5143,5144],{},"ProseImg 组件 + mdc.prose 配置",[2049,5146,5147,5150,5153],{},[2064,5148,5149],{},"content 图片 404",[2064,5151,5152],{},"content 目录不暴露给浏览器",[2064,5154,5155],{},"Server Route 直接读取返回",[16,5157,5158],{},"Nuxt Content v3 的设计理念是\"图片放 public\u002F，用绝对路径\"。如果想像 SvelteKit 项目那样把图片和文章放一起，需要额外做不少工作。",[16,5160,5161],{},"不过一旦这些坑都踩过，整体开发体验还是很流畅的。Nuxt 的生态系统、自动导入、模块系统都比 SvelteKit 成熟很多。",[2124,5163,5164],{},"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":83,"searchDepth":104,"depth":145,"links":5166},[5167,5168,5169,5170,5171,5172,5173,5174],{"id":4034,"depth":104,"text":4034},{"id":4047,"depth":104,"text":4047},{"id":2446,"depth":104,"text":2446},{"id":4421,"depth":104,"text":4034},{"id":4466,"depth":104,"text":4047},{"id":4501,"depth":104,"text":2446},{"id":4797,"depth":104,"text":4034},{"id":4807,"depth":104,"text":4807},"从 SvelteKit 迁移到 Nuxt 3 的过程中，遇到了不少 Nuxt Content v3 的坑。这篇文章记录了目录导航、图片路径、ProseImg 组件等问题的排查和解决过程。","\u002Fposts\u002Fnuxt-content-lessons\u002Fimg\u002Fcover.jpg",{},"\u002Fposts\u002Fnuxt-content-lessons","2026-05-31T14:00:00",{"title":4015,"description":5175},"posts\u002Fnuxt-content-lessons\u002Findex",[3504,5183,5184],"踩坑","Nuxt Content","vbSt6ND1z7xt7L8FxgZ8Qe3EdxV9InsRerN3E23GC7s",{"id":5187,"title":5188,"author":5189,"body":5190,"description":5460,"difficulty":2147,"draft":2148,"extension":2149,"image":5461,"meta":5462,"navigation":141,"path":5463,"pinned":2148,"published":5464,"seo":5465,"series":4007,"seriesOrder":145,"stem":5466,"tags":5467,"updated":4011,"__hash__":5470},"posts\u002Fposts\u002Fnuxt-tailwind-underline-fix\u002Findex.md","Nuxt 3 + Tailwind CSS 客户端导航链接下划线闪烁问题","Nixus",{"type":8,"value":5191,"toc":5453},[5192,5195,5198,5213,5216,5219,5222,5263,5274,5281,5303,5310,5314,5317,5320,5322,5325,5348,5359,5415,5420,5433,5435,5441,5450],[11,5193,5194],{"id":5194},"问题描述",[16,5196,5197],{},"在 Nuxt 3 项目中使用 Tailwind CSS 时，发现一个奇怪的现象：",[3705,5199,5200,5210],{},[51,5201,5202,5203,5206,5207],{},"首次通过客户端导航（点击 ",[30,5204,5205],{},"\u003CNuxtLink>","）跳转到某个页面时，",[38,5208,5209],{},"所有链接文字都会出现下划线",[51,5211,5212],{},"手动刷新页面后，下划线消失，样式恢复正常",[16,5214,5215],{},"这个问题在开发模式和生产构建后都会出现。",[11,5217,5218],{"id":5218},"原因分析",[16,5220,5221],{},"Tailwind CSS 的 Preflight（基于 modern-normalize）会重置链接样式：",[78,5223,5227],{"className":5224,"code":5225,"language":5226,"meta":83,"style":83},"language-css shiki shiki-themes github-light github-dark","a {\n  color: inherit;\n  text-decoration: inherit;\n}\n","css",[30,5228,5229,5235,5248,5259],{"__ignoreMap":83},[87,5230,5231,5233],{"class":89,"line":90},[87,5232,3974],{"class":3855},[87,5234,595],{"class":148},[87,5236,5237,5240,5242,5245],{"class":89,"line":104},[87,5238,5239],{"class":415},"  color",[87,5241,1025],{"class":148},[87,5243,5244],{"class":415},"inherit",[87,5246,5247],{"class":148},";\n",[87,5249,5250,5253,5255,5257],{"class":89,"line":145},[87,5251,5252],{"class":415},"  text-decoration",[87,5254,1025],{"class":148},[87,5256,5244],{"class":415},[87,5258,5247],{"class":148},[87,5260,5261],{"class":89,"line":164},[87,5262,1062],{"class":148},[16,5264,5265,5266,5269,5270,5273],{},"正常情况下，这会让链接继承父元素的 ",[30,5267,5268],{},"text-decoration","（通常是 ",[30,5271,5272],{},"none","），从而去掉浏览器默认的下划线。",[16,5275,5276,5277,5280],{},"问题出在 ",[38,5278,5279],{},"Nuxt 的客户端导航机制","：",[48,5282,5283,5289,5292,5297,5300],{},[51,5284,5285,5286,5288],{},"用户点击 ",[30,5287,5205],{},"，Vue Router 拦截导航",[51,5290,5291],{},"新页面组件异步加载并渲染",[51,5293,5294],{},[38,5295,5296],{},"在 CSS 完全加载\u002F应用之前，页面已经渲染了",[51,5298,5299],{},"此时浏览器使用默认样式（链接带下划线）",[51,5301,5302],{},"CSS 加载完成后，下划线消失",[16,5304,5305,5306,5309],{},"这就是典型的 ",[38,5307,5308],{},"FOUC（Flash of Unstyled Content）"," 问题。",[11,5311,5313],{"id":5312},"为什么刷新就正常了","为什么刷新就正常了？",[16,5315,5316],{},"刷新页面时，浏览器会等待所有 CSS 加载完成后再渲染页面（SSR 模式下 HTML 和 CSS 是一起返回的）。所以刷新后样式是正确的。",[16,5318,5319],{},"而客户端导航是 JavaScript 驱动的，CSS 的加载和页面的渲染是异步的，就出现了时序差。",[11,5321,2975],{"id":2975},[16,5323,5324],{},"尝试了多种方案：",[3705,5326,5327,5333,5339,5345],{},[51,5328,5329,5332],{},[30,5330,5331],{},"@layer base"," 覆盖 — 无效，CSS layers 的优先级问题",[51,5334,5335,5338],{},[30,5336,5337],{},"corePlugins.preflight: false"," — 无效，且破坏其他基础样式",[51,5340,5341,5344],{},[30,5342,5343],{},"features.inlineStyles: true"," — 无效",[51,5346,5347],{},"页面过渡动画 — 只是掩盖，不能根治",[16,5349,5350,5351,5354,5355,5358],{},"最终有效的方案是在 ",[30,5352,5353],{},"app.vue"," 中用 ",[30,5356,5357],{},"!important"," 强制覆盖：",[78,5360,5362],{"className":4519,"code":5361,"language":4521,"meta":83,"style":83},"\u003Cstyle>\na:not(.prose a) {\n  text-decoration-line: none !important;\n}\n\u003C\u002Fstyle>\n",[30,5363,5364,5372,5389,5403,5407],{"__ignoreMap":83},[87,5365,5366,5368,5370],{"class":89,"line":90},[87,5367,3089],{"class":148},[87,5369,2124],{"class":3855},[87,5371,2749],{"class":148},[87,5373,5374,5376,5379,5381,5384,5387],{"class":89,"line":104},[87,5375,3974],{"class":3855},[87,5377,5378],{"class":93},":not",[87,5380,219],{"class":148},[87,5382,5383],{"class":93},".prose",[87,5385,5386],{"class":3855}," a",[87,5388,387],{"class":148},[87,5390,5391,5394,5396,5398,5401],{"class":89,"line":145},[87,5392,5393],{"class":415},"  text-decoration-line",[87,5395,1025],{"class":148},[87,5397,5272],{"class":415},[87,5399,5400],{"class":126}," !important",[87,5402,5247],{"class":148},[87,5404,5405],{"class":89,"line":164},[87,5406,1062],{"class":148},[87,5408,5409,5411,5413],{"class":89,"line":177},[87,5410,4690],{"class":148},[87,5412,2124],{"class":3855},[87,5414,2749],{"class":148},[16,5416,5417],{},[38,5418,5419],{},"关键点：",[3705,5421,5422,5427],{},[51,5423,5424,5426],{},[30,5425,5357],{}," 是为了对抗 Tailwind CSS layers 的优先级",[51,5428,5429,5432],{},[30,5430,5431],{},":not(.prose a)"," 排除文章正文区域，保留文章内链接的下划线（可读性需要）",[11,5434,2041],{"id":2041},[16,5436,5437,5438,5440],{},"这是 Nuxt 3 + Tailwind CSS 的一个已知问题，本质上是 CSS 加载时序导致的 FOUC。",[30,5439,5357],{}," 虽然不够优雅，但在这个场景下是务实的解决方案。",[16,5442,5443,5444,5449],{},"如果你有更好的方案，欢迎在 ",[3974,5445,5448],{"href":5446,"rel":5447},"https:\u002F\u002Fgithub.com\u002Fwishesl\u002FSuBlog\u002Fissues",[3978],"Issues"," 提出！",[2124,5451,5452],{},"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":83,"searchDepth":104,"depth":145,"links":5454},[5455,5456,5457,5458,5459],{"id":5194,"depth":104,"text":5194},{"id":5218,"depth":104,"text":5218},{"id":5312,"depth":104,"text":5313},{"id":2975,"depth":104,"text":2975},{"id":2041,"depth":104,"text":2041},"记录 Nuxt 3 配合 Tailwind CSS 时，客户端导航导致链接文字出现下划线闪烁的问题及解决方案。","\u002Fposts\u002Fnuxt-tailwind-underline-fix\u002Fimg\u002Fcover.png",{},"\u002Fposts\u002Fnuxt-tailwind-underline-fix","2026-05-31T18:47:00",{"title":5188,"description":5460},"posts\u002Fnuxt-tailwind-underline-fix\u002Findex",[3504,5468,5469,5183],"Tailwind CSS","CSS","yMReEaosecX7QtrQkKoqINdTG0f9PrvEW4j27IgQF7g",{"id":4,"title":5,"author":6,"body":5472,"description":2146,"difficulty":2147,"draft":2148,"extension":2149,"image":2150,"meta":7195,"navigation":141,"path":2152,"pinned":2148,"published":2153,"seo":7196,"series":2155,"seriesOrder":90,"stem":2156,"tags":7197,"updated":2163,"__hash__":2164},{"type":8,"value":5473,"toc":7176},[5474,5476,5478,5480,5482,5486,5490,5492,5494,5508,5510,5512,5532,5534,5612,5620,5622,5754,5756,5950,5952,5958,6042,6046,6048,6288,6290,6358,6360,6362,6364,6370,6372,6376,6694,6860,6862,6908,6910,7046,7048,7050,7110,7112,7168,7174],[11,5475,14],{"id":13},[16,5477,18],{},[16,5479,21],{},[11,5481,25],{"id":24},[16,5483,28,5484,33],{},[30,5485,32],{},[16,5487,36,5488],{},[38,5489,40],{},[11,5491,43],{"id":43},[16,5493,46],{},[48,5495,5496,5500,5504],{},[51,5497,5498,56],{},[38,5499,55],{},[51,5501,5502,62],{},[38,5503,61],{},[51,5505,5506,68],{},[38,5507,67],{},[11,5509,71],{"id":71},[73,5511,76],{"id":75},[78,5513,5514],{"className":80,"code":81,"language":82,"meta":83,"style":83},[30,5515,5516,5524],{"__ignoreMap":83},[87,5517,5518,5520,5522],{"class":89,"line":90},[87,5519,94],{"class":93},[87,5521,98],{"class":97},[87,5523,101],{"class":97},[87,5525,5526,5528,5530],{"class":89,"line":104},[87,5527,94],{"class":93},[87,5529,98],{"class":97},[87,5531,111],{"class":97},[73,5533,115],{"id":114},[78,5535,5536],{"className":118,"code":119,"language":94,"meta":83,"style":83},[30,5537,5538,5548,5552,5564,5572,5580,5588,5596,5608],{"__ignoreMap":83},[87,5539,5540,5542,5544,5546],{"class":89,"line":90},[87,5541,127],{"class":126},[87,5543,130],{"class":97},[87,5545,133],{"class":93},[87,5547,136],{"class":97},[87,5549,5550],{"class":89,"line":104},[87,5551,142],{"emptyLinePlaceholder":141},[87,5553,5554,5556,5558,5560,5562],{"class":89,"line":145},[87,5555,149],{"class":148},[87,5557,152],{"class":126},[87,5559,155],{"class":148},[87,5561,158],{"class":93},[87,5563,161],{"class":148},[87,5565,5566,5568,5570],{"class":89,"line":164},[87,5567,167],{"class":97},[87,5569,170],{"class":148},[87,5571,174],{"class":173},[87,5573,5574,5576,5578],{"class":89,"line":177},[87,5575,180],{"class":97},[87,5577,183],{"class":148},[87,5579,186],{"class":173},[87,5581,5582,5584,5586],{"class":89,"line":189},[87,5583,192],{"class":148},[87,5585,195],{"class":93},[87,5587,198],{"class":148},[87,5589,5590,5592,5594],{"class":89,"line":201},[87,5591,192],{"class":148},[87,5593,206],{"class":93},[87,5595,198],{"class":148},[87,5597,5598,5600,5602,5604,5606],{"class":89,"line":211},[87,5599,192],{"class":148},[87,5601,216],{"class":93},[87,5603,219],{"class":148},[87,5605,222],{"class":97},[87,5607,225],{"class":148},[87,5609,5610],{"class":89,"line":228},[87,5611,231],{"class":148},[16,5613,5614,236,5616,240,5618,244],{},[30,5615,158],{},[30,5617,239],{},[30,5619,243],{},[73,5621,248],{"id":247},[78,5623,5624],{"className":118,"code":251,"language":94,"meta":83,"style":83},[30,5625,5626,5636,5640,5644,5660,5672,5676,5680,5684,5730,5734,5750],{"__ignoreMap":83},[87,5627,5628,5630,5632,5634],{"class":89,"line":90},[87,5629,127],{"class":126},[87,5631,130],{"class":97},[87,5633,262],{"class":93},[87,5635,136],{"class":97},[87,5637,5638],{"class":89,"line":104},[87,5639,142],{"emptyLinePlaceholder":141},[87,5641,5642],{"class":89,"line":145},[87,5643,273],{"class":173},[87,5645,5646,5648,5650,5652,5654,5656,5658],{"class":89,"line":164},[87,5647,278],{"class":148},[87,5649,152],{"class":126},[87,5651,283],{"class":148},[87,5653,286],{"class":93},[87,5655,219],{"class":148},[87,5657,291],{"class":97},[87,5659,294],{"class":148},[87,5661,5662,5664,5666,5668,5670],{"class":89,"line":177},[87,5663,299],{"class":148},[87,5665,302],{"class":93},[87,5667,219],{"class":148},[87,5669,307],{"class":97},[87,5671,225],{"class":148},[87,5673,5674],{"class":89,"line":189},[87,5675,231],{"class":148},[87,5677,5678],{"class":89,"line":201},[87,5679,142],{"emptyLinePlaceholder":141},[87,5681,5682],{"class":89,"line":211},[87,5683,322],{"class":173},[87,5685,5686,5688,5690,5692,5694,5696,5698,5700,5702,5704,5706,5708,5710,5712,5714,5716,5718,5720,5722,5724,5726,5728],{"class":89,"line":228},[87,5687,327],{"class":148},[87,5689,330],{"class":93},[87,5691,333],{"class":148},[87,5693,336],{"class":126},[87,5695,219],{"class":148},[87,5697,342],{"class":341},[87,5699,345],{"class":93},[87,5701,348],{"class":148},[87,5703,351],{"class":93},[87,5705,354],{"class":148},[87,5707,357],{"class":341},[87,5709,360],{"class":93},[87,5711,348],{"class":148},[87,5713,365],{"class":93},[87,5715,368],{"class":148},[87,5717,371],{"class":126},[87,5719,374],{"class":93},[87,5721,348],{"class":148},[87,5723,379],{"class":93},[87,5725,354],{"class":148},[87,5727,384],{"class":126},[87,5729,387],{"class":148},[87,5731,5732],{"class":89,"line":390},[87,5733,393],{"class":173},[87,5735,5736,5738,5740,5742,5744,5746,5748],{"class":89,"line":396},[87,5737,399],{"class":126},[87,5739,283],{"class":148},[87,5741,404],{"class":93},[87,5743,219],{"class":148},[87,5745,409],{"class":97},[87,5747,412],{"class":148},[87,5749,416],{"class":415},[87,5751,5752],{"class":89,"line":419},[87,5753,422],{"class":148},[16,5755,425],{},[78,5757,5758],{"className":118,"code":428,"language":94,"meta":83,"style":83},[30,5759,5760,5776,5788,5800,5808,5820,5824,5828,5832,5878,5894,5906,5922,5926,5930,5946],{"__ignoreMap":83},[87,5761,5762,5764,5766,5768,5770,5772,5774],{"class":89,"line":90},[87,5763,435],{"class":148},[87,5765,152],{"class":126},[87,5767,283],{"class":148},[87,5769,286],{"class":93},[87,5771,219],{"class":148},[87,5773,446],{"class":97},[87,5775,294],{"class":148},[87,5777,5778,5780,5782,5784,5786],{"class":89,"line":104},[87,5779,299],{"class":148},[87,5781,302],{"class":93},[87,5783,219],{"class":148},[87,5785,459],{"class":97},[87,5787,225],{"class":148},[87,5789,5790,5792,5794,5796,5798],{"class":89,"line":145},[87,5791,299],{"class":148},[87,5793,468],{"class":93},[87,5795,219],{"class":148},[87,5797,473],{"class":97},[87,5799,294],{"class":148},[87,5801,5802,5804,5806],{"class":89,"line":164},[87,5803,480],{"class":148},[87,5805,483],{"class":93},[87,5807,198],{"class":148},[87,5809,5810,5812,5814,5816,5818],{"class":89,"line":177},[87,5811,480],{"class":148},[87,5813,492],{"class":93},[87,5815,219],{"class":148},[87,5817,497],{"class":97},[87,5819,225],{"class":148},[87,5821,5822],{"class":89,"line":189},[87,5823,504],{"class":148},[87,5825,5826],{"class":89,"line":201},[87,5827,231],{"class":148},[87,5829,5830],{"class":89,"line":211},[87,5831,142],{"emptyLinePlaceholder":141},[87,5833,5834,5836,5838,5840,5842,5844,5846,5848,5850,5852,5854,5856,5858,5860,5862,5864,5866,5868,5870,5872,5874,5876],{"class":89,"line":228},[87,5835,327],{"class":148},[87,5837,330],{"class":93},[87,5839,521],{"class":148},[87,5841,336],{"class":126},[87,5843,219],{"class":148},[87,5845,342],{"class":341},[87,5847,345],{"class":93},[87,5849,348],{"class":148},[87,5851,351],{"class":93},[87,5853,354],{"class":148},[87,5855,357],{"class":341},[87,5857,360],{"class":93},[87,5859,348],{"class":148},[87,5861,365],{"class":93},[87,5863,368],{"class":148},[87,5865,371],{"class":126},[87,5867,374],{"class":93},[87,5869,348],{"class":148},[87,5871,379],{"class":93},[87,5873,354],{"class":148},[87,5875,384],{"class":126},[87,5877,387],{"class":148},[87,5879,5880,5882,5884,5886,5888,5890,5892],{"class":89,"line":390},[87,5881,564],{"class":148},[87,5883,152],{"class":126},[87,5885,569],{"class":148},[87,5887,572],{"class":93},[87,5889,219],{"class":148},[87,5891,473],{"class":97},[87,5893,231],{"class":148},[87,5895,5896,5898,5900,5902,5904],{"class":89,"line":396},[87,5897,583],{"class":126},[87,5899,586],{"class":148},[87,5901,589],{"class":126},[87,5903,592],{"class":415},[87,5905,595],{"class":148},[87,5907,5908,5910,5912,5914,5916,5918,5920],{"class":89,"line":419},[87,5909,600],{"class":126},[87,5911,283],{"class":148},[87,5913,605],{"class":93},[87,5915,219],{"class":148},[87,5917,610],{"class":97},[87,5919,412],{"class":148},[87,5921,416],{"class":415},[87,5923,5924],{"class":89,"line":617},[87,5925,620],{"class":148},[87,5927,5928],{"class":89,"line":623},[87,5929,626],{"class":173},[87,5931,5932,5934,5936,5938,5940,5942,5944],{"class":89,"line":629},[87,5933,399],{"class":126},[87,5935,283],{"class":148},[87,5937,404],{"class":93},[87,5939,219],{"class":148},[87,5941,640],{"class":97},[87,5943,412],{"class":148},[87,5945,416],{"class":415},[87,5947,5948],{"class":89,"line":647},[87,5949,422],{"class":148},[73,5951,653],{"id":652},[16,5953,656,5954,660,5956,664],{},[30,5955,659],{},[30,5957,663],{},[78,5959,5960],{"className":118,"code":667,"language":94,"meta":83,"style":83},[30,5961,5962,5972,5976,5988,6000,6014,6018,6022,6026],{"__ignoreMap":83},[87,5963,5964,5966,5968,5970],{"class":89,"line":90},[87,5965,127],{"class":126},[87,5967,130],{"class":97},[87,5969,133],{"class":93},[87,5971,136],{"class":97},[87,5973,5974],{"class":89,"line":104},[87,5975,142],{"emptyLinePlaceholder":141},[87,5977,5978,5980,5982,5984,5986],{"class":89,"line":145},[87,5979,688],{"class":148},[87,5981,152],{"class":126},[87,5983,155],{"class":148},[87,5985,695],{"class":93},[87,5987,698],{"class":148},[87,5989,5990,5992,5994,5996,5998],{"class":89,"line":164},[87,5991,192],{"class":148},[87,5993,705],{"class":93},[87,5995,219],{"class":148},[87,5997,710],{"class":97},[87,5999,225],{"class":148},[87,6001,6002,6004,6006,6008,6010,6012],{"class":89,"line":177},[87,6003,192],{"class":148},[87,6005,719],{"class":93},[87,6007,219],{"class":148},[87,6009,724],{"class":415},[87,6011,371],{"class":126},[87,6013,729],{"class":148},[87,6015,6016],{"class":89,"line":189},[87,6017,231],{"class":148},[87,6019,6020],{"class":89,"line":201},[87,6021,142],{"emptyLinePlaceholder":141},[87,6023,6024],{"class":89,"line":211},[87,6025,742],{"class":173},[87,6027,6028,6030,6032,6034,6036,6038,6040],{"class":89,"line":228},[87,6029,747],{"class":148},[87,6031,750],{"class":93},[87,6033,219],{"class":148},[87,6035,710],{"class":97},[87,6037,757],{"class":148},[87,6039,760],{"class":93},[87,6041,763],{"class":148},[16,6043,766,6044,770],{},[30,6045,769],{},[73,6047,774],{"id":773},[78,6049,6050],{"className":118,"code":777,"language":94,"meta":83,"style":83},[30,6051,6052,6060,6072,6076,6080,6100,6108,6116,6120,6124,6128,6134,6138,6142,6154,6166,6180,6184,6200,6204,6208,6236,6264,6268,6272,6284],{"__ignoreMap":83},[87,6053,6054,6056,6058],{"class":89,"line":90},[87,6055,336],{"class":126},[87,6057,786],{"class":93},[87,6059,789],{"class":148},[87,6061,6062,6064,6066,6068,6070],{"class":89,"line":104},[87,6063,794],{"class":148},[87,6065,152],{"class":126},[87,6067,799],{"class":148},[87,6069,802],{"class":93},[87,6071,805],{"class":148},[87,6073,6074],{"class":89,"line":145},[87,6075,142],{"emptyLinePlaceholder":141},[87,6077,6078],{"class":89,"line":164},[87,6079,814],{"class":173},[87,6081,6082,6084,6086,6088,6090,6092,6094,6096,6098],{"class":89,"line":177},[87,6083,819],{"class":148},[87,6085,152],{"class":126},[87,6087,155],{"class":148},[87,6089,158],{"class":93},[87,6091,219],{"class":148},[87,6093,830],{"class":97},[87,6095,354],{"class":148},[87,6097,835],{"class":97},[87,6099,294],{"class":148},[87,6101,6102,6104,6106],{"class":89,"line":189},[87,6103,842],{"class":148},[87,6105,195],{"class":93},[87,6107,198],{"class":148},[87,6109,6110,6112,6114],{"class":89,"line":201},[87,6111,842],{"class":148},[87,6113,206],{"class":93},[87,6115,198],{"class":148},[87,6117,6118],{"class":89,"line":211},[87,6119,859],{"class":148},[87,6121,6122],{"class":89,"line":228},[87,6123,142],{"emptyLinePlaceholder":141},[87,6125,6126],{"class":89,"line":390},[87,6127,868],{"class":173},[87,6129,6130,6132],{"class":89,"line":396},[87,6131,873],{"class":93},[87,6133,876],{"class":148},[87,6135,6136],{"class":89,"line":419},[87,6137,142],{"emptyLinePlaceholder":141},[87,6139,6140],{"class":89,"line":617},[87,6141,885],{"class":173},[87,6143,6144,6146,6148,6150,6152],{"class":89,"line":623},[87,6145,890],{"class":148},[87,6147,152],{"class":126},[87,6149,155],{"class":148},[87,6151,695],{"class":93},[87,6153,698],{"class":148},[87,6155,6156,6158,6160,6162,6164],{"class":89,"line":629},[87,6157,842],{"class":148},[87,6159,705],{"class":93},[87,6161,219],{"class":148},[87,6163,710],{"class":97},[87,6165,225],{"class":148},[87,6167,6168,6170,6172,6174,6176,6178],{"class":89,"line":647},[87,6169,842],{"class":148},[87,6171,719],{"class":93},[87,6173,219],{"class":148},[87,6175,724],{"class":415},[87,6177,371],{"class":126},[87,6179,729],{"class":148},[87,6181,6182],{"class":89,"line":927},[87,6183,859],{"class":148},[87,6185,6186,6188,6190,6192,6194,6196,6198],{"class":89,"line":932},[87,6187,935],{"class":148},[87,6189,750],{"class":93},[87,6191,219],{"class":148},[87,6193,710],{"class":97},[87,6195,757],{"class":148},[87,6197,760],{"class":93},[87,6199,948],{"class":148},[87,6201,6202],{"class":89,"line":951},[87,6203,142],{"emptyLinePlaceholder":141},[87,6205,6206],{"class":89,"line":956},[87,6207,959],{"class":173},[87,6209,6210,6212,6214,6216,6218,6220,6222,6224,6226,6228,6230,6232,6234],{"class":89,"line":962},[87,6211,935],{"class":148},[87,6213,967],{"class":93},[87,6215,219],{"class":148},[87,6217,972],{"class":97},[87,6219,354],{"class":148},[87,6221,336],{"class":126},[87,6223,219],{"class":148},[87,6225,981],{"class":341},[87,6227,984],{"class":126},[87,6229,987],{"class":93},[87,6231,348],{"class":148},[87,6233,351],{"class":93},[87,6235,387],{"class":148},[87,6237,6238,6240,6242,6244,6246,6248,6250,6252,6254,6256,6258,6260,6262],{"class":89,"line":996},[87,6239,999],{"class":148},[87,6241,1002],{"class":93},[87,6243,219],{"class":148},[87,6245,1007],{"class":415},[87,6247,354],{"class":148},[87,6249,987],{"class":93},[87,6251,348],{"class":148},[87,6253,1016],{"class":93},[87,6255,1019],{"class":148},[87,6257,1022],{"class":97},[87,6259,1025],{"class":148},[87,6261,1028],{"class":97},[87,6263,422],{"class":148},[87,6265,6266],{"class":89,"line":1033},[87,6267,1036],{"class":148},[87,6269,6270],{"class":89,"line":1039},[87,6271,142],{"emptyLinePlaceholder":141},[87,6273,6274,6276,6278,6280,6282],{"class":89,"line":1044},[87,6275,935],{"class":148},[87,6277,1049],{"class":93},[87,6279,219],{"class":148},[87,6281,1054],{"class":97},[87,6283,231],{"class":148},[87,6285,6286],{"class":89,"line":1059},[87,6287,1062],{"class":148},[16,6289,1065],{},[78,6291,6292],{"className":80,"code":1068,"language":82,"meta":83,"style":83},[30,6293,6294,6298,6310,6318,6324,6328,6332,6344,6352],{"__ignoreMap":83},[87,6295,6296],{"class":89,"line":90},[87,6297,1075],{"class":173},[87,6299,6300,6302,6304,6306,6308],{"class":89,"line":104},[87,6301,1080],{"class":93},[87,6303,1083],{"class":415},[87,6305,1086],{"class":97},[87,6307,1089],{"class":97},[87,6309,1092],{"class":415},[87,6311,6312,6314,6316],{"class":89,"line":145},[87,6313,1097],{"class":415},[87,6315,1100],{"class":97},[87,6317,1092],{"class":415},[87,6319,6320,6322],{"class":89,"line":164},[87,6321,1107],{"class":415},[87,6323,1110],{"class":97},[87,6325,6326],{"class":89,"line":177},[87,6327,142],{"emptyLinePlaceholder":141},[87,6329,6330],{"class":89,"line":189},[87,6331,1119],{"class":173},[87,6333,6334,6336,6338,6340,6342],{"class":89,"line":201},[87,6335,1080],{"class":93},[87,6337,1083],{"class":415},[87,6339,1086],{"class":97},[87,6341,1089],{"class":97},[87,6343,1092],{"class":415},[87,6345,6346,6348,6350],{"class":89,"line":211},[87,6347,1097],{"class":415},[87,6349,1100],{"class":97},[87,6351,1092],{"class":415},[87,6353,6354,6356],{"class":89,"line":228},[87,6355,1107],{"class":415},[87,6357,1146],{"class":97},[11,6359,1150],{"id":1149},[16,6361,1153],{},[73,6363,1156],{"id":1156},[16,6365,1159,6366,1163,6368,1167],{},[30,6367,1162],{},[30,6369,1166],{},[73,6371,1171],{"id":1170},[16,6373,1174,6374,1178],{},[30,6375,1177],{},[78,6377,6378],{"className":118,"code":1181,"language":94,"meta":83,"style":83},[30,6379,6380,6384,6408,6428,6444,6456,6484,6488,6492,6496,6516,6546,6574,6578,6582,6586,6602,6614,6642,6646,6650,6654,6666,6678,6686,6690],{"__ignoreMap":83},[87,6381,6382],{"class":89,"line":90},[87,6383,1188],{"class":173},[87,6385,6386,6388,6390,6392,6394,6396,6398,6400,6402,6404,6406],{"class":89,"line":104},[87,6387,336],{"class":126},[87,6389,1195],{"class":93},[87,6391,219],{"class":148},[87,6393,1200],{"class":341},[87,6395,984],{"class":126},[87,6397,1205],{"class":93},[87,6399,1208],{"class":148},[87,6401,987],{"class":93},[87,6403,348],{"class":148},[87,6405,1215],{"class":93},[87,6407,595],{"class":148},[87,6409,6410,6412,6414,6416,6418,6420,6422,6424,6426],{"class":89,"line":145},[87,6411,399],{"class":126},[87,6413,1224],{"class":126},[87,6415,219],{"class":148},[87,6417,981],{"class":341},[87,6419,984],{"class":126},[87,6421,987],{"class":93},[87,6423,348],{"class":148},[87,6425,351],{"class":93},[87,6427,387],{"class":148},[87,6429,6430,6432,6434,6436,6438,6440,6442],{"class":89,"line":164},[87,6431,1243],{"class":148},[87,6433,152],{"class":126},[87,6435,1248],{"class":148},[87,6437,1251],{"class":93},[87,6439,219],{"class":148},[87,6441,1256],{"class":97},[87,6443,231],{"class":148},[87,6445,6446,6448,6450,6452,6454],{"class":89,"line":177},[87,6447,1263],{"class":126},[87,6449,1266],{"class":148},[87,6451,1269],{"class":126},[87,6453,1272],{"class":97},[87,6455,595],{"class":148},[87,6457,6458,6460,6462,6464,6466,6468,6470,6472,6474,6476,6478,6480,6482],{"class":89,"line":189},[87,6459,1279],{"class":148},[87,6461,1282],{"class":93},[87,6463,219],{"class":148},[87,6465,1287],{"class":415},[87,6467,354],{"class":148},[87,6469,987],{"class":93},[87,6471,348],{"class":148},[87,6473,1016],{"class":93},[87,6475,1019],{"class":148},[87,6477,1300],{"class":97},[87,6479,1025],{"class":148},[87,6481,1305],{"class":97},[87,6483,422],{"class":148},[87,6485,6486],{"class":89,"line":201},[87,6487,1312],{"class":126},[87,6489,6490],{"class":89,"line":211},[87,6491,1317],{"class":148},[87,6493,6494],{"class":89,"line":228},[87,6495,142],{"emptyLinePlaceholder":141},[87,6497,6498,6500,6502,6504,6506,6508,6510,6512,6514],{"class":89,"line":390},[87,6499,1326],{"class":148},[87,6501,152],{"class":126},[87,6503,1331],{"class":148},[87,6505,1334],{"class":93},[87,6507,1337],{"class":148},[87,6509,1340],{"class":97},[87,6511,354],{"class":148},[87,6513,1345],{"class":415},[87,6515,231],{"class":148},[87,6517,6518,6520,6522,6524,6526,6528,6530,6532,6534,6536,6538,6540,6542,6544],{"class":89,"line":396},[87,6519,1263],{"class":126},[87,6521,1354],{"class":93},[87,6523,1357],{"class":148},[87,6525,589],{"class":126},[87,6527,1362],{"class":415},[87,6529,1365],{"class":126},[87,6531,1368],{"class":126},[87,6533,1371],{"class":148},[87,6535,1374],{"class":93},[87,6537,1377],{"class":148},[87,6539,1380],{"class":415},[87,6541,1383],{"class":148},[87,6543,1386],{"class":97},[87,6545,387],{"class":148},[87,6547,6548,6550,6552,6554,6556,6558,6560,6562,6564,6566,6568,6570,6572],{"class":89,"line":419},[87,6549,1279],{"class":148},[87,6551,1282],{"class":93},[87,6553,219],{"class":148},[87,6555,1287],{"class":415},[87,6557,354],{"class":148},[87,6559,987],{"class":93},[87,6561,348],{"class":148},[87,6563,1016],{"class":93},[87,6565,1019],{"class":148},[87,6567,1300],{"class":97},[87,6569,1025],{"class":148},[87,6571,1415],{"class":97},[87,6573,422],{"class":148},[87,6575,6576],{"class":89,"line":617},[87,6577,1312],{"class":126},[87,6579,6580],{"class":89,"line":623},[87,6581,1317],{"class":148},[87,6583,6584],{"class":89,"line":629},[87,6585,142],{"emptyLinePlaceholder":141},[87,6587,6588,6590,6592,6594,6596,6598,6600],{"class":89,"line":647},[87,6589,1434],{"class":148},[87,6591,152],{"class":126},[87,6593,1439],{"class":148},[87,6595,1442],{"class":93},[87,6597,1377],{"class":148},[87,6599,1447],{"class":415},[87,6601,1450],{"class":148},[87,6603,6604,6606,6608,6610,6612],{"class":89,"line":927},[87,6605,1263],{"class":126},[87,6607,586],{"class":148},[87,6609,589],{"class":126},[87,6611,592],{"class":415},[87,6613,595],{"class":148},[87,6615,6616,6618,6620,6622,6624,6626,6628,6630,6632,6634,6636,6638,6640],{"class":89,"line":932},[87,6617,1279],{"class":148},[87,6619,1282],{"class":93},[87,6621,219],{"class":148},[87,6623,1287],{"class":415},[87,6625,354],{"class":148},[87,6627,987],{"class":93},[87,6629,348],{"class":148},[87,6631,1016],{"class":93},[87,6633,1019],{"class":148},[87,6635,1300],{"class":97},[87,6637,1025],{"class":148},[87,6639,1489],{"class":97},[87,6641,422],{"class":148},[87,6643,6644],{"class":89,"line":951},[87,6645,1312],{"class":126},[87,6647,6648],{"class":89,"line":956},[87,6649,1317],{"class":148},[87,6651,6652],{"class":89,"line":962},[87,6653,142],{"emptyLinePlaceholder":141},[87,6655,6656,6658,6660,6662,6664],{"class":89,"line":996},[87,6657,999],{"class":148},[87,6659,1510],{"class":93},[87,6661,219],{"class":148},[87,6663,1515],{"class":97},[87,6665,1518],{"class":148},[87,6667,6668,6670,6672,6674,6676],{"class":89,"line":1033},[87,6669,999],{"class":148},[87,6671,1510],{"class":93},[87,6673,219],{"class":148},[87,6675,1529],{"class":97},[87,6677,1532],{"class":148},[87,6679,6680,6682,6684],{"class":89,"line":1039},[87,6681,999],{"class":148},[87,6683,1539],{"class":93},[87,6685,805],{"class":148},[87,6687,6688],{"class":89,"line":1044},[87,6689,620],{"class":148},[87,6691,6692],{"class":89,"line":1059},[87,6693,1062],{"class":148},[78,6695,6696],{"className":118,"code":1552,"language":94,"meta":83,"style":83},[30,6697,6698,6702,6710,6716,6726,6736,6740,6744,6760,6780,6792,6812,6832,6844,6852,6856],{"__ignoreMap":83},[87,6699,6700],{"class":89,"line":90},[87,6701,1559],{"class":173},[87,6703,6704,6706,6708],{"class":89,"line":104},[87,6705,1564],{"class":126},[87,6707,1567],{"class":93},[87,6709,1570],{"class":126},[87,6711,6712,6714],{"class":89,"line":145},[87,6713,1575],{"class":126},[87,6715,1578],{"class":148},[87,6717,6718,6720,6722,6724],{"class":89,"line":164},[87,6719,1583],{"class":415},[87,6721,1586],{"class":93},[87,6723,1589],{"class":126},[87,6725,1592],{"class":97},[87,6727,6728,6730,6732,6734],{"class":89,"line":177},[87,6729,1597],{"class":415},[87,6731,1567],{"class":93},[87,6733,1589],{"class":126},[87,6735,1604],{"class":97},[87,6737,6738],{"class":89,"line":189},[87,6739,231],{"class":148},[87,6741,6742],{"class":89,"line":201},[87,6743,142],{"emptyLinePlaceholder":141},[87,6745,6746,6748,6750,6752,6754,6756,6758],{"class":89,"line":211},[87,6747,336],{"class":126},[87,6749,1619],{"class":93},[87,6751,1622],{"class":148},[87,6753,987],{"class":93},[87,6755,348],{"class":148},[87,6757,1215],{"class":93},[87,6759,595],{"class":148},[87,6761,6762,6764,6766,6768,6770,6772,6774,6776,6778],{"class":89,"line":228},[87,6763,399],{"class":126},[87,6765,1224],{"class":126},[87,6767,219],{"class":148},[87,6769,981],{"class":341},[87,6771,984],{"class":126},[87,6773,987],{"class":93},[87,6775,348],{"class":148},[87,6777,351],{"class":93},[87,6779,387],{"class":148},[87,6781,6782,6784,6786,6788,6790],{"class":89,"line":390},[87,6783,1655],{"class":148},[87,6785,152],{"class":126},[87,6787,1660],{"class":148},[87,6789,351],{"class":93},[87,6791,805],{"class":148},[87,6793,6794,6796,6798,6800,6802,6804,6806,6808,6810],{"class":89,"line":396},[87,6795,1655],{"class":148},[87,6797,1671],{"class":126},[87,6799,1674],{"class":148},[87,6801,1677],{"class":93},[87,6803,1680],{"class":148},[87,6805,1683],{"class":93},[87,6807,219],{"class":148},[87,6809,1515],{"class":97},[87,6811,1690],{"class":148},[87,6813,6814,6816,6818,6820,6822,6824,6826,6828,6830],{"class":89,"line":419},[87,6815,1655],{"class":148},[87,6817,1671],{"class":126},[87,6819,1674],{"class":148},[87,6821,1677],{"class":93},[87,6823,1703],{"class":148},[87,6825,1706],{"class":93},[87,6827,219],{"class":148},[87,6829,1529],{"class":97},[87,6831,1690],{"class":148},[87,6833,6834,6836,6838,6840,6842],{"class":89,"line":617},[87,6835,1717],{"class":148},[87,6837,1671],{"class":126},[87,6839,1660],{"class":148},[87,6841,1724],{"class":93},[87,6843,1727],{"class":148},[87,6845,6846,6848,6850],{"class":89,"line":623},[87,6847,999],{"class":148},[87,6849,1539],{"class":93},[87,6851,805],{"class":148},[87,6853,6854],{"class":89,"line":629},[87,6855,620],{"class":148},[87,6857,6858],{"class":89,"line":647},[87,6859,1062],{"class":148},[73,6861,1746],{"id":1746},[78,6863,6864],{"className":118,"code":1749,"language":94,"meta":83,"style":83},[30,6865,6866,6878,6886,6894,6904],{"__ignoreMap":83},[87,6867,6868,6870,6872,6874,6876],{"class":89,"line":90},[87,6869,747],{"class":148},[87,6871,750],{"class":93},[87,6873,219],{"class":148},[87,6875,710],{"class":97},[87,6877,294],{"class":148},[87,6879,6880,6882,6884],{"class":89,"line":104},[87,6881,1768],{"class":93},[87,6883,1771],{"class":148},[87,6885,1774],{"class":173},[87,6887,6888,6890,6892],{"class":89,"line":145},[87,6889,1779],{"class":93},[87,6891,1782],{"class":148},[87,6893,1785],{"class":173},[87,6895,6896,6898,6900,6902],{"class":89,"line":164},[87,6897,1790],{"class":148},[87,6899,760],{"class":93},[87,6901,1795],{"class":148},[87,6903,1798],{"class":173},[87,6905,6906],{"class":89,"line":177},[87,6907,231],{"class":148},[16,6909,1805],{},[78,6911,6912],{"className":118,"code":1808,"language":94,"meta":83,"style":83},[30,6913,6914,6966,6982,6990,7006,7010,7014,7026,7030,7042],{"__ignoreMap":83},[87,6915,6916,6918,6920,6922,6924,6926,6928,6930,6932,6934,6936,6938,6940,6942,6944,6946,6948,6950,6952,6954,6956,6958,6960,6962,6964],{"class":89,"line":90},[87,6917,336],{"class":126},[87,6919,1817],{"class":148},[87,6921,1820],{"class":341},[87,6923,371],{"class":126},[87,6925,1825],{"class":93},[87,6927,1208],{"class":148},[87,6929,1830],{"class":93},[87,6931,219],{"class":148},[87,6933,342],{"class":341},[87,6935,345],{"class":93},[87,6937,348],{"class":148},[87,6939,351],{"class":93},[87,6941,354],{"class":148},[87,6943,357],{"class":341},[87,6945,360],{"class":93},[87,6947,348],{"class":148},[87,6949,365],{"class":93},[87,6951,368],{"class":148},[87,6953,371],{"class":126},[87,6955,374],{"class":93},[87,6957,348],{"class":148},[87,6959,379],{"class":93},[87,6961,354],{"class":148},[87,6963,384],{"class":126},[87,6965,387],{"class":148},[87,6967,6968,6970,6972,6974,6976,6978,6980],{"class":89,"line":104},[87,6969,1871],{"class":148},[87,6971,152],{"class":126},[87,6973,1876],{"class":148},[87,6975,1879],{"class":93},[87,6977,1882],{"class":148},[87,6979,1885],{"class":126},[87,6981,231],{"class":148},[87,6983,6984,6986,6988],{"class":89,"line":145},[87,6985,583],{"class":126},[87,6987,1368],{"class":126},[87,6989,1896],{"class":148},[87,6991,6992,6994,6996,6998,7000,7002,7004],{"class":89,"line":164},[87,6993,600],{"class":126},[87,6995,283],{"class":148},[87,6997,605],{"class":93},[87,6999,219],{"class":148},[87,7001,1909],{"class":97},[87,7003,412],{"class":148},[87,7005,416],{"class":415},[87,7007,7008],{"class":89,"line":177},[87,7009,620],{"class":148},[87,7011,7012],{"class":89,"line":189},[87,7013,142],{"emptyLinePlaceholder":141},[87,7015,7016,7018,7020,7022,7024],{"class":89,"line":201},[87,7017,1926],{"class":148},[87,7019,152],{"class":126},[87,7021,1931],{"class":148},[87,7023,1934],{"class":93},[87,7025,1937],{"class":148},[87,7027,7028],{"class":89,"line":211},[87,7029,1942],{"class":173},[87,7031,7032,7034,7036,7038,7040],{"class":89,"line":228},[87,7033,399],{"class":126},[87,7035,283],{"class":148},[87,7037,404],{"class":93},[87,7039,1953],{"class":148},[87,7041,416],{"class":415},[87,7043,7044],{"class":89,"line":390},[87,7045,1062],{"class":148},[73,7047,1962],{"id":1962},[16,7049,1965],{},[78,7051,7052],{"className":1968,"code":1969,"language":1970,"meta":83,"style":83},[30,7053,7054,7058,7064,7070,7080,7086,7094,7098,7102,7106],{"__ignoreMap":83},[87,7055,7056],{"class":89,"line":90},[87,7057,1977],{"class":148},[87,7059,7060,7062],{"class":89,"line":104},[87,7061,1982],{"class":415},[87,7063,1985],{"class":148},[87,7065,7066,7068],{"class":89,"line":145},[87,7067,167],{"class":415},[87,7069,1985],{"class":148},[87,7071,7072,7074,7076,7078],{"class":89,"line":164},[87,7073,1996],{"class":415},[87,7075,1025],{"class":148},[87,7077,2001],{"class":97},[87,7079,294],{"class":148},[87,7081,7082,7084],{"class":89,"line":177},[87,7083,2008],{"class":415},[87,7085,1985],{"class":148},[87,7087,7088,7090,7092],{"class":89,"line":189},[87,7089,2015],{"class":415},[87,7091,1025],{"class":148},[87,7093,2020],{"class":97},[87,7095,7096],{"class":89,"line":201},[87,7097,2025],{"class":148},[87,7099,7100],{"class":89,"line":211},[87,7101,620],{"class":148},[87,7103,7104],{"class":89,"line":228},[87,7105,2034],{"class":148},[87,7107,7108],{"class":89,"line":390},[87,7109,1062],{"class":148},[11,7111,2041],{"id":2041},[2043,7113,7114,7122],{},[2046,7115,7116],{},[2049,7117,7118,7120],{},[2052,7119,2054],{},[2052,7121,2057],{},[2059,7123,7124,7132,7142,7150,7158],{},[2049,7125,7126,7130],{},[2064,7127,7128],{},[30,7129,158],{},[2064,7131,2070],{},[2049,7133,7134,7140],{},[2064,7135,7136,2077,7138],{},[30,7137,286],{},[30,7139,330],{},[2064,7141,2082],{},[2049,7143,7144,7148],{},[2064,7145,7146],{},[30,7147,695],{},[2064,7149,2091],{},[2049,7151,7152,7156],{},[2064,7153,7154],{},[30,7155,663],{},[2064,7157,2100],{},[2049,7159,7160,7166],{},[2064,7161,7162,2077,7164],{},[30,7163,2107],{},[30,7165,2110],{},[2064,7167,2113],{},[16,7169,2116,7170,2120,7172,1178],{},[38,7171,2119],{},[30,7173,1177],{},[2124,7175,2126],{},{"title":83,"searchDepth":104,"depth":145,"links":7177},[7178,7179,7180,7181,7188,7194],{"id":13,"depth":104,"text":14},{"id":24,"depth":104,"text":25},{"id":43,"depth":104,"text":43},{"id":71,"depth":104,"text":71,"children":7182},[7183,7184,7185,7186,7187],{"id":75,"depth":145,"text":76},{"id":114,"depth":145,"text":115},{"id":247,"depth":145,"text":248},{"id":652,"depth":145,"text":653},{"id":773,"depth":145,"text":774},{"id":1149,"depth":104,"text":1150,"children":7189},[7190,7191,7192,7193],{"id":1156,"depth":145,"text":1156},{"id":1170,"depth":145,"text":1171},{"id":1746,"depth":145,"text":1746},{"id":1962,"depth":145,"text":1962},{"id":2041,"depth":104,"text":2041},{},{"title":5,"description":2146},[2158,2159,2160,2161,2162],{"id":7199,"title":7200,"author":6,"body":7201,"description":7968,"difficulty":6,"draft":2148,"extension":2149,"image":7969,"meta":7970,"navigation":141,"path":7971,"pinned":2148,"published":7972,"seo":7973,"series":6,"seriesOrder":6,"stem":7974,"tags":7975,"updated":6,"__hash__":7979},"posts\u002Fposts\u002Fdws-dingtalk-cli\u002Findex.md","DWS — 钉钉官方出品的 AI 友好命令行工具",{"type":8,"value":7202,"toc":7954},[7203,7207,7210,7217,7343,7346,7349,7405,7408,7423,7426,7429,7436,7668,7675,7679,7682,7702,7705,7720,7723,7743,7746,7749,7756,7822,7825,7828,7832,7835,7887,7891,7912,7916,7919,7933,7936,7940,7943,7945,7948,7951],[11,7204,7206],{"id":7205},"dws-是什么","dws 是什么",[16,7208,7209],{},"dws（DingTalk Workspace CLI）是阿里巴巴 2026 年开源的钉钉命令行工具，Apache-2.0 协议，Go 语言编写。",[16,7211,7212,7213,7216],{},"它的定位很明确：",[38,7214,7215],{},"把钉钉的所有产品能力暴露给 AI Agent 和命令行用户","。不用打开钉钉 App，不用写 API 调用代码，一条命令搞定。",[78,7218,7220],{"className":80,"code":7219,"language":82,"meta":83,"style":83},"# 查看今天有什么会\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",[30,7221,7222,7227,7253,7257,7262,7284,7288,7293,7318,7322,7327],{"__ignoreMap":83},[87,7223,7224],{"class":89,"line":90},[87,7225,7226],{"class":173},"# 查看今天有什么会\n",[87,7228,7229,7232,7235,7238,7241,7244,7247,7250],{"class":89,"line":104},[87,7230,7231],{"class":93},"dws",[87,7233,7234],{"class":97}," calendar",[87,7236,7237],{"class":97}," event",[87,7239,7240],{"class":97}," list",[87,7242,7243],{"class":415}," --start",[87,7245,7246],{"class":97}," 2026-06-01",[87,7248,7249],{"class":415}," --end",[87,7251,7252],{"class":97}," 2026-06-01\n",[87,7254,7255],{"class":89,"line":145},[87,7256,142],{"emptyLinePlaceholder":141},[87,7258,7259],{"class":89,"line":164},[87,7260,7261],{"class":173},"# 给群里发条消息\n",[87,7263,7264,7266,7269,7272,7275,7278,7281],{"class":89,"line":177},[87,7265,7231],{"class":93},[87,7267,7268],{"class":97}," chat",[87,7270,7271],{"class":97}," send",[87,7273,7274],{"class":415}," --group-id",[87,7276,7277],{"class":97}," xxx",[87,7279,7280],{"class":415}," --content",[87,7282,7283],{"class":97}," \"今天发布推迟到下午3点\"\n",[87,7285,7286],{"class":89,"line":189},[87,7287,142],{"emptyLinePlaceholder":141},[87,7289,7290],{"class":89,"line":201},[87,7291,7292],{"class":173},"# 创建一个待办\n",[87,7294,7295,7297,7300,7303,7306,7309,7312,7315],{"class":89,"line":211},[87,7296,7231],{"class":93},[87,7298,7299],{"class":97}," todo",[87,7301,7302],{"class":97}," task",[87,7304,7305],{"class":97}," create",[87,7307,7308],{"class":415}," --subject",[87,7310,7311],{"class":97}," \"Review PR #123\"",[87,7313,7314],{"class":415}," --due-date",[87,7316,7317],{"class":97}," 2026-06-02\n",[87,7319,7320],{"class":89,"line":228},[87,7321,142],{"emptyLinePlaceholder":141},[87,7323,7324],{"class":89,"line":390},[87,7325,7326],{"class":173},"# 读取钉钉文档\n",[87,7328,7329,7331,7334,7337,7340],{"class":89,"line":396},[87,7330,7231],{"class":93},[87,7332,7333],{"class":97}," doc",[87,7335,7336],{"class":97}," read",[87,7338,7339],{"class":415}," --url",[87,7341,7342],{"class":97}," \"https:\u002F\u002Falidocs.dingtalk.com\u002Fi\u002Fnodes\u002Fxxx\"\n",[11,7344,7345],{"id":7345},"安装",[16,7347,7348],{},"三种方式，选你喜欢的：",[78,7350,7352],{"className":80,"code":7351,"language":82,"meta":83,"style":83},"# 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",[30,7353,7354,7359,7372,7376,7381,7391,7395,7400],{"__ignoreMap":83},[87,7355,7356],{"class":89,"line":90},[87,7357,7358],{"class":173},"# npm（推荐，跨平台自动下载二进制）\n",[87,7360,7361,7364,7366,7369],{"class":89,"line":104},[87,7362,7363],{"class":93},"npm",[87,7365,2251],{"class":97},[87,7367,7368],{"class":415}," -g",[87,7370,7371],{"class":97}," dingtalk-workspace-cli\n",[87,7373,7374],{"class":89,"line":145},[87,7375,142],{"emptyLinePlaceholder":141},[87,7377,7378],{"class":89,"line":164},[87,7379,7380],{"class":173},"# Homebrew（macOS\u002FLinux）\n",[87,7382,7383,7386,7388],{"class":89,"line":177},[87,7384,7385],{"class":93},"brew",[87,7387,2251],{"class":97},[87,7389,7390],{"class":97}," dws\n",[87,7392,7393],{"class":89,"line":189},[87,7394,142],{"emptyLinePlaceholder":141},[87,7396,7397],{"class":89,"line":201},[87,7398,7399],{"class":173},"# 直接下载二进制\n",[87,7401,7402],{"class":89,"line":211},[87,7403,7404],{"class":173},"# https:\u002F\u002Fgithub.com\u002Fopen-dingtalk\u002Fdingtalk-workspace-cli\u002Freleases\n",[16,7406,7407],{},"安装后认证：",[78,7409,7411],{"className":80,"code":7410,"language":82,"meta":83,"style":83},"dws auth login\n",[30,7412,7413],{"__ignoreMap":83},[87,7414,7415,7417,7420],{"class":89,"line":90},[87,7416,7231],{"class":93},[87,7418,7419],{"class":97}," auth",[87,7421,7422],{"class":97}," login\n",[16,7424,7425],{},"会打开浏览器走 OAuth 设备流认证，token 加密存储在系统钥匙串里（PBKDF2 + AES-256-GCM）。",[11,7427,7428],{"id":7428},"覆盖的钉钉产品",[16,7430,7431,7432,7435],{},"这是最夸张的部分 — ",[38,7433,7434],{},"19 个产品，213 个命令","，基本把钉钉全家桶都搬到了命令行：",[2043,7437,7438,7450],{},[2046,7439,7440],{},[2049,7441,7442,7445,7448],{},[2052,7443,7444],{},"产品",[2052,7446,7447],{},"命令数",[2052,7449,2057],{},[2059,7451,7452,7466,7480,7494,7508,7522,7536,7550,7563,7577,7590,7604,7618,7631,7644,7657],{},[2049,7453,7454,7460,7463],{},[2064,7455,7456,7457],{},"聊天 ",[30,7458,7459],{},"chat",[2064,7461,7462],{},"57",[2064,7464,7465],{},"消息收发、群管理、机器人、已读状态、Webhook",[2049,7467,7468,7474,7477],{},[2064,7469,7470,7471],{},"AI 表格 ",[30,7472,7473],{},"aitable",[2064,7475,7476],{},"42",[2064,7478,7479],{},"Base\u002F数据表\u002F字段\u002F记录\u002F视图\u002F图表\u002F仪表盘，完整 CRUD",[2049,7481,7482,7488,7491],{},[2064,7483,7484,7485],{},"电子表格 ",[30,7486,7487],{},"sheet",[2064,7489,7490],{},"34",[2064,7492,7493],{},"工作表读写、行列操作、合并、筛选视图、导出",[2049,7495,7496,7502,7505],{},[2064,7497,7498,7499],{},"文档 ",[30,7500,7501],{},"doc",[2064,7503,7504],{},"21",[2064,7506,7507],{},"搜索、读写、块级编辑、评论、复制移动",[2049,7509,7510,7516,7519],{},[2064,7511,7512,7513],{},"会议纪要 ",[30,7514,7515],{},"minutes",[2064,7517,7518],{},"19",[2064,7520,7521],{},"列表、摘要、转写、思维导图、发言人、热词",[2049,7523,7524,7530,7533],{},[2064,7525,7526,7527],{},"日历 ",[30,7528,7529],{},"calendar",[2064,7531,7532],{},"14",[2064,7534,7535],{},"事件 CRUD、会议室、忙闲查询、时间建议",[2049,7537,7538,7544,7547],{},[2064,7539,7540,7541],{},"审批 ",[30,7542,7543],{},"oa",[2064,7545,7546],{},"9",[2064,7548,7549],{},"待办、发起、同意\u002F拒绝\u002F撤销",[2049,7551,7552,7558,7560],{},[2064,7553,7554,7555],{},"云盘 ",[30,7556,7557],{},"drive",[2064,7559,7546],{},[2064,7561,7562],{},"列表、下载、上传、创建目录",[2049,7564,7565,7571,7574],{},[2064,7566,7567,7568],{},"知识库 ",[30,7569,7570],{},"wiki",[2064,7572,7573],{},"7",[2064,7575,7576],{},"空间管理、成员管理",[2049,7578,7579,7585,7587],{},[2064,7580,7581,7582],{},"汇报 ",[30,7583,7584],{},"report",[2064,7586,7573],{},[2064,7588,7589],{},"日志\u002F日报\u002F周报，按模板创建、统计",[2049,7591,7592,7598,7601],{},[2064,7593,7594,7595],{},"待办 ",[30,7596,7597],{},"todo",[2064,7599,7600],{},"6",[2064,7602,7603],{},"创建、完成、删除，支持优先级和循环",[2049,7605,7606,7612,7615],{},[2064,7607,7608,7609],{},"邮件 ",[30,7610,7611],{},"mail",[2064,7613,7614],{},"4",[2064,7616,7617],{},"邮箱查询、邮件搜索、发送",[2049,7619,7620,7626,7628],{},[2064,7621,7622,7623],{},"考勤 ",[30,7624,7625],{},"attendance",[2064,7627,7614],{},[2064,7629,7630],{},"打卡记录、排班、汇总",[2049,7632,7633,7639,7641],{},[2064,7634,7635,7636],{},"通讯录 ",[30,7637,7638],{},"contact",[2064,7640,4101],{},[2064,7642,7643],{},"用户搜索、部门查询",[2049,7645,7646,7652,7654],{},[2064,7647,7648,7649],{},"DING 消息 ",[30,7650,7651],{},"ding",[2064,7653,1345],{},[2064,7655,7656],{},"发送\u002F撤回（应用内、短信、电话）",[2049,7658,7659,7662,7665],{},[2064,7660,7661],{},"其他",[2064,7663,7664],{},"若干",[2064,7666,7667],{},"AI 搜索、AI 应用、直播、开放平台文档",[16,7669,7670,7671,7674],{},"还有一个万能命令 ",[30,7672,7673],{},"dws api","，可以直接调用任意钉钉 OpenAPI，不受这 213 个命令的限制。",[11,7676,7678],{"id":7677},"agent-skill-系统","Agent Skill 系统",[16,7680,7681],{},"dws 内置了一套完整的 Agent Skill，可以直接被 AI 编程工具发现和使用：",[3705,7683,7684,7690,7696],{},[51,7685,7686,7689],{},[38,7687,7688],{},"Claude Code"," — 通过 SKILL.md 自动加载",[51,7691,7692,7695],{},[38,7693,7694],{},"Cursor"," — 支持 MCP 协议接入",[51,7697,7698,7701],{},[38,7699,7700],{},"Codex \u002F OpenCode \u002F Qoder"," — 通过 skill 安装命令接入",[16,7703,7704],{},"安装 skill：",[78,7706,7708],{"className":80,"code":7707,"language":82,"meta":83,"style":83},"dws skill setup\n",[30,7709,7710],{"__ignoreMap":83},[87,7711,7712,7714,7717],{"class":89,"line":90},[87,7713,7231],{"class":93},[87,7715,7716],{"class":97}," skill",[87,7718,7719],{"class":97}," setup\n",[16,7721,7722],{},"SKILL.md 里定义了：",[3705,7724,7725,7734,7737,7740],{},[51,7726,7727,7728,7730,7731,7733],{},"16 个产品的意图判断决策树（用户说\"表格\"→ ",[30,7729,7473],{},"，说\"打卡\"→ ",[30,7732,7625],{},"）",[51,7735,7736],{},"危险操作确认流程（删除、撤回等必须先确认）",[51,7738,7739],{},"错误处理规范（verbose 重试、recovery 事件 ID）",[51,7741,7742],{},"URL 分流规则（alidocs.dingtalk.com 的多种路径格式自动识别）",[16,7744,7745],{},"Agent 读到这份 SKILL.md 就知道什么时候该调什么命令，不需要额外的 prompt 工程。",[11,7747,7748],{"id":7748},"自动升级",[16,7750,7751,7752,7755],{},"内置 ",[30,7753,7754],{},"dws upgrade","，不需要重新下载安装包：",[78,7757,7759],{"className":80,"code":7758,"language":82,"meta":83,"style":83},"dws upgrade              # 交互式升级到最新版\ndws upgrade --check      # 仅检查是否有新版本\ndws upgrade --list       # 列出最近版本\ndws upgrade --version v1.0.5  # 升级到指定版本\ndws upgrade --rollback   # 回滚到上一版本\n",[30,7760,7761,7771,7783,7795,7810],{"__ignoreMap":83},[87,7762,7763,7765,7768],{"class":89,"line":90},[87,7764,7231],{"class":93},[87,7766,7767],{"class":97}," upgrade",[87,7769,7770],{"class":173},"              # 交互式升级到最新版\n",[87,7772,7773,7775,7777,7780],{"class":89,"line":104},[87,7774,7231],{"class":93},[87,7776,7767],{"class":97},[87,7778,7779],{"class":415}," --check",[87,7781,7782],{"class":173},"      # 仅检查是否有新版本\n",[87,7784,7785,7787,7789,7792],{"class":89,"line":145},[87,7786,7231],{"class":93},[87,7788,7767],{"class":97},[87,7790,7791],{"class":415}," --list",[87,7793,7794],{"class":173},"       # 列出最近版本\n",[87,7796,7797,7799,7801,7804,7807],{"class":89,"line":164},[87,7798,7231],{"class":93},[87,7800,7767],{"class":97},[87,7802,7803],{"class":415}," --version",[87,7805,7806],{"class":97}," v1.0.5",[87,7808,7809],{"class":173},"  # 升级到指定版本\n",[87,7811,7812,7814,7816,7819],{"class":89,"line":177},[87,7813,7231],{"class":93},[87,7815,7767],{"class":97},[87,7817,7818],{"class":415}," --rollback",[87,7820,7821],{"class":173},"   # 回滚到上一版本\n",[16,7823,7824],{},"升级流程：下载 → SHA256 校验 → 备份当前版本 → 原子替换 → 更新 Skill。失败可一键回滚。",[11,7826,7827],{"id":7827},"亮点",[73,7829,7831],{"id":7830},"_1-为-ai-agent-专门优化","1. 为 AI Agent 专门优化",[16,7833,7834],{},"这可能是目前对 AI 最友好的 CLI 工具：",[3705,7836,7837,7858,7868,7877],{},[51,7838,7839,7842,7843,7846,7847,7850,7851,7846,7854,7857],{},[38,7840,7841],{},"智能纠错"," — AI 模型经常生成错误的参数格式（",[30,7844,7845],{},"--userId"," 应该是 ",[30,7848,7849],{},"--user-id","，",[30,7852,7853],{},"--limit100",[30,7855,7856],{},"--limit 100","），dws 的 5 阶段 Pipeline 会自动修正",[51,7859,7860,7863,7864,7867],{},[38,7861,7862],{},"Schema 发现"," — ",[30,7865,7866],{},"dws schema"," 让 Agent 动态查询可用工具和参数，不需要硬编码",[51,7869,7870,7863,7873,7876],{},[38,7871,7872],{},"jq 过滤",[30,7874,7875],{},"--jq '.data[] | {name, id}'"," 直接过滤输出，减少 Agent 消耗的 token",[51,7878,7879,7882,7883,7886],{},[38,7880,7881],{},"JSON 输出"," — 所有命令支持 ",[30,7884,7885],{},"--format json","，方便程序解析",[73,7888,7890],{"id":7889},"_2-安全设计","2. 安全设计",[3705,7892,7893,7896,7899,7902,7909],{},[51,7894,7895],{},"token 用 PBKDF2 + AES-256-GCM 加密，密钥绑定设备 MAC 地址",[51,7897,7898],{},"域名白名单，只能访问钉钉官方 API",[51,7900,7901],{},"强制 HTTPS",[51,7903,7904,7905,7908],{},"写操作必须显式 ",[30,7906,7907],{},"--yes"," 确认",[51,7910,7911],{},"完整审计链",[73,7913,7915],{"id":7914},"_3-发现驱动架构","3. 发现驱动架构",[16,7917,7918],{},"不是把所有命令硬编码在代码里，而是：",[48,7920,7921,7924,7927,7930],{},[51,7922,7923],{},"从钉钉 MCP 服务器注册中心拉取元数据",[51,7925,7926],{},"运行时发现服务端能力",[51,7928,7929],{},"转换成统一的中间表示（IR）",[51,7931,7932],{},"动态挂载到命令树",[16,7934,7935],{},"这意味着钉钉新增产品能力时，dws 不需要发版就能支持。",[73,7937,7939],{"id":7938},"_4-多平台分发","4. 多平台分发",[16,7941,7942],{},"GoReleaser 交叉编译 + Homebrew formula + npm 包 + shell\u002FPowerShell 安装脚本，覆盖 Windows、macOS、Linux。",[11,7944,2041],{"id":2041},[16,7946,7947],{},"dws 把钉钉从一个\"必须打开 App 才能用\"的工具变成了一个\"命令行 + AI Agent 随时调用\"的平台。213 个命令覆盖日常办公的方方面面，Agent Skill 系统让 AI 编程工具可以直接理解和使用这些能力。",[16,7949,7950],{},"如果你是钉钉重度用户，或者在做企业内部的 AI Agent 集成，dws 值得一试。",[2124,7952,7953],{},"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":83,"searchDepth":104,"depth":145,"links":7955},[7956,7957,7958,7959,7960,7961,7967],{"id":7205,"depth":104,"text":7206},{"id":7345,"depth":104,"text":7345},{"id":7428,"depth":104,"text":7428},{"id":7677,"depth":104,"text":7678},{"id":7748,"depth":104,"text":7748},{"id":7827,"depth":104,"text":7827,"children":7962},[7963,7964,7965,7966],{"id":7830,"depth":145,"text":7831},{"id":7889,"depth":145,"text":7890},{"id":7914,"depth":145,"text":7915},{"id":7938,"depth":145,"text":7939},{"id":2041,"depth":104,"text":2041},"介绍阿里巴巴官方的 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":7200,"description":7968},"posts\u002Fdws-dingtalk-cli\u002Findex",[7976,7977,2161,2158,7978],"钉钉","CLI","效率工具","4ca65VUZ2Sz9yG8dNtftrBDDHE6QowA3ed9g4egQkKI",{"id":7981,"title":7982,"author":6,"body":7983,"description":8946,"difficulty":2147,"draft":2148,"extension":2149,"image":8947,"meta":8948,"navigation":141,"path":8949,"pinned":2148,"published":8950,"seo":8951,"series":2155,"seriesOrder":104,"stem":8952,"tags":8953,"updated":8957,"__hash__":8958},"posts\u002Fposts\u002Fgo-embed-frontend\u002Findex.md","Go 单文件打包实战：把前端塞进二进制",{"type":8,"value":7984,"toc":8929},[7985,7987,7994,7997,8000,8011,8014,8018,8351,8355,8463,8467,8601,8604,8608,8617,8634,8645,8664,8678,8684,8692,8791,8797,8801,8808,8817,8820,8856,8859,8864,8903,8905,8911,8923,8926],[11,7986,4021],{"id":4021},[16,7988,7989,7990,7993],{},"一个 Gin + Vue 3 的全栈项目，之前的发布方式是：一个 Go 二进制 + 一个 ",[30,7991,7992],{},"web\u002F"," 前端静态目录，必须放一起才能运行。",[16,7995,7996],{},"为了让部署更丝滑——一个 exe 拖哪都能跑——决定把前端构建产物嵌入到 Go 二进制里。",[11,7998,7999],{"id":7999},"核心思路",[16,8001,8002,8003,8006,8007,8010],{},"利用 Go 1.16 引入的 ",[30,8004,8005],{},"\u002F\u002Fgo:embed"," 指令，在编译时将前端 ",[30,8008,8009],{},"dist\u002F"," 目录嵌入二进制，运行时通过嵌入的文件系统提供 SPA 服务。",[11,8012,8013],{"id":8013},"逐步实现",[73,8015,8017],{"id":8016},"_1-创建嵌入模块","1. 创建嵌入模块",[78,8019,8021],{"className":118,"code":8020,"language":94,"meta":83,"style":83},"\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",[30,8022,8023,8028,8036,8040,8046,8056,8065,8074,8083,8092,8101,8105,8114,8118,8122,8127,8142,8146,8163,8184,8188,8208,8228,8241,8251,8255,8259,8274,8286,8291,8310,8314,8318,8341,8346],{"__ignoreMap":83},[87,8024,8025],{"class":89,"line":90},[87,8026,8027],{"class":173},"\u002F\u002F service\u002Fweb\u002Fembed.go\n",[87,8029,8030,8033],{"class":89,"line":104},[87,8031,8032],{"class":126},"package",[87,8034,8035],{"class":93}," web\n",[87,8037,8038],{"class":89,"line":145},[87,8039,142],{"emptyLinePlaceholder":141},[87,8041,8042,8044],{"class":89,"line":164},[87,8043,127],{"class":126},[87,8045,1578],{"class":148},[87,8047,8048,8051,8054],{"class":89,"line":177},[87,8049,8050],{"class":97},"    \"",[87,8052,8053],{"class":93},"embed",[87,8055,136],{"class":97},[87,8057,8058,8060,8063],{"class":89,"line":189},[87,8059,8050],{"class":97},[87,8061,8062],{"class":93},"io\u002Ffs",[87,8064,136],{"class":97},[87,8066,8067,8069,8072],{"class":89,"line":201},[87,8068,8050],{"class":97},[87,8070,8071],{"class":93},"mime",[87,8073,136],{"class":97},[87,8075,8076,8078,8081],{"class":89,"line":211},[87,8077,8050],{"class":97},[87,8079,8080],{"class":93},"net\u002Fhttp",[87,8082,136],{"class":97},[87,8084,8085,8087,8090],{"class":89,"line":228},[87,8086,8050],{"class":97},[87,8088,8089],{"class":93},"path\u002Ffilepath",[87,8091,136],{"class":97},[87,8093,8094,8096,8099],{"class":89,"line":390},[87,8095,8050],{"class":97},[87,8097,8098],{"class":93},"strings",[87,8100,136],{"class":97},[87,8102,8103],{"class":89,"line":396},[87,8104,142],{"emptyLinePlaceholder":141},[87,8106,8107,8109,8112],{"class":89,"line":419},[87,8108,8050],{"class":97},[87,8110,8111],{"class":93},"github.com\u002Fgin-gonic\u002Fgin",[87,8113,136],{"class":97},[87,8115,8116],{"class":89,"line":617},[87,8117,231],{"class":148},[87,8119,8120],{"class":89,"line":623},[87,8121,142],{"emptyLinePlaceholder":141},[87,8123,8124],{"class":89,"line":629},[87,8125,8126],{"class":173},"\u002F\u002Fgo:embed dist\n",[87,8128,8129,8132,8135,8137,8139],{"class":89,"line":647},[87,8130,8131],{"class":126},"var",[87,8133,8134],{"class":148}," embedFS ",[87,8136,8053],{"class":93},[87,8138,348],{"class":148},[87,8140,8141],{"class":93},"FS\n",[87,8143,8144],{"class":89,"line":927},[87,8145,142],{"emptyLinePlaceholder":141},[87,8147,8148,8150,8153,8155,8157,8159,8161],{"class":89,"line":932},[87,8149,336],{"class":126},[87,8151,8152],{"class":93}," SPAHandler",[87,8154,1622],{"class":148},[87,8156,987],{"class":93},[87,8158,348],{"class":148},[87,8160,1215],{"class":93},[87,8162,595],{"class":148},[87,8164,8165,8168,8170,8173,8176,8179,8182],{"class":89,"line":951},[87,8166,8167],{"class":148},"    sub, _ ",[87,8169,152],{"class":126},[87,8171,8172],{"class":148}," fs.",[87,8174,8175],{"class":93},"Sub",[87,8177,8178],{"class":148},"(embedFS, ",[87,8180,8181],{"class":97},"\"dist\"",[87,8183,231],{"class":148},[87,8185,8186],{"class":89,"line":956},[87,8187,142],{"emptyLinePlaceholder":141},[87,8189,8190,8192,8194,8196,8198,8200,8202,8204,8206],{"class":89,"line":962},[87,8191,399],{"class":126},[87,8193,1224],{"class":126},[87,8195,219],{"class":148},[87,8197,981],{"class":341},[87,8199,984],{"class":126},[87,8201,987],{"class":93},[87,8203,348],{"class":148},[87,8205,351],{"class":93},[87,8207,387],{"class":148},[87,8209,8210,8213,8215,8217,8220,8223,8226],{"class":89,"line":996},[87,8211,8212],{"class":148},"        p ",[87,8214,152],{"class":126},[87,8216,1331],{"class":148},[87,8218,8219],{"class":93},"TrimPrefix",[87,8221,8222],{"class":148},"(c.Request.URL.Path, ",[87,8224,8225],{"class":97},"\"\u002F\"",[87,8227,231],{"class":148},[87,8229,8230,8232,8235,8237,8239],{"class":89,"line":1033},[87,8231,1263],{"class":126},[87,8233,8234],{"class":148}," p ",[87,8236,1269],{"class":126},[87,8238,1272],{"class":97},[87,8240,595],{"class":148},[87,8242,8243,8246,8248],{"class":89,"line":1039},[87,8244,8245],{"class":148},"            p ",[87,8247,1671],{"class":126},[87,8249,8250],{"class":97}," \"index.html\"\n",[87,8252,8253],{"class":89,"line":1044},[87,8254,1317],{"class":148},[87,8256,8257],{"class":89,"line":1059},[87,8258,142],{"emptyLinePlaceholder":141},[87,8260,8261,8264,8266,8268,8271],{"class":89,"line":3261},[87,8262,8263],{"class":148},"        data, err ",[87,8265,152],{"class":126},[87,8267,8172],{"class":148},[87,8269,8270],{"class":93},"ReadFile",[87,8272,8273],{"class":148},"(sub, p)\n",[87,8275,8276,8278,8280,8282,8284],{"class":89,"line":3269},[87,8277,1263],{"class":126},[87,8279,586],{"class":148},[87,8281,589],{"class":126},[87,8283,592],{"class":415},[87,8285,595],{"class":148},[87,8287,8288],{"class":89,"line":3277},[87,8289,8290],{"class":173},"            \u002F\u002F SPA fallback：文件不存在则返回 index.html\n",[87,8292,8293,8296,8298,8300,8302,8305,8308],{"class":89,"line":3282},[87,8294,8295],{"class":148},"            data, _ ",[87,8297,1671],{"class":126},[87,8299,8172],{"class":148},[87,8301,8270],{"class":93},[87,8303,8304],{"class":148},"(sub, ",[87,8306,8307],{"class":97},"\"index.html\"",[87,8309,231],{"class":148},[87,8311,8312],{"class":89,"line":3288},[87,8313,1317],{"class":148},[87,8315,8316],{"class":89,"line":3297},[87,8317,142],{"emptyLinePlaceholder":141},[87,8319,8321,8323,8326,8329,8332,8335,8338],{"class":89,"line":8320},33,[87,8322,999],{"class":148},[87,8324,8325],{"class":93},"Data",[87,8327,8328],{"class":148},"(http.StatusOK, mime.",[87,8330,8331],{"class":93},"TypeByExtension",[87,8333,8334],{"class":148},"(filepath.",[87,8336,8337],{"class":93},"Ext",[87,8339,8340],{"class":148},"(p)), data)\n",[87,8342,8344],{"class":89,"line":8343},34,[87,8345,620],{"class":148},[87,8347,8349],{"class":89,"line":8348},35,[87,8350,1062],{"class":148},[73,8352,8354],{"id":8353},"_2-改造路由","2. 改造路由",[78,8356,8358],{"className":118,"code":8357,"language":94,"meta":83,"style":83},"\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",[30,8359,8360,8365,8376,8380,8385,8400,8414,8427,8440,8453],{"__ignoreMap":83},[87,8361,8362],{"class":89,"line":90},[87,8363,8364],{"class":173},"\u002F\u002F router.go\n",[87,8366,8367,8369,8371,8374],{"class":89,"line":104},[87,8368,127],{"class":126},[87,8370,130],{"class":97},[87,8372,8373],{"class":93},"myproject\u002Fweb",[87,8375,136],{"class":97},[87,8377,8378],{"class":89,"line":145},[87,8379,142],{"emptyLinePlaceholder":141},[87,8381,8382],{"class":89,"line":164},[87,8383,8384],{"class":173},"\u002F\u002F 替换原来的磁盘静态文件服务\n",[87,8386,8387,8390,8392,8395,8398],{"class":89,"line":177},[87,8388,8389],{"class":148},"webHandler ",[87,8391,152],{"class":126},[87,8393,8394],{"class":148}," web.",[87,8396,8397],{"class":93},"SPAHandler",[87,8399,805],{"class":148},[87,8401,8402,8405,8407,8409,8411],{"class":89,"line":189},[87,8403,8404],{"class":148},"router.",[87,8406,967],{"class":93},[87,8408,219],{"class":148},[87,8410,8225],{"class":97},[87,8412,8413],{"class":148},", webHandler)\n",[87,8415,8416,8418,8420,8422,8425],{"class":89,"line":201},[87,8417,8404],{"class":148},[87,8419,967],{"class":93},[87,8421,219],{"class":148},[87,8423,8424],{"class":97},"\"\u002Fassets\u002F*filepath\"",[87,8426,8413],{"class":148},[87,8428,8429,8431,8433,8435,8438],{"class":89,"line":211},[87,8430,8404],{"class":148},[87,8432,967],{"class":93},[87,8434,219],{"class":148},[87,8436,8437],{"class":97},"\"\u002Ffavicon.ico\"",[87,8439,8413],{"class":148},[87,8441,8442,8444,8446,8448,8451],{"class":89,"line":228},[87,8443,8404],{"class":148},[87,8445,967],{"class":93},[87,8447,219],{"class":148},[87,8449,8450],{"class":97},"\"\u002Ffavicon.svg\"",[87,8452,8413],{"class":148},[87,8454,8455,8457,8460],{"class":89,"line":390},[87,8456,8404],{"class":148},[87,8458,8459],{"class":93},"NoRoute",[87,8461,8462],{"class":148},"(webHandler)\n",[73,8464,8466],{"id":8465},"_3-一键构建脚本","3. 一键构建脚本",[78,8468,8470],{"className":80,"code":8469,"language":82,"meta":83,"style":83},"#!\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",[30,8471,8472,8477,8482,8486,8494,8498,8503,8513,8523,8527,8532,8543,8556,8560,8565,8573,8586,8596],{"__ignoreMap":83},[87,8473,8474],{"class":89,"line":90},[87,8475,8476],{"class":173},"#!\u002Fbin\u002Fbash\n",[87,8478,8479],{"class":89,"line":104},[87,8480,8481],{"class":173},"# build-standalone.sh\n",[87,8483,8484],{"class":89,"line":145},[87,8485,142],{"emptyLinePlaceholder":141},[87,8487,8488,8491],{"class":89,"line":164},[87,8489,8490],{"class":415},"set",[87,8492,8493],{"class":415}," -e\n",[87,8495,8496],{"class":89,"line":177},[87,8497,142],{"emptyLinePlaceholder":141},[87,8499,8500],{"class":89,"line":189},[87,8501,8502],{"class":173},"# 1. 构建前端\n",[87,8504,8505,8508,8510],{"class":89,"line":201},[87,8506,8507],{"class":93},"pnpm",[87,8509,2251],{"class":97},[87,8511,8512],{"class":415}," --no-frozen-lockfile\n",[87,8514,8515,8517,8520],{"class":89,"line":211},[87,8516,3919],{"class":93},[87,8518,8519],{"class":97}," vite",[87,8521,8522],{"class":97}," build\n",[87,8524,8525],{"class":89,"line":228},[87,8526,142],{"emptyLinePlaceholder":141},[87,8528,8529],{"class":89,"line":390},[87,8530,8531],{"class":173},"# 2. 复制到嵌入目录\n",[87,8533,8534,8537,8540],{"class":89,"line":396},[87,8535,8536],{"class":93},"rm",[87,8538,8539],{"class":415}," -rf",[87,8541,8542],{"class":97}," service\u002Fweb\u002Fdist\n",[87,8544,8545,8548,8551,8554],{"class":89,"line":419},[87,8546,8547],{"class":93},"cp",[87,8549,8550],{"class":415}," -r",[87,8552,8553],{"class":97}," dist",[87,8555,8542],{"class":97},[87,8557,8558],{"class":89,"line":617},[87,8559,142],{"emptyLinePlaceholder":141},[87,8561,8562],{"class":89,"line":623},[87,8563,8564],{"class":173},"# 3. 编译 Go 二进制\n",[87,8566,8567,8570],{"class":89,"line":629},[87,8568,8569],{"class":415},"cd",[87,8571,8572],{"class":97}," service\n",[87,8574,8575,8577,8579,8581,8584],{"class":89,"line":647},[87,8576,94],{"class":93},[87,8578,2317],{"class":97},[87,8580,2326],{"class":415},[87,8582,8583],{"class":97}," myapp.exe",[87,8585,1092],{"class":415},[87,8587,8588,8591,8594],{"class":89,"line":927},[87,8589,8590],{"class":415},"    -ldflags=",[87,8592,8593],{"class":97},"\"-X myproject\u002Fglobal.RUNCODE=release\"",[87,8595,1092],{"class":415},[87,8597,8598],{"class":89,"line":932},[87,8599,8600],{"class":97},"    main.go\n",[11,8602,8603],{"id":8603},"踩坑记录",[73,8605,8607],{"id":8606},"坑一路由不匹配静态资源-404","坑一：路由不匹配，静态资源 404",[16,8609,8610,8611,8613,8614,8616],{},"首次实现时用了 Gin 的 ",[30,8612,8459],{}," 兜底，但部分静态资源返回 404。排查发现 ",[30,8615,8459],{}," 在某些路由未显式注册时不会按预期触发。",[16,8618,8619,8622,8623,8626,8627,8630,8631,8633],{},[38,8620,8621],{},"解决","：显式注册 ",[30,8624,8625],{},"\u002Fassets\u002F*filepath","、",[30,8628,8629],{},"\u002Ffavicon.ico"," 等路由，",[30,8632,8459],{}," 仅作为兜底。",[73,8635,8637,8638,8640,8641,8644],{"id":8636},"坑二goembed-排除-_-开头文件","坑二：",[30,8639,8005],{}," 排除 ",[30,8642,8643],{},"_"," 开头文件",[16,8646,8647,8648,8651,8652,8654,8655,1178],{},"这是最坑的。Vite 构建产出了一个 ",[30,8649,8650],{},"_plugin-vue_export-helper-xxx.js","，Go 的 ",[30,8653,8005],{}," 当模式为目录时，",[38,8656,8657,8658,8660,8661,8663],{},"硬编码排除了 ",[30,8659,8643],{}," 和 ",[30,8662,348],{}," 开头的文件",[78,8665,8667],{"className":118,"code":8666,"language":94,"meta":83,"style":83},"\u002F\u002F 这行会跳过 dist\u002Fassets\u002F_plugin-xxx.js\n\u002F\u002Fgo:embed dist\n",[30,8668,8669,8674],{"__ignoreMap":83},[87,8670,8671],{"class":89,"line":90},[87,8672,8673],{"class":173},"\u002F\u002F 这行会跳过 dist\u002Fassets\u002F_plugin-xxx.js\n",[87,8675,8676],{"class":89,"line":104},[87,8677,8126],{"class":173},[16,8679,8680,8681,1178],{},"错误表现：JS 文件返回的是 HTML 内容（SPA fallback），浏览器报 ",[30,8682,8683],{},"Unexpected token '\u003C'",[16,8685,8686,8688,8689,8691],{},[38,8687,8621],{},"：改 Vite 配置，去除了 ",[30,8690,8643],{}," 前缀：",[78,8693,8697],{"className":8694,"code":8695,"language":8696,"meta":83,"style":83},"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",[30,8698,8699,8704,8711,8718,8725,8733,8771,8776,8781,8786],{"__ignoreMap":83},[87,8700,8701],{"class":89,"line":90},[87,8702,8703],{"class":173},"\u002F\u002F vite.config.ts\n",[87,8705,8706,8709],{"class":89,"line":104},[87,8707,8708],{"class":93},"build",[87,8710,1985],{"class":148},[87,8712,8713,8716],{"class":89,"line":145},[87,8714,8715],{"class":93},"  rollupOptions",[87,8717,1985],{"class":148},[87,8719,8720,8723],{"class":89,"line":164},[87,8721,8722],{"class":93},"    output",[87,8724,1985],{"class":148},[87,8726,8727,8730],{"class":89,"line":177},[87,8728,8729],{"class":93},"      chunkFileNames",[87,8731,8732],{"class":148},"(chunkInfo) {\n",[87,8734,8735,8737,8740,8743,8745,8748,8750,8752,8754,8756,8758,8760,8762,8764,8766,8768],{"class":89,"line":189},[87,8736,600],{"class":126},[87,8738,8739],{"class":97}," `assets\u002F${",[87,8741,8742],{"class":148},"chunkInfo",[87,8744,348],{"class":97},[87,8746,8747],{"class":148},"name",[87,8749,348],{"class":97},[87,8751,4638],{"class":93},[87,8753,219],{"class":97},[87,8755,4264],{"class":97},[87,8757,4251],{"class":126},[87,8759,8643],{"class":4254},[87,8761,4264],{"class":97},[87,8763,354],{"class":97},[87,8765,4655],{"class":97},[87,8767,4958],{"class":97},[87,8769,8770],{"class":97},"}-[hash].js`\n",[87,8772,8773],{"class":89,"line":201},[87,8774,8775],{"class":148},"      },\n",[87,8777,8778],{"class":89,"line":211},[87,8779,8780],{"class":148},"    },\n",[87,8782,8783],{"class":89,"line":228},[87,8784,8785],{"class":148},"  },\n",[87,8787,8788],{"class":89,"line":390},[87,8789,8790],{"class":148},"},\n",[16,8792,8793,8794,8796],{},"这样就避免产生 ",[30,8795,8643],{}," 前缀文件名的同时，保持构建产物的一致性和引用关系的正确性。",[73,8798,8800],{"id":8799},"坑三构建缓存导致-embed-内容未更新","坑三：构建缓存导致 embed 内容未更新",[16,8802,8803,8804,8807],{},"多次 ",[30,8805,8806],{},"go build"," 后，Go 的构建缓存可能使用了旧版本的 embed 内容，导致实际运行的文件不是最新的。",[16,8809,8810,8812,8813,8816],{},[38,8811,8621],{},"：使用 ",[30,8814,8815],{},"go build -a"," 强制重新编译所有包。",[11,8818,8819],{"id":8819},"最终效果",[78,8821,8823],{"className":80,"code":8822,"language":82,"meta":83,"style":83},"$ bash build-standalone.sh\n=== 构建成功 ===\n输出: service\u002Fmyapp.exe  41M\n",[30,8824,8825,8835,8845],{"__ignoreMap":83},[87,8826,8827,8829,8832],{"class":89,"line":90},[87,8828,4261],{"class":93},[87,8830,8831],{"class":97}," bash",[87,8833,8834],{"class":97}," build-standalone.sh\n",[87,8836,8837,8839,8842],{"class":89,"line":104},[87,8838,4239],{"class":97},[87,8840,8841],{"class":97}," 构建成功",[87,8843,8844],{"class":97}," ===\n",[87,8846,8847,8850,8853],{"class":89,"line":145},[87,8848,8849],{"class":93},"输出:",[87,8851,8852],{"class":97}," service\u002Fmyapp.exe",[87,8854,8855],{"class":97},"  41M\n",[16,8857,8858],{},"一个 41MB 的 exe，内置前端页面、API 后端、SQLite 数据库（运行时初始化），拖到哪都能跑。",[16,8860,8861],{},[38,8862,8863],{},"部署对比：",[2043,8865,8866,8879],{},[2046,8867,8868],{},[2049,8869,8870,8873,8876],{},[2052,8871,8872],{},"方式",[2052,8874,8875],{},"文件数",[2052,8877,8878],{},"操作",[2059,8880,8881,8892],{},[2049,8882,8883,8886,8889],{},[2064,8884,8885],{},"旧方案",[2064,8887,8888],{},"1 exe + 1 web 目录",[2064,8890,8891],{},"解压→保持目录结构",[2049,8893,8894,8897,8900],{},[2064,8895,8896],{},"新方案",[2064,8898,8899],{},"1 个 exe",[2064,8901,8902],{},"直接扔到服务器运行",[11,8904,2041],{"id":2041},[16,8906,8907,8908,8910],{},"Go 的 ",[30,8909,8005],{}," 是一个强大的工具，但有两个隐藏行为需要注意：",[48,8912,8913,8920],{},[51,8914,8915,8916,8660,8918,8663],{},"嵌入目录时自动排除 ",[30,8917,8643],{},[30,8919,348],{},[51,8921,8922],{},"构建缓存可能导致 embed 不是最新的",[16,8924,8925],{},"对于前端+后端合体的场景，这是性价比最高的方案——不需要额外的打包工具，纯标准库搞定。",[2124,8927,8928],{},"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":83,"searchDepth":104,"depth":145,"links":8930},[8931,8932,8933,8938,8944,8945],{"id":4021,"depth":104,"text":4021},{"id":7999,"depth":104,"text":7999},{"id":8013,"depth":104,"text":8013,"children":8934},[8935,8936,8937],{"id":8016,"depth":145,"text":8017},{"id":8353,"depth":145,"text":8354},{"id":8465,"depth":145,"text":8466},{"id":8603,"depth":104,"text":8603,"children":8939},[8940,8941,8943],{"id":8606,"depth":145,"text":8607},{"id":8636,"depth":145,"text":8942},"坑二：\u002F\u002Fgo:embed 排除 _ 开头文件",{"id":8799,"depth":145,"text":8800},{"id":8819,"depth":104,"text":8819},{"id":2041,"depth":104,"text":2041},"记录将 Vue 3 前端嵌入 Go 二进制实现单 exe 发布的完整流程，以及遇到的坑和解决方案。","\u002Fposts\u002Fgo-embed-frontend\u002Fimg\u002Fcover.jpg",{},"\u002Fposts\u002Fgo-embed-frontend","2026-06-06T16:00:00",{"title":7982,"description":8946},"posts\u002Fgo-embed-frontend\u002Findex",[2158,8954,8955,8956],"Vue","编译","埋坑","2026-06-06","SIQNL24wVAFb18wIGWSB3lNVZUQ9B76jWgQ55TASTYk",{"id":8960,"title":8961,"author":6,"body":8962,"description":10358,"difficulty":3367,"draft":2148,"extension":2149,"image":10359,"meta":10360,"navigation":141,"path":10361,"pinned":2148,"published":10362,"seo":10363,"series":6,"seriesOrder":6,"stem":10364,"tags":10365,"updated":10362,"__hash__":10367},"posts\u002Fposts\u002Fjira-cli-intro\u002Findex.md","jira_cli — 把企业 Jira 装进命令行，AI Agent 直接调用",{"type":8,"value":8963,"toc":10337},[8964,8968,8983,9050,9053,9056,9133,9135,9168,9175,9178,9182,9294,9298,9429,9433,9501,9504,9567,9570,9572,9576,9579,9599,9602,9617,9620,9624,9630,9768,9779,9810,9814,9817,9914,9918,9921,10039,10043,10046,10110,10113,10117,10120,10188,10191,10197,10200,10234,10241,10244,10304,10306,10309,10326,10334],[11,8965,8967],{"id":8966},"jira_cli-是什么","jira_cli 是什么",[16,8969,8970,8975,8976,8660,8979,8982],{},[3974,8971,8974],{"href":8972,"rel":8973},"https:\u002F\u002Fgithub.com\u002Fwishesl\u002Fjira_cli",[3978],"jira_cli"," 是一个面向企业 Jira（Server \u002F Data Center）的本地命令行工具，Go 语言编写，MIT 协议开源。它同时提供 ",[38,8977,8978],{},"Cobra CLI",[38,8980,8981],{},"MCP stdio server"," 两种入口，既能当日常命令行用，又能直接接入 AI 编程工具。",[78,8984,8986],{"className":80,"code":8985,"language":82,"meta":83,"style":83},"# 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",[30,8987,8988,8993,9006,9017,9032,9036,9041],{"__ignoreMap":83},[87,8989,8990],{"class":89,"line":90},[87,8991,8992],{"class":173},"# CLI 日常使用\n",[87,8994,8995,8997,9000,9003],{"class":89,"line":104},[87,8996,8974],{"class":93},[87,8998,8999],{"class":97}," issue",[87,9001,9002],{"class":97}," search",[87,9004,9005],{"class":97}," \"project = PROJ ORDER BY updated DESC\"\n",[87,9007,9008,9010,9012,9014],{"class":89,"line":145},[87,9009,8974],{"class":93},[87,9011,8999],{"class":97},[87,9013,98],{"class":97},[87,9015,9016],{"class":97}," PROJ-123\n",[87,9018,9019,9021,9023,9026,9029],{"class":89,"line":164},[87,9020,8974],{"class":93},[87,9022,8999],{"class":97},[87,9024,9025],{"class":97}," comment",[87,9027,9028],{"class":97}," PROJ-123",[87,9030,9031],{"class":97}," \"处理完成，请测试验证\"\n",[87,9033,9034],{"class":89,"line":177},[87,9035,142],{"emptyLinePlaceholder":141},[87,9037,9038],{"class":89,"line":189},[87,9039,9040],{"class":173},"# MCP 模式 — 给 AI 装上操作 Jira 的手\n",[87,9042,9043,9045,9047],{"class":89,"line":201},[87,9044,8974],{"class":93},[87,9046,360],{"class":97},[87,9048,9049],{"class":415}," --user=work\n",[11,9051,9052],{"id":9052},"为什么需要它",[16,9054,9055],{},"大多数 Jira 命令行工具只解决\"开发者手动操作 Jira\"这一个场景。但 2026 年 AI 编程工具已经普及，你的 AI Agent（Claude Code、Codex、Cursor 等）也需要读写 Jira。痛点就来了：",[2043,9057,9058,9068],{},[2046,9059,9060],{},[2049,9061,9062,9065],{},[2052,9063,9064],{},"痛点",[2052,9066,9067],{},"jira_cli 怎么解",[2059,9069,9070,9078,9086,9098,9110,9121],{},[2049,9071,9072,9075],{},[2064,9073,9074],{},"企业 Jira 在内网，没有公网 API",[2064,9076,9077],{},"本地 stdio MCP server，不经过外网",[2049,9079,9080,9083],{},[2064,9081,9082],{},"AI 可能误删、误写 Jira 数据",[2064,9084,9085],{},"账号级只读策略，AI 只能查不能改",[2049,9087,9088,9091],{},[2064,9089,9090],{},"团队多人多 Jira 实例",[2064,9092,9093,9094,9097],{},"多 Profile 管理，",[30,9095,9096],{},"--user=work"," 切换",[2049,9099,9100,9103],{},[2064,9101,9102],{},"Windows 终端体验差",[2064,9104,9105,9106,9109],{},"原生 ",[30,9107,9108],{},".exe"," 构建 + 一键安装脚本",[2049,9111,9112,9115],{},[2064,9113,9114],{},"AI 生成 JQL 困难",[2064,9116,9117,9118],{},"字段元数据发现：",[30,9119,9120],{},"field list --query xxx",[2049,9122,9123,9126],{},[2064,9124,9125],{},"MCP 直接拿原始 JSON 太冗长",[2064,9127,9128,9129,9132],{},"紧凑视图 + ",[30,9130,9131],{},"--view summary"," 减少 token 消耗",[11,9134,7345],{"id":7345},[78,9136,9138],{"className":80,"code":9137,"language":82,"meta":83,"style":83},"# 从源码安装（要求 Go 1.26.3+）\ngo install github.com\u002Fwishesl\u002Fjira_cli@latest\n\n# 或下载预编译二进制\n# https:\u002F\u002Fgithub.com\u002Fwishesl\u002Fjira_cli\u002Freleases\n",[30,9139,9140,9145,9154,9158,9163],{"__ignoreMap":83},[87,9141,9142],{"class":89,"line":90},[87,9143,9144],{"class":173},"# 从源码安装（要求 Go 1.26.3+）\n",[87,9146,9147,9149,9151],{"class":89,"line":104},[87,9148,94],{"class":93},[87,9150,2251],{"class":97},[87,9152,9153],{"class":97}," github.com\u002Fwishesl\u002Fjira_cli@latest\n",[87,9155,9156],{"class":89,"line":145},[87,9157,142],{"emptyLinePlaceholder":141},[87,9159,9160],{"class":89,"line":164},[87,9161,9162],{"class":173},"# 或下载预编译二进制\n",[87,9164,9165],{"class":89,"line":177},[87,9166,9167],{"class":173},"# https:\u002F\u002Fgithub.com\u002Fwishesl\u002Fjira_cli\u002Freleases\n",[16,9169,9170,9171,9174],{},"Windows 用户下载 zip 包解压后双击 ",[30,9172,9173],{},"install.bat"," 即可全局安装，自动加入 PATH。",[11,9176,9177],{"id":9177},"快速上手",[73,9179,9181],{"id":9180},"_1-配置认证","1. 配置认证",[78,9183,9185],{"className":80,"code":9184,"language":82,"meta":83,"style":83},"# 设置 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",[30,9186,9187,9192,9208,9212,9217,9237,9241,9246,9275,9279,9284],{"__ignoreMap":83},[87,9188,9189],{"class":89,"line":90},[87,9190,9191],{"class":173},"# 设置 Jira 地址\n",[87,9193,9194,9196,9199,9202,9205],{"class":89,"line":104},[87,9195,8974],{"class":93},[87,9197,9198],{"class":97}," set",[87,9200,9201],{"class":97}," base-url",[87,9203,9204],{"class":97}," https:\u002F\u002Fjira.your-company.com",[87,9206,9207],{"class":415}," --global\n",[87,9209,9210],{"class":89,"line":145},[87,9211,142],{"emptyLinePlaceholder":141},[87,9213,9214],{"class":89,"line":164},[87,9215,9216],{"class":173},"# Token 认证（推荐）\n",[87,9218,9219,9221,9223,9226,9228,9231,9233,9235],{"class":89,"line":177},[87,9220,8974],{"class":93},[87,9222,9198],{"class":97},[87,9224,9225],{"class":97}," token",[87,9227,2693],{"class":126},[87,9229,9230],{"class":97},"your-toke",[87,9232,3095],{"class":148},[87,9234,2702],{"class":126},[87,9236,9207],{"class":415},[87,9238,9239],{"class":89,"line":189},[87,9240,142],{"emptyLinePlaceholder":141},[87,9242,9243],{"class":89,"line":201},[87,9244,9245],{"class":173},"# 或用户名密码认证\n",[87,9247,9248,9250,9252,9254,9256,9259,9261,9263,9265,9268,9271,9273],{"class":89,"line":211},[87,9249,8974],{"class":93},[87,9251,9198],{"class":97},[87,9253,7419],{"class":97},[87,9255,2693],{"class":126},[87,9257,9258],{"class":97},"usernam",[87,9260,3081],{"class":148},[87,9262,2702],{"class":126},[87,9264,2693],{"class":126},[87,9266,9267],{"class":97},"passwor",[87,9269,9270],{"class":148},"d",[87,9272,2702],{"class":126},[87,9274,9207],{"class":415},[87,9276,9277],{"class":89,"line":228},[87,9278,142],{"emptyLinePlaceholder":141},[87,9280,9281],{"class":89,"line":390},[87,9282,9283],{"class":173},"# 验证一下\n",[87,9285,9286,9288,9291],{"class":89,"line":396},[87,9287,8974],{"class":93},[87,9289,9290],{"class":97}," user",[87,9292,9293],{"class":97}," me\n",[73,9295,9297],{"id":9296},"_2-日常使用","2. 日常使用",[78,9299,9301],{"className":80,"code":9300,"language":82,"meta":83,"style":83},"# 查自己今天的任务\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",[30,9302,9303,9308,9319,9323,9328,9338,9342,9347,9361,9378,9382,9387,9400,9404,9409],{"__ignoreMap":83},[87,9304,9305],{"class":89,"line":90},[87,9306,9307],{"class":173},"# 查自己今天的任务\n",[87,9309,9310,9312,9314,9316],{"class":89,"line":104},[87,9311,8974],{"class":93},[87,9313,8999],{"class":97},[87,9315,9002],{"class":97},[87,9317,9318],{"class":97}," \"assignee = currentUser() ORDER BY updated DESC\"\n",[87,9320,9321],{"class":89,"line":145},[87,9322,142],{"emptyLinePlaceholder":141},[87,9324,9325],{"class":89,"line":164},[87,9326,9327],{"class":173},"# 看某个任务详情\n",[87,9329,9330,9332,9334,9336],{"class":89,"line":177},[87,9331,8974],{"class":93},[87,9333,8999],{"class":97},[87,9335,98],{"class":97},[87,9337,9016],{"class":97},[87,9339,9340],{"class":89,"line":189},[87,9341,142],{"emptyLinePlaceholder":141},[87,9343,9344],{"class":89,"line":201},[87,9345,9346],{"class":173},"# 流转状态\n",[87,9348,9349,9351,9353,9356,9358],{"class":89,"line":211},[87,9350,8974],{"class":93},[87,9352,8999],{"class":97},[87,9354,9355],{"class":97}," transitions",[87,9357,9028],{"class":97},[87,9359,9360],{"class":173},"                    # 可以转哪些\n",[87,9362,9363,9365,9367,9370,9372,9375],{"class":89,"line":228},[87,9364,8974],{"class":93},[87,9366,8999],{"class":97},[87,9368,9369],{"class":97}," do-transition",[87,9371,9028],{"class":97},[87,9373,9374],{"class":415}," 21",[87,9376,9377],{"class":173},"               # 执行流转\n",[87,9379,9380],{"class":89,"line":390},[87,9381,142],{"emptyLinePlaceholder":141},[87,9383,9384],{"class":89,"line":396},[87,9385,9386],{"class":173},"# 添加备注\n",[87,9388,9389,9391,9393,9395,9397],{"class":89,"line":419},[87,9390,8974],{"class":93},[87,9392,8999],{"class":97},[87,9394,9025],{"class":97},[87,9396,9028],{"class":97},[87,9398,9399],{"class":97}," \"已定位到问题，正在修复\"\n",[87,9401,9402],{"class":89,"line":617},[87,9403,142],{"emptyLinePlaceholder":141},[87,9405,9406],{"class":89,"line":623},[87,9407,9408],{"class":173},"# 创建任务\n",[87,9410,9411,9413,9415,9417,9420,9423,9426],{"class":89,"line":629},[87,9412,8974],{"class":93},[87,9414,8999],{"class":97},[87,9416,7305],{"class":97},[87,9418,9419],{"class":97}," PROJ",[87,9421,9422],{"class":97}," Task",[87,9424,9425],{"class":415}," --fields",[87,9427,9428],{"class":97}," @fields.json\n",[73,9430,9432],{"id":9431},"_3-配给-ai-使用","3. 配给 AI 使用",[78,9434,9436],{"className":80,"code":9435,"language":82,"meta":83,"style":83},"# 先配置一个只读账号给 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",[30,9437,9438,9443,9460,9483],{"__ignoreMap":83},[87,9439,9440],{"class":89,"line":90},[87,9441,9442],{"class":173},"# 先配置一个只读账号给 AI 用\n",[87,9444,9445,9447,9449,9451,9453,9455,9458],{"class":89,"line":104},[87,9446,8974],{"class":93},[87,9448,9198],{"class":97},[87,9450,9201],{"class":97},[87,9452,9204],{"class":97},[87,9454,2690],{"class":415},[87,9456,9457],{"class":97}," ai",[87,9459,9207],{"class":415},[87,9461,9462,9464,9466,9468,9470,9473,9475,9477,9479,9481],{"class":89,"line":145},[87,9463,8974],{"class":93},[87,9465,9198],{"class":97},[87,9467,9225],{"class":97},[87,9469,2693],{"class":126},[87,9471,9472],{"class":97},"toke",[87,9474,3095],{"class":148},[87,9476,2702],{"class":126},[87,9478,2690],{"class":415},[87,9480,9457],{"class":97},[87,9482,9207],{"class":415},[87,9484,9485,9487,9489,9492,9495,9497,9499],{"class":89,"line":164},[87,9486,8974],{"class":93},[87,9488,9198],{"class":97},[87,9490,9491],{"class":97}," readonly",[87,9493,9494],{"class":97}," on",[87,9496,2690],{"class":415},[87,9498,9457],{"class":97},[87,9500,9207],{"class":415},[16,9502,9503],{},"然后在 AI 工具的 MCP 配置里加上：",[78,9505,9507],{"className":1968,"code":9506,"language":1970,"meta":83,"style":83},"{\n  \"mcpServers\": {\n    \"jira\": {\n      \"command\": \"jira_cli\",\n      \"args\": [\"mcp\", \"--user=ai\"]\n    }\n  }\n}\n",[30,9508,9509,9513,9519,9526,9538,9555,9559,9563],{"__ignoreMap":83},[87,9510,9511],{"class":89,"line":90},[87,9512,1977],{"class":148},[87,9514,9515,9517],{"class":89,"line":104},[87,9516,1982],{"class":415},[87,9518,1985],{"class":148},[87,9520,9521,9524],{"class":89,"line":145},[87,9522,9523],{"class":415},"    \"jira\"",[87,9525,1985],{"class":148},[87,9527,9528,9531,9533,9536],{"class":89,"line":164},[87,9529,9530],{"class":415},"      \"command\"",[87,9532,1025],{"class":148},[87,9534,9535],{"class":97},"\"jira_cli\"",[87,9537,294],{"class":148},[87,9539,9540,9543,9545,9548,9550,9553],{"class":89,"line":177},[87,9541,9542],{"class":415},"      \"args\"",[87,9544,3889],{"class":148},[87,9546,9547],{"class":97},"\"mcp\"",[87,9549,354],{"class":148},[87,9551,9552],{"class":97},"\"--user=ai\"",[87,9554,2615],{"class":148},[87,9556,9557],{"class":89,"line":189},[87,9558,620],{"class":148},[87,9560,9561],{"class":89,"line":201},[87,9562,2034],{"class":148},[87,9564,9565],{"class":89,"line":211},[87,9566,1062],{"class":148},[16,9568,9569],{},"这样 AI 就能查 Jira 但不能修改，既有了上下文，又不用担心误操作。",[11,9571,7827],{"id":7827},[73,9573,9575],{"id":9574},"_1-账号级只读策略","1. 账号级只读策略",[16,9577,9578],{},"这是 jira_cli 最实用的设计。给 AI Agent 配置只读账号：",[78,9580,9582],{"className":80,"code":9581,"language":82,"meta":83,"style":83},"jira_cli set readonly on --name ai\n",[30,9583,9584],{"__ignoreMap":83},[87,9585,9586,9588,9590,9592,9594,9596],{"class":89,"line":90},[87,9587,8974],{"class":93},[87,9589,9198],{"class":97},[87,9591,9491],{"class":97},[87,9593,9494],{"class":97},[87,9595,2690],{"class":415},[87,9597,9598],{"class":97}," ai\n",[16,9600,9601],{},"只读模式下：",[3705,9603,9604,9607,9610],{},[51,9605,9606],{},"CLI 写命令被隐藏和拒绝",[51,9608,9609],{},"MCP 的写入工具不会注册",[51,9611,9612,9613,9616],{},"环境变量 ",[30,9614,9615],{},"JIRA_READONLY=true"," 也可以临时覆盖",[16,9618,9619],{},"AI 只能查 JQL、看任务、查字段、读评论，绝对不会误改数据。需要创建\u002F更新时，另配一个有写入权限的账号由开发者自己操作。",[73,9621,9623],{"id":9622},"_2-多-profile-切换","2. 多 Profile 切换",[16,9625,9626,9627,9629],{},"一个配置文件里管理多个 Jira 账号，",[30,9628,3046],{}," 切换：",[78,9631,9633],{"className":80,"code":9632,"language":82,"meta":83,"style":83},"# 工作账号\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",[30,9634,9635,9640,9658,9681,9685,9690,9708,9731,9735,9740,9754],{"__ignoreMap":83},[87,9636,9637],{"class":89,"line":90},[87,9638,9639],{"class":173},"# 工作账号\n",[87,9641,9642,9644,9646,9648,9651,9653,9656],{"class":89,"line":104},[87,9643,8974],{"class":93},[87,9645,9198],{"class":97},[87,9647,9201],{"class":97},[87,9649,9650],{"class":97}," https:\u002F\u002Fjira.company.com",[87,9652,2690],{"class":415},[87,9654,9655],{"class":97}," work",[87,9657,9207],{"class":415},[87,9659,9660,9662,9664,9666,9668,9671,9673,9675,9677,9679],{"class":89,"line":145},[87,9661,8974],{"class":93},[87,9663,9198],{"class":97},[87,9665,9225],{"class":97},[87,9667,2693],{"class":126},[87,9669,9670],{"class":97},"work-toke",[87,9672,3095],{"class":148},[87,9674,2702],{"class":126},[87,9676,2690],{"class":415},[87,9678,9655],{"class":97},[87,9680,9207],{"class":415},[87,9682,9683],{"class":89,"line":164},[87,9684,142],{"emptyLinePlaceholder":141},[87,9686,9687],{"class":89,"line":177},[87,9688,9689],{"class":173},"# 个人账号\n",[87,9691,9692,9694,9696,9698,9701,9703,9706],{"class":89,"line":189},[87,9693,8974],{"class":93},[87,9695,9198],{"class":97},[87,9697,9201],{"class":97},[87,9699,9700],{"class":97}," https:\u002F\u002Fjira.personal.com",[87,9702,2690],{"class":415},[87,9704,9705],{"class":97}," personal",[87,9707,9207],{"class":415},[87,9709,9710,9712,9714,9716,9718,9721,9723,9725,9727,9729],{"class":89,"line":201},[87,9711,8974],{"class":93},[87,9713,9198],{"class":97},[87,9715,9225],{"class":97},[87,9717,2693],{"class":126},[87,9719,9720],{"class":97},"personal-toke",[87,9722,3095],{"class":148},[87,9724,2702],{"class":126},[87,9726,2690],{"class":415},[87,9728,9705],{"class":97},[87,9730,9207],{"class":415},[87,9732,9733],{"class":89,"line":211},[87,9734,142],{"emptyLinePlaceholder":141},[87,9736,9737],{"class":89,"line":228},[87,9738,9739],{"class":173},"# 使用时切换\n",[87,9741,9742,9744,9747,9749,9751],{"class":89,"line":390},[87,9743,8974],{"class":93},[87,9745,9746],{"class":415}," --user=work",[87,9748,8999],{"class":97},[87,9750,9002],{"class":97},[87,9752,9753],{"class":97}," \"project = PROJ\"\n",[87,9755,9756,9758,9761,9763,9765],{"class":89,"line":396},[87,9757,8974],{"class":93},[87,9759,9760],{"class":415}," --user=personal",[87,9762,8999],{"class":97},[87,9764,9002],{"class":97},[87,9766,9767],{"class":97}," \"project = SIDE\"\n",[16,9769,9770,9771,9774,9775,9778],{},"同时支持",[38,9772,9773],{},"项目级配置","（",[30,9776,9777],{},"--local","）和工作目录绑定：",[78,9780,9782],{"className":80,"code":9781,"language":82,"meta":83,"style":83},"# 在项目目录下，配置只对当前项目生效\ncd ~\u002Fprojects\u002Fbackend\njira_cli set base-url https:\u002F\u002Fjira-backend.company.com --local\n",[30,9783,9784,9789,9796],{"__ignoreMap":83},[87,9785,9786],{"class":89,"line":90},[87,9787,9788],{"class":173},"# 在项目目录下，配置只对当前项目生效\n",[87,9790,9791,9793],{"class":89,"line":104},[87,9792,8569],{"class":415},[87,9794,9795],{"class":97}," ~\u002Fprojects\u002Fbackend\n",[87,9797,9798,9800,9802,9804,9807],{"class":89,"line":145},[87,9799,8974],{"class":93},[87,9801,9198],{"class":97},[87,9803,9201],{"class":97},[87,9805,9806],{"class":97}," https:\u002F\u002Fjira-backend.company.com",[87,9808,9809],{"class":415}," --local\n",[73,9811,9813],{"id":9812},"_3-对-ai-友好的输出","3. 对 AI 友好的输出",[16,9815,9816],{},"jira_cli 在输出设计上充分考虑了 AI Agent 的 token 消耗：",[78,9818,9820],{"className":80,"code":9819,"language":82,"meta":83,"style":83},"# 紧凑视图 — 去掉 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",[30,9821,9822,9827,9843,9847,9852,9869,9873,9878,9893,9897,9902],{"__ignoreMap":83},[87,9823,9824],{"class":89,"line":90},[87,9825,9826],{"class":173},"# 紧凑视图 — 去掉 self\u002Favatar\u002F缩略图等无用字段\n",[87,9828,9829,9831,9833,9835,9837,9840],{"class":89,"line":104},[87,9830,8974],{"class":93},[87,9832,8999],{"class":97},[87,9834,98],{"class":97},[87,9836,9028],{"class":97},[87,9838,9839],{"class":415}," --view",[87,9841,9842],{"class":97}," summary\n",[87,9844,9845],{"class":89,"line":145},[87,9846,142],{"emptyLinePlaceholder":141},[87,9848,9849],{"class":89,"line":164},[87,9850,9851],{"class":173},"# 表格输出 — 一眼看到关键信息\n",[87,9853,9854,9856,9858,9860,9863,9866],{"class":89,"line":177},[87,9855,8974],{"class":93},[87,9857,8999],{"class":97},[87,9859,9002],{"class":97},[87,9861,9862],{"class":97}," \"project = PROJ\"",[87,9864,9865],{"class":415}," --output",[87,9867,9868],{"class":97}," table\n",[87,9870,9871],{"class":89,"line":189},[87,9872,142],{"emptyLinePlaceholder":141},[87,9874,9875],{"class":89,"line":201},[87,9876,9877],{"class":173},"# JSON 输出 — 程序解析，不转义 Unicode\n",[87,9879,9880,9882,9884,9887,9889,9891],{"class":89,"line":211},[87,9881,8974],{"class":93},[87,9883,9865],{"class":415},[87,9885,9886],{"class":97}," json",[87,9888,8999],{"class":97},[87,9890,98],{"class":97},[87,9892,9016],{"class":97},[87,9894,9895],{"class":89,"line":228},[87,9896,142],{"emptyLinePlaceholder":141},[87,9898,9899],{"class":89,"line":390},[87,9900,9901],{"class":173},"# 自定义搜索列 — 只要需要的字段\n",[87,9903,9904,9906,9908,9911],{"class":89,"line":396},[87,9905,8974],{"class":93},[87,9907,9198],{"class":97},[87,9909,9910],{"class":97}," search-columns",[87,9912,9913],{"class":97}," key,summary,fixVersions\n",[73,9915,9917],{"id":9916},"_4-字段元数据发现","4. 字段元数据发现",[16,9919,9920],{},"Jira 的 customfield 让人头疼，jira_cli 提供了完整的字段发现能力：",[78,9922,9924],{"className":80,"code":9923,"language":82,"meta":83,"style":83},"# 列出所有字段及其类型\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",[30,9925,9926,9931,9940,9944,9949,9963,9967,9972,9983,9987,9992,10009,10013,10018],{"__ignoreMap":83},[87,9927,9928],{"class":89,"line":90},[87,9929,9930],{"class":173},"# 列出所有字段及其类型\n",[87,9932,9933,9935,9938],{"class":89,"line":104},[87,9934,8974],{"class":93},[87,9936,9937],{"class":97}," field",[87,9939,4201],{"class":97},[87,9941,9942],{"class":89,"line":145},[87,9943,142],{"emptyLinePlaceholder":141},[87,9945,9946],{"class":89,"line":164},[87,9947,9948],{"class":173},"# 搜索包含特定关键词的字段\n",[87,9950,9951,9953,9955,9957,9960],{"class":89,"line":177},[87,9952,8974],{"class":93},[87,9954,9937],{"class":97},[87,9956,7240],{"class":97},[87,9958,9959],{"class":415}," --query",[87,9961,9962],{"class":97}," \"版本\"\n",[87,9964,9965],{"class":89,"line":189},[87,9966,142],{"emptyLinePlaceholder":141},[87,9968,9969],{"class":89,"line":201},[87,9970,9971],{"class":173},"# 只看自定义字段\n",[87,9973,9974,9976,9978,9980],{"class":89,"line":211},[87,9975,8974],{"class":93},[87,9977,9937],{"class":97},[87,9979,7240],{"class":97},[87,9981,9982],{"class":415}," --custom-only\n",[87,9984,9985],{"class":89,"line":228},[87,9986,142],{"emptyLinePlaceholder":141},[87,9988,9989],{"class":89,"line":390},[87,9990,9991],{"class":173},"# 创建任务前，先看看需要哪些字段\n",[87,9993,9994,9996,9998,10001,10003,10006],{"class":89,"line":396},[87,9995,8974],{"class":93},[87,9997,8999],{"class":97},[87,9999,10000],{"class":97}," create-meta",[87,10002,9419],{"class":97},[87,10004,10005],{"class":415}," --issue-type",[87,10007,10008],{"class":97}," Task\n",[87,10010,10011],{"class":89,"line":419},[87,10012,142],{"emptyLinePlaceholder":141},[87,10014,10015],{"class":89,"line":617},[87,10016,10017],{"class":173},"# 分页查看可选值\n",[87,10019,10020,10022,10024,10027,10029,10031,10034,10036],{"class":89,"line":623},[87,10021,8974],{"class":93},[87,10023,8999],{"class":97},[87,10025,10026],{"class":97}," create-meta-values",[87,10028,9419],{"class":97},[87,10030,9422],{"class":97},[87,10032,10033],{"class":97}," priority",[87,10035,9959],{"class":415},[87,10037,10038],{"class":97}," High\n",[73,10040,10042],{"id":10041},"_5-mcp-写入控制","5. MCP 写入控制",[16,10044,10045],{},"除了全局只读，MCP 还有更细粒度的控制：",[2043,10047,10048,10058],{},[2046,10049,10050],{},[2049,10051,10052,10055],{},[2052,10053,10054],{},"环境变量",[2052,10056,10057],{},"作用",[2059,10059,10060,10070,10080,10090,10100],{},[2049,10061,10062,10067],{},[2064,10063,10064],{},[30,10065,10066],{},"JIRA_CREATE_OPEN=false",[2064,10068,10069],{},"禁用创建\u002F更新工具",[2049,10071,10072,10077],{},[2064,10073,10074],{},[30,10075,10076],{},"JIRA_DISABLE_DISCLAIMER=true",[2064,10078,10079],{},"去掉 AI 自动添加的备注声明",[2049,10081,10082,10087],{},[2064,10083,10084],{},[30,10085,10086],{},"JIRA_CREATE_SUMMARY_MAX",[2064,10088,10089],{},"限制摘要长度",[2049,10091,10092,10097],{},[2064,10093,10094],{},[30,10095,10096],{},"JIRA_CREATE_DESCRIPTION_MAX",[2064,10098,10099],{},"限制描述长度",[2049,10101,10102,10107],{},[2064,10103,10104],{},[30,10105,10106],{},"JIRA_CREATE_FIELDS_MAX_BYTES",[2064,10108,10109],{},"限制创建字段的 JSON 大小",[16,10111,10112],{},"这些限制确保 AI 即使有写入权限也不会\"失控\"——例如防止 AI 往描述里塞几万字的废话。",[73,10114,10116],{"id":10115},"_6-中英双语帮助","6. 中英双语帮助",[16,10118,10119],{},"帮助信息默认中文，也支持一键切英文：",[78,10121,10123],{"className":80,"code":10122,"language":82,"meta":83,"style":83},"# 保存英文偏好\njira_cli set help-language en\n\n# 切回中文\njira_cli set help-language zh\n\n# 临时覆盖\njira_cli --lang en issue search -h\n",[30,10124,10125,10130,10142,10146,10151,10162,10166,10171],{"__ignoreMap":83},[87,10126,10127],{"class":89,"line":90},[87,10128,10129],{"class":173},"# 保存英文偏好\n",[87,10131,10132,10134,10136,10139],{"class":89,"line":104},[87,10133,8974],{"class":93},[87,10135,9198],{"class":97},[87,10137,10138],{"class":97}," help-language",[87,10140,10141],{"class":97}," en\n",[87,10143,10144],{"class":89,"line":145},[87,10145,142],{"emptyLinePlaceholder":141},[87,10147,10148],{"class":89,"line":164},[87,10149,10150],{"class":173},"# 切回中文\n",[87,10152,10153,10155,10157,10159],{"class":89,"line":177},[87,10154,8974],{"class":93},[87,10156,9198],{"class":97},[87,10158,10138],{"class":97},[87,10160,10161],{"class":97}," zh\n",[87,10163,10164],{"class":89,"line":189},[87,10165,142],{"emptyLinePlaceholder":141},[87,10167,10168],{"class":89,"line":201},[87,10169,10170],{"class":173},"# 临时覆盖\n",[87,10172,10173,10175,10178,10181,10183,10185],{"class":89,"line":211},[87,10174,8974],{"class":93},[87,10176,10177],{"class":415}," --lang",[87,10179,10180],{"class":97}," en",[87,10182,8999],{"class":97},[87,10184,9002],{"class":97},[87,10186,10187],{"class":415}," -h\n",[11,10189,10190],{"id":10190},"架构一览",[78,10192,10195],{"className":10193,"code":10194,"language":3474},[3472],"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",[30,10196,10194],{"__ignoreMap":83},[16,10198,10199],{},"设计原则清晰：",[3705,10201,10202,10210,10218,10226],{},[51,10203,10204,10209],{},[38,10205,10206],{},[30,10207,10208],{},"pkg\u002Fjira"," 是纯 Jira 客户端，HTTP、认证、上下文处理都在这里",[51,10211,10212,10217],{},[38,10213,10214],{},[30,10215,10216],{},"cmd"," 只管 Cobra 解析和呈现",[51,10219,10220,10225],{},[38,10221,10222],{},[30,10223,10224],{},"internal\u002Ftools\u002Fjira_tools"," 只管 MCP schema 验证和结果适配",[51,10227,10228,10233],{},[38,10229,10230],{},[30,10231,10232],{},"internal\u002Fconfig"," 拥有 Profile 的全部逻辑，不分散到 cmd",[16,10235,10236,10237,10240],{},"每一层职责单一，新增 Jira 能力时按 ",[30,10238,10239],{},"endpoints.go → Client 方法 → CLI 命令 → MCP tool → 策略注册"," 的路径走就行。",[11,10242,10243],{"id":10243},"和已有方案的对比",[2043,10245,10246,10258],{},[2046,10247,10248],{},[2049,10249,10250,10252,10255],{},[2052,10251,4816],{},[2052,10253,10254],{},"适合场景",[2052,10256,10257],{},"jira_cli 的区别",[2059,10259,10260,10271,10282,10293],{},[2049,10261,10262,10265,10268],{},[2064,10263,10264],{},"Atlassian Rovo MCP Server",[2064,10266,10267],{},"Jira Cloud、官方 OAuth",[2064,10269,10270],{},"jira_cli 是本地 stdio，适合内网 Server\u002FDC",[2049,10272,10273,10276,10279],{},[2064,10274,10275],{},"go-jira \u002F jira-cli 社区版",[2064,10277,10278],{},"纯命令行管理",[2064,10280,10281],{},"jira_cli 同时提供 MCP server，面向 AI 工作流",[2049,10283,10284,10287,10290],{},[2064,10285,10286],{},"直接调 REST API",[2064,10288,10289],{},"自定义集成",[2064,10291,10292],{},"jira_cli 封装了认证、只读策略、字段元数据",[2049,10294,10295,10298,10301],{},[2064,10296,10297],{},"Jira 网页",[2064,10299,10300],{},"日常使用",[2064,10302,10303],{},"jira_cli 把 Jira 变成终端里的一行命令",[11,10305,2041],{"id":2041},[16,10307,10308],{},"如果你的团队满足以下任一条件，jira_cli 值得一试：",[3705,10310,10311,10314,10317,10320,10323],{},[51,10312,10313],{},"企业 Jira 部署在内网，想接入 AI 编程工具",[51,10315,10316],{},"想给 AI Agent 只读权限来查 Jira 上下文",[51,10318,10319],{},"需要命令行快速查看\u002F操作 Jira，不想切浏览器",[51,10321,10322],{},"需要管理多个 Jira 实例和多套账号",[51,10324,10325],{},"Windows 用户想要无摩擦的 CLI 体验",[16,10327,10328,10329,10333],{},"GitHub：",[3974,10330,10332],{"href":8972,"rel":10331},[3978],"github.com\u002Fwishesl\u002Fjira_cli"," | MIT 协议 | Go 1.26.3+",[2124,10335,10336],{},"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":83,"searchDepth":104,"depth":145,"links":10338},[10339,10340,10341,10342,10347,10355,10356,10357],{"id":8966,"depth":104,"text":8967},{"id":9052,"depth":104,"text":9052},{"id":7345,"depth":104,"text":7345},{"id":9177,"depth":104,"text":9177,"children":10343},[10344,10345,10346],{"id":9180,"depth":145,"text":9181},{"id":9296,"depth":145,"text":9297},{"id":9431,"depth":145,"text":9432},{"id":7827,"depth":104,"text":7827,"children":10348},[10349,10350,10351,10352,10353,10354],{"id":9574,"depth":145,"text":9575},{"id":9622,"depth":145,"text":9623},{"id":9812,"depth":145,"text":9813},{"id":9916,"depth":145,"text":9917},{"id":10041,"depth":145,"text":10042},{"id":10115,"depth":145,"text":10116},{"id":10190,"depth":104,"text":10190},{"id":10243,"depth":104,"text":10243},{"id":2041,"depth":104,"text":2041},"介绍 jira_cli，一个开源的企业 Jira CLI 命令行工具和MCP，支持 Jira Server \u002F Data Center，自带 MCP stdio server，让 Claude Code、Codex 等 AI 编程工具直接读写 Jira 任务。","\u002Fposts\u002Fjira-cli-intro\u002Fimg\u002Fcover.svg",{},"\u002Fposts\u002Fjira-cli-intro","2026-07-17",{"title":8961,"description":10358},"posts\u002Fjira-cli-intro\u002Findex",[10366,7977,2159,2158,2161,7978],"Jira","V1-ms-ndMO09C96sfR-xfjgfdErA4lWoy1XiFen6Izw",1784303541089]