[{"data":1,"prerenderedAt":12319},["ShallowReactive",2],{"articles":3},[4,1806,4984,6774,10262,11117],{"_path":5,"_dir":6,"_draft":7,"_partial":7,"_locale":8,"title":9,"description":10,"date":11,"tags":12,"body":16,"_type":1800,"_id":1801,"_source":1802,"_file":1803,"_stem":1804,"_extension":1805},"\u002Farticles\u002Fbackend\u002Ffastapi","backend",false,"","FastAPI 入门：7 个核心概念快速上手 Python Web API","从最小程序到完整示例，系统梳理 FastAPI 的 7 个基础概念：路由、路径与查询参数、Pydantic 请求体、JSON 响应、HTTPException 错误处理与 async\u002Fdef 选择，帮助快速搭建可用于 AI 药物发现项目的接口。","2026-05-25",[13,14,15],"人工智能","软件工程","Agent开发",{"type":17,"children":18,"toc":1790},"root",[19,27,38,43,78,111,120,125,134,147,229,234,264,269,316,321,345,355,364,369,524,529,584,593,598,629,633,647,676,689,720,724,738,758,873,878,944,953,974,1029,1038,1058,1134,1139,1219,1228,1233,1302,1307,1332,1351,1359,1716,1721,1784],{"type":20,"tag":21,"props":22,"children":23},"element","p",{},[24],{"type":25,"value":26},"text","明白。FastAPI 基础可以按 7 个概念学，不要一开始就陷进项目结构。",{"type":20,"tag":28,"props":29,"children":31},"h3",{"id":30},"_1-fastapi-是什么",[32],{"type":20,"tag":33,"props":34,"children":35},"strong",{},[36],{"type":25,"value":37},"1. FastAPI 是什么",{"type":20,"tag":21,"props":39,"children":40},{},[41],{"type":25,"value":42},"FastAPI 是 Python 的 Web API 框架。你写普通 Python 函数，它帮你处理：",{"type":20,"tag":44,"props":45,"children":46},"ul",{},[47,53,58,63,68,73],{"type":20,"tag":48,"props":49,"children":50},"li",{},[51],{"type":25,"value":52},"HTTP 路由",{"type":20,"tag":48,"props":54,"children":55},{},[56],{"type":25,"value":57},"JSON 请求和响应",{"type":20,"tag":48,"props":59,"children":60},{},[61],{"type":25,"value":62},"参数校验",{"type":20,"tag":48,"props":64,"children":65},{},[66],{"type":25,"value":67},"自动 API 文档",{"type":20,"tag":48,"props":69,"children":70},{},[71],{"type":25,"value":72},"错误返回",{"type":20,"tag":48,"props":74,"children":75},{},[76],{"type":25,"value":77},"异步接口",{"type":20,"tag":21,"props":79,"children":80},{},[81,83,90,92,98,100,109],{"type":25,"value":82},"官方基础示例也是从 ",{"type":20,"tag":84,"props":85,"children":87},"code",{"className":86},[],[88],{"type":25,"value":89},"FastAPI()",{"type":25,"value":91},"、路由装饰器、启动服务、访问 ",{"type":20,"tag":84,"props":93,"children":95},{"className":94},[],[96],{"type":25,"value":97},"\u002Fdocs",{"type":25,"value":99}," 开始。参考：",{"type":20,"tag":101,"props":102,"children":106},"a",{"href":103,"rel":104},"https:\u002F\u002Ffastapi.tiangolo.com\u002Ftutorial\u002Ffirst-steps\u002F",[105],"nofollow",[107],{"type":25,"value":108},"FastAPI First Steps",{"type":25,"value":110},"。",{"type":20,"tag":28,"props":112,"children":114},{"id":113},"_2-最小程序",[115],{"type":20,"tag":33,"props":116,"children":117},{},[118],{"type":25,"value":119},"2. 最小程序",{"type":20,"tag":21,"props":121,"children":122},{},[123],{"type":25,"value":124},"下载依赖：\nuvicorn：服务器",{"type":20,"tag":126,"props":127,"children":129},"pre",{"code":128},"uv install python 3.11\nuv add 'fastapi[standard]@latest'\nuv add 'uvicorn[standard]'\n",[130],{"type":20,"tag":84,"props":131,"children":132},{"__ignoreMap":8},[133],{"type":25,"value":128},{"type":20,"tag":21,"props":135,"children":136},{},[137,139,145],{"type":25,"value":138},"新建 ",{"type":20,"tag":84,"props":140,"children":142},{"className":141},[],[143],{"type":25,"value":144},"main.py",{"type":25,"value":146},"：",{"type":20,"tag":126,"props":148,"children":152},{"code":149,"language":150,"meta":8,"className":151,"style":8},"from fastapi import FastAPI\n\napp = FastAPI()\n\n\n@app.get(\"\u002F\")\ndef read_root():\n    return {\"message\": \"Hello FastAPI\"}\n","python","language-python shiki shiki-themes github-dark",[153],{"type":20,"tag":84,"props":154,"children":155},{"__ignoreMap":8},[156,167,177,186,194,202,211,220],{"type":20,"tag":157,"props":158,"children":161},"span",{"class":159,"line":160},"line",1,[162],{"type":20,"tag":157,"props":163,"children":164},{},[165],{"type":25,"value":166},"from fastapi import FastAPI\n",{"type":20,"tag":157,"props":168,"children":170},{"class":159,"line":169},2,[171],{"type":20,"tag":157,"props":172,"children":174},{"emptyLinePlaceholder":173},true,[175],{"type":25,"value":176},"\n",{"type":20,"tag":157,"props":178,"children":180},{"class":159,"line":179},3,[181],{"type":20,"tag":157,"props":182,"children":183},{},[184],{"type":25,"value":185},"app = FastAPI()\n",{"type":20,"tag":157,"props":187,"children":189},{"class":159,"line":188},4,[190],{"type":20,"tag":157,"props":191,"children":192},{"emptyLinePlaceholder":173},[193],{"type":25,"value":176},{"type":20,"tag":157,"props":195,"children":197},{"class":159,"line":196},5,[198],{"type":20,"tag":157,"props":199,"children":200},{"emptyLinePlaceholder":173},[201],{"type":25,"value":176},{"type":20,"tag":157,"props":203,"children":205},{"class":159,"line":204},6,[206],{"type":20,"tag":157,"props":207,"children":208},{},[209],{"type":25,"value":210},"@app.get(\"\u002F\")\n",{"type":20,"tag":157,"props":212,"children":214},{"class":159,"line":213},7,[215],{"type":20,"tag":157,"props":216,"children":217},{},[218],{"type":25,"value":219},"def read_root():\n",{"type":20,"tag":157,"props":221,"children":223},{"class":159,"line":222},8,[224],{"type":20,"tag":157,"props":225,"children":226},{},[227],{"type":25,"value":228},"    return {\"message\": \"Hello FastAPI\"}\n",{"type":20,"tag":21,"props":230,"children":231},{},[232],{"type":25,"value":233},"启动：",{"type":20,"tag":126,"props":235,"children":239},{"code":236,"language":237,"meta":8,"className":238,"style":8},"uvicorn main:app --reload\n","bash","language-bash shiki shiki-themes github-dark",[240],{"type":20,"tag":84,"props":241,"children":242},{"__ignoreMap":8},[243],{"type":20,"tag":157,"props":244,"children":245},{"class":159,"line":160},[246,252,258],{"type":20,"tag":157,"props":247,"children":249},{"style":248},"--shiki-default:#B392F0",[250],{"type":25,"value":251},"uvicorn",{"type":20,"tag":157,"props":253,"children":255},{"style":254},"--shiki-default:#9ECBFF",[256],{"type":25,"value":257}," main:app",{"type":20,"tag":157,"props":259,"children":261},{"style":260},"--shiki-default:#79B8FF",[262],{"type":25,"value":263}," --reload\n",{"type":20,"tag":21,"props":265,"children":266},{},[267],{"type":25,"value":268},"含义：",{"type":20,"tag":44,"props":270,"children":271},{},[272,288,305],{"type":20,"tag":48,"props":273,"children":274},{},[275,281,283],{"type":20,"tag":84,"props":276,"children":278},{"className":277},[],[279],{"type":25,"value":280},"main",{"type":25,"value":282},"：文件名 ",{"type":20,"tag":84,"props":284,"children":286},{"className":285},[],[287],{"type":25,"value":144},{"type":20,"tag":48,"props":289,"children":290},{},[291,297,299],{"type":20,"tag":84,"props":292,"children":294},{"className":293},[],[295],{"type":25,"value":296},"app",{"type":25,"value":298},"：代码里的 ",{"type":20,"tag":84,"props":300,"children":302},{"className":301},[],[303],{"type":25,"value":304},"app = FastAPI()",{"type":20,"tag":48,"props":306,"children":307},{},[308,314],{"type":20,"tag":84,"props":309,"children":311},{"className":310},[],[312],{"type":25,"value":313},"--reload",{"type":25,"value":315},"：开发模式，代码变化自动重启",{"type":20,"tag":21,"props":317,"children":318},{},[319],{"type":25,"value":320},"访问：",{"type":20,"tag":126,"props":322,"children":325},{"code":323,"language":25,"meta":8,"className":324,"style":8},"http:\u002F\u002F127.0.0.1:8000\u002F\nhttp:\u002F\u002F127.0.0.1:8000\u002Fdocs\n","language-text shiki shiki-themes github-dark",[326],{"type":20,"tag":84,"props":327,"children":328},{"__ignoreMap":8},[329,337],{"type":20,"tag":157,"props":330,"children":331},{"class":159,"line":160},[332],{"type":20,"tag":157,"props":333,"children":334},{},[335],{"type":25,"value":336},"http:\u002F\u002F127.0.0.1:8000\u002F\n",{"type":20,"tag":157,"props":338,"children":339},{"class":159,"line":169},[340],{"type":20,"tag":157,"props":341,"children":342},{},[343],{"type":25,"value":344},"http:\u002F\u002F127.0.0.1:8000\u002Fdocs\n",{"type":20,"tag":21,"props":346,"children":347},{},[348,353],{"type":20,"tag":84,"props":349,"children":351},{"className":350},[],[352],{"type":25,"value":97},{"type":25,"value":354}," 是自动生成的接口调试页面。",{"type":20,"tag":28,"props":356,"children":358},{"id":357},"_3-路由get-post-put-delete",[359],{"type":20,"tag":33,"props":360,"children":361},{},[362],{"type":25,"value":363},"3. 路由：GET \u002F POST \u002F PUT \u002F DELETE",{"type":20,"tag":21,"props":365,"children":366},{},[367],{"type":25,"value":368},"路由就是“URL + HTTP 方法”。",{"type":20,"tag":126,"props":370,"children":372},{"code":371,"language":150,"meta":8,"className":151,"style":8},"@app.get(\"\u002Fhealth\")\ndef health():\n    return {\"status\": \"ok\"}\n\n\n@app.post(\"\u002Fitems\")\ndef create_item():\n    return {\"message\": \"created\"}\n\n\n@app.put(\"\u002Fitems\u002F{item_id}\")\ndef update_item(item_id: int):\n    return {\"item_id\": item_id, \"message\": \"updated\"}\n\n\n@app.delete(\"\u002Fitems\u002F{item_id}\")\ndef delete_item(item_id: int):\n    return {\"item_id\": item_id, \"message\": \"deleted\"}\n",[373],{"type":20,"tag":84,"props":374,"children":375},{"__ignoreMap":8},[376,384,392,400,407,414,422,430,438,446,454,463,472,481,489,497,506,515],{"type":20,"tag":157,"props":377,"children":378},{"class":159,"line":160},[379],{"type":20,"tag":157,"props":380,"children":381},{},[382],{"type":25,"value":383},"@app.get(\"\u002Fhealth\")\n",{"type":20,"tag":157,"props":385,"children":386},{"class":159,"line":169},[387],{"type":20,"tag":157,"props":388,"children":389},{},[390],{"type":25,"value":391},"def health():\n",{"type":20,"tag":157,"props":393,"children":394},{"class":159,"line":179},[395],{"type":20,"tag":157,"props":396,"children":397},{},[398],{"type":25,"value":399},"    return {\"status\": \"ok\"}\n",{"type":20,"tag":157,"props":401,"children":402},{"class":159,"line":188},[403],{"type":20,"tag":157,"props":404,"children":405},{"emptyLinePlaceholder":173},[406],{"type":25,"value":176},{"type":20,"tag":157,"props":408,"children":409},{"class":159,"line":196},[410],{"type":20,"tag":157,"props":411,"children":412},{"emptyLinePlaceholder":173},[413],{"type":25,"value":176},{"type":20,"tag":157,"props":415,"children":416},{"class":159,"line":204},[417],{"type":20,"tag":157,"props":418,"children":419},{},[420],{"type":25,"value":421},"@app.post(\"\u002Fitems\")\n",{"type":20,"tag":157,"props":423,"children":424},{"class":159,"line":213},[425],{"type":20,"tag":157,"props":426,"children":427},{},[428],{"type":25,"value":429},"def create_item():\n",{"type":20,"tag":157,"props":431,"children":432},{"class":159,"line":222},[433],{"type":20,"tag":157,"props":434,"children":435},{},[436],{"type":25,"value":437},"    return {\"message\": \"created\"}\n",{"type":20,"tag":157,"props":439,"children":441},{"class":159,"line":440},9,[442],{"type":20,"tag":157,"props":443,"children":444},{"emptyLinePlaceholder":173},[445],{"type":25,"value":176},{"type":20,"tag":157,"props":447,"children":449},{"class":159,"line":448},10,[450],{"type":20,"tag":157,"props":451,"children":452},{"emptyLinePlaceholder":173},[453],{"type":25,"value":176},{"type":20,"tag":157,"props":455,"children":457},{"class":159,"line":456},11,[458],{"type":20,"tag":157,"props":459,"children":460},{},[461],{"type":25,"value":462},"@app.put(\"\u002Fitems\u002F{item_id}\")\n",{"type":20,"tag":157,"props":464,"children":466},{"class":159,"line":465},12,[467],{"type":20,"tag":157,"props":468,"children":469},{},[470],{"type":25,"value":471},"def update_item(item_id: int):\n",{"type":20,"tag":157,"props":473,"children":475},{"class":159,"line":474},13,[476],{"type":20,"tag":157,"props":477,"children":478},{},[479],{"type":25,"value":480},"    return {\"item_id\": item_id, \"message\": \"updated\"}\n",{"type":20,"tag":157,"props":482,"children":484},{"class":159,"line":483},14,[485],{"type":20,"tag":157,"props":486,"children":487},{"emptyLinePlaceholder":173},[488],{"type":25,"value":176},{"type":20,"tag":157,"props":490,"children":492},{"class":159,"line":491},15,[493],{"type":20,"tag":157,"props":494,"children":495},{"emptyLinePlaceholder":173},[496],{"type":25,"value":176},{"type":20,"tag":157,"props":498,"children":500},{"class":159,"line":499},16,[501],{"type":20,"tag":157,"props":502,"children":503},{},[504],{"type":25,"value":505},"@app.delete(\"\u002Fitems\u002F{item_id}\")\n",{"type":20,"tag":157,"props":507,"children":509},{"class":159,"line":508},17,[510],{"type":20,"tag":157,"props":511,"children":512},{},[513],{"type":25,"value":514},"def delete_item(item_id: int):\n",{"type":20,"tag":157,"props":516,"children":518},{"class":159,"line":517},18,[519],{"type":20,"tag":157,"props":520,"children":521},{},[522],{"type":25,"value":523},"    return {\"item_id\": item_id, \"message\": \"deleted\"}\n",{"type":20,"tag":21,"props":525,"children":526},{},[527],{"type":25,"value":528},"常见习惯：",{"type":20,"tag":44,"props":530,"children":531},{},[532,543,554,573],{"type":20,"tag":48,"props":533,"children":534},{},[535,541],{"type":20,"tag":84,"props":536,"children":538},{"className":537},[],[539],{"type":25,"value":540},"GET",{"type":25,"value":542},"：查询",{"type":20,"tag":48,"props":544,"children":545},{},[546,552],{"type":20,"tag":84,"props":547,"children":549},{"className":548},[],[550],{"type":25,"value":551},"POST",{"type":25,"value":553},"：创建或执行动作",{"type":20,"tag":48,"props":555,"children":556},{},[557,563,565,571],{"type":20,"tag":84,"props":558,"children":560},{"className":559},[],[561],{"type":25,"value":562},"PUT",{"type":25,"value":564}," \u002F ",{"type":20,"tag":84,"props":566,"children":568},{"className":567},[],[569],{"type":25,"value":570},"PATCH",{"type":25,"value":572},"：更新",{"type":20,"tag":48,"props":574,"children":575},{},[576,582],{"type":20,"tag":84,"props":577,"children":579},{"className":578},[],[580],{"type":25,"value":581},"DELETE",{"type":25,"value":583},"：删除",{"type":20,"tag":28,"props":585,"children":587},{"id":586},"_4-参数路径参数查询参数请求体",[588],{"type":20,"tag":33,"props":589,"children":590},{},[591],{"type":25,"value":592},"4. 参数：路径参数、查询参数、请求体",{"type":20,"tag":21,"props":594,"children":595},{},[596],{"type":25,"value":597},"路径参数来自 URL 中间(常用于查询单个 item)：",{"type":20,"tag":126,"props":599,"children":601},{"code":600,"language":150,"meta":8,"className":151,"style":8},"@app.get(\"\u002Fitems\u002F{item_id}\")\ndef get_item(item_id: int):\n    return {\"item_id\": item_id}\n",[602],{"type":20,"tag":84,"props":603,"children":604},{"__ignoreMap":8},[605,613,621],{"type":20,"tag":157,"props":606,"children":607},{"class":159,"line":160},[608],{"type":20,"tag":157,"props":609,"children":610},{},[611],{"type":25,"value":612},"@app.get(\"\u002Fitems\u002F{item_id}\")\n",{"type":20,"tag":157,"props":614,"children":615},{"class":159,"line":169},[616],{"type":20,"tag":157,"props":617,"children":618},{},[619],{"type":25,"value":620},"def get_item(item_id: int):\n",{"type":20,"tag":157,"props":622,"children":623},{"class":159,"line":179},[624],{"type":20,"tag":157,"props":625,"children":626},{},[627],{"type":25,"value":628},"    return {\"item_id\": item_id}\n",{"type":20,"tag":21,"props":630,"children":631},{},[632],{"type":25,"value":320},{"type":20,"tag":126,"props":634,"children":636},{"code":635,"language":25,"meta":8,"className":324,"style":8},"\u002Fitems\u002F123\n",[637],{"type":20,"tag":84,"props":638,"children":639},{"__ignoreMap":8},[640],{"type":20,"tag":157,"props":641,"children":642},{"class":159,"line":160},[643],{"type":20,"tag":157,"props":644,"children":645},{},[646],{"type":25,"value":635},{"type":20,"tag":21,"props":648,"children":649},{},[650,652,658,660,666,668,674],{"type":25,"value":651},"FastAPI 会自动把 ",{"type":20,"tag":84,"props":653,"children":655},{"className":654},[],[656],{"type":25,"value":657},"\"123\"",{"type":25,"value":659}," 转成 ",{"type":20,"tag":84,"props":661,"children":663},{"className":662},[],[664],{"type":25,"value":665},"int",{"type":25,"value":667},"。如果传 ",{"type":20,"tag":84,"props":669,"children":671},{"className":670},[],[672],{"type":25,"value":673},"\u002Fitems\u002Fabc",{"type":25,"value":675},"，会自动报参数错误。",{"type":20,"tag":21,"props":677,"children":678},{},[679,681,687],{"type":25,"value":680},"查询参数来自 ",{"type":20,"tag":84,"props":682,"children":684},{"className":683},[],[685],{"type":25,"value":686},"?",{"type":25,"value":688}," 后面(常用于查询 item 列表)：",{"type":20,"tag":126,"props":690,"children":692},{"code":691,"language":150,"meta":8,"className":151,"style":8},"@app.get(\"\u002Fsearch\")\ndef search(q: str, limit: int = 10):\n    return {\"q\": q, \"limit\": limit}\n",[693],{"type":20,"tag":84,"props":694,"children":695},{"__ignoreMap":8},[696,704,712],{"type":20,"tag":157,"props":697,"children":698},{"class":159,"line":160},[699],{"type":20,"tag":157,"props":700,"children":701},{},[702],{"type":25,"value":703},"@app.get(\"\u002Fsearch\")\n",{"type":20,"tag":157,"props":705,"children":706},{"class":159,"line":169},[707],{"type":20,"tag":157,"props":708,"children":709},{},[710],{"type":25,"value":711},"def search(q: str, limit: int = 10):\n",{"type":20,"tag":157,"props":713,"children":714},{"class":159,"line":179},[715],{"type":20,"tag":157,"props":716,"children":717},{},[718],{"type":25,"value":719},"    return {\"q\": q, \"limit\": limit}\n",{"type":20,"tag":21,"props":721,"children":722},{},[723],{"type":25,"value":320},{"type":20,"tag":126,"props":725,"children":727},{"code":726,"language":25,"meta":8,"className":324,"style":8},"\u002Fsearch?q=aspirin&limit=5\n",[728],{"type":20,"tag":84,"props":729,"children":730},{"__ignoreMap":8},[731],{"type":20,"tag":157,"props":732,"children":733},{"class":159,"line":160},[734],{"type":20,"tag":157,"props":735,"children":736},{},[737],{"type":25,"value":726},{"type":20,"tag":21,"props":739,"children":740},{},[741,743,748,750,757],{"type":25,"value":742},"请求体一般用于 ",{"type":20,"tag":84,"props":744,"children":746},{"className":745},[],[747],{"type":25,"value":551},{"type":25,"value":749},"，用 Pydantic 模型声明。官方文档也强调，Pydantic 模型会被 FastAPI 识别为 request body。参考：",{"type":20,"tag":101,"props":751,"children":754},{"href":752,"rel":753},"https:\u002F\u002Ffastapi.tiangolo.com\u002Ftutorial\u002Fbody\u002F",[105],[755],{"type":25,"value":756},"FastAPI Request Body",{"type":25,"value":110},{"type":20,"tag":126,"props":759,"children":761},{"code":760,"language":150,"meta":8,"className":151,"style":8},"from pydantic import BaseModel\n\n\nclass MoleculeRequest(BaseModel):\n    smiles: str\n    name: str | None = None\n\n\n@app.post(\"\u002Fmolecules\")\ndef create_molecule(data: MoleculeRequest):\n    return {\n        \"smiles\": data.smiles,\n        \"name\": data.name,\n    }\n",[762],{"type":20,"tag":84,"props":763,"children":764},{"__ignoreMap":8},[765,773,780,787,795,803,811,818,825,833,841,849,857,865],{"type":20,"tag":157,"props":766,"children":767},{"class":159,"line":160},[768],{"type":20,"tag":157,"props":769,"children":770},{},[771],{"type":25,"value":772},"from pydantic import BaseModel\n",{"type":20,"tag":157,"props":774,"children":775},{"class":159,"line":169},[776],{"type":20,"tag":157,"props":777,"children":778},{"emptyLinePlaceholder":173},[779],{"type":25,"value":176},{"type":20,"tag":157,"props":781,"children":782},{"class":159,"line":179},[783],{"type":20,"tag":157,"props":784,"children":785},{"emptyLinePlaceholder":173},[786],{"type":25,"value":176},{"type":20,"tag":157,"props":788,"children":789},{"class":159,"line":188},[790],{"type":20,"tag":157,"props":791,"children":792},{},[793],{"type":25,"value":794},"class MoleculeRequest(BaseModel):\n",{"type":20,"tag":157,"props":796,"children":797},{"class":159,"line":196},[798],{"type":20,"tag":157,"props":799,"children":800},{},[801],{"type":25,"value":802},"    smiles: str\n",{"type":20,"tag":157,"props":804,"children":805},{"class":159,"line":204},[806],{"type":20,"tag":157,"props":807,"children":808},{},[809],{"type":25,"value":810},"    name: str | None = None\n",{"type":20,"tag":157,"props":812,"children":813},{"class":159,"line":213},[814],{"type":20,"tag":157,"props":815,"children":816},{"emptyLinePlaceholder":173},[817],{"type":25,"value":176},{"type":20,"tag":157,"props":819,"children":820},{"class":159,"line":222},[821],{"type":20,"tag":157,"props":822,"children":823},{"emptyLinePlaceholder":173},[824],{"type":25,"value":176},{"type":20,"tag":157,"props":826,"children":827},{"class":159,"line":440},[828],{"type":20,"tag":157,"props":829,"children":830},{},[831],{"type":25,"value":832},"@app.post(\"\u002Fmolecules\")\n",{"type":20,"tag":157,"props":834,"children":835},{"class":159,"line":448},[836],{"type":20,"tag":157,"props":837,"children":838},{},[839],{"type":25,"value":840},"def create_molecule(data: MoleculeRequest):\n",{"type":20,"tag":157,"props":842,"children":843},{"class":159,"line":456},[844],{"type":20,"tag":157,"props":845,"children":846},{},[847],{"type":25,"value":848},"    return {\n",{"type":20,"tag":157,"props":850,"children":851},{"class":159,"line":465},[852],{"type":20,"tag":157,"props":853,"children":854},{},[855],{"type":25,"value":856},"        \"smiles\": data.smiles,\n",{"type":20,"tag":157,"props":858,"children":859},{"class":159,"line":474},[860],{"type":20,"tag":157,"props":861,"children":862},{},[863],{"type":25,"value":864},"        \"name\": data.name,\n",{"type":20,"tag":157,"props":866,"children":867},{"class":159,"line":483},[868],{"type":20,"tag":157,"props":869,"children":870},{},[871],{"type":25,"value":872},"    }\n",{"type":20,"tag":21,"props":874,"children":875},{},[876],{"type":25,"value":877},"请求 JSON：",{"type":20,"tag":126,"props":879,"children":883},{"code":880,"language":881,"meta":8,"className":882,"style":8},"{\n  \"smiles\": \"CCO\",\n  \"name\": \"ethanol\"\n}\n","json","language-json shiki shiki-themes github-dark",[884],{"type":20,"tag":84,"props":885,"children":886},{"__ignoreMap":8},[887,896,919,936],{"type":20,"tag":157,"props":888,"children":889},{"class":159,"line":160},[890],{"type":20,"tag":157,"props":891,"children":893},{"style":892},"--shiki-default:#E1E4E8",[894],{"type":25,"value":895},"{\n",{"type":20,"tag":157,"props":897,"children":898},{"class":159,"line":169},[899,904,909,914],{"type":20,"tag":157,"props":900,"children":901},{"style":260},[902],{"type":25,"value":903},"  \"smiles\"",{"type":20,"tag":157,"props":905,"children":906},{"style":892},[907],{"type":25,"value":908},": ",{"type":20,"tag":157,"props":910,"children":911},{"style":254},[912],{"type":25,"value":913},"\"CCO\"",{"type":20,"tag":157,"props":915,"children":916},{"style":892},[917],{"type":25,"value":918},",\n",{"type":20,"tag":157,"props":920,"children":921},{"class":159,"line":179},[922,927,931],{"type":20,"tag":157,"props":923,"children":924},{"style":260},[925],{"type":25,"value":926},"  \"name\"",{"type":20,"tag":157,"props":928,"children":929},{"style":892},[930],{"type":25,"value":908},{"type":20,"tag":157,"props":932,"children":933},{"style":254},[934],{"type":25,"value":935},"\"ethanol\"\n",{"type":20,"tag":157,"props":937,"children":938},{"class":159,"line":188},[939],{"type":20,"tag":157,"props":940,"children":941},{"style":892},[942],{"type":25,"value":943},"}\n",{"type":20,"tag":28,"props":945,"children":947},{"id":946},"_5-返回-json",[948],{"type":20,"tag":33,"props":949,"children":950},{},[951],{"type":25,"value":952},"5. 返回 JSON",{"type":20,"tag":21,"props":954,"children":955},{},[956,958,964,966,972],{"type":25,"value":957},"你直接返回 ",{"type":20,"tag":84,"props":959,"children":961},{"className":960},[],[962],{"type":25,"value":963},"dict",{"type":25,"value":965},"、",{"type":20,"tag":84,"props":967,"children":969},{"className":968},[],[970],{"type":25,"value":971},"list",{"type":25,"value":973},"、Pydantic model，FastAPI 会自动转成 JSON：",{"type":20,"tag":126,"props":975,"children":977},{"code":976,"language":150,"meta":8,"className":151,"style":8},"@app.get(\"\u002Fmolecules\")\ndef list_molecules():\n    return [\n        {\"name\": \"ethanol\", \"smiles\": \"CCO\"},\n        {\"name\": \"aspirin\", \"smiles\": \"CC(=O)Oc1ccccc1C(=O)O\"},\n    ]\n",[978],{"type":20,"tag":84,"props":979,"children":980},{"__ignoreMap":8},[981,989,997,1005,1013,1021],{"type":20,"tag":157,"props":982,"children":983},{"class":159,"line":160},[984],{"type":20,"tag":157,"props":985,"children":986},{},[987],{"type":25,"value":988},"@app.get(\"\u002Fmolecules\")\n",{"type":20,"tag":157,"props":990,"children":991},{"class":159,"line":169},[992],{"type":20,"tag":157,"props":993,"children":994},{},[995],{"type":25,"value":996},"def list_molecules():\n",{"type":20,"tag":157,"props":998,"children":999},{"class":159,"line":179},[1000],{"type":20,"tag":157,"props":1001,"children":1002},{},[1003],{"type":25,"value":1004},"    return [\n",{"type":20,"tag":157,"props":1006,"children":1007},{"class":159,"line":188},[1008],{"type":20,"tag":157,"props":1009,"children":1010},{},[1011],{"type":25,"value":1012},"        {\"name\": \"ethanol\", \"smiles\": \"CCO\"},\n",{"type":20,"tag":157,"props":1014,"children":1015},{"class":159,"line":196},[1016],{"type":20,"tag":157,"props":1017,"children":1018},{},[1019],{"type":25,"value":1020},"        {\"name\": \"aspirin\", \"smiles\": \"CC(=O)Oc1ccccc1C(=O)O\"},\n",{"type":20,"tag":157,"props":1022,"children":1023},{"class":159,"line":204},[1024],{"type":20,"tag":157,"props":1025,"children":1026},{},[1027],{"type":25,"value":1028},"    ]\n",{"type":20,"tag":28,"props":1030,"children":1032},{"id":1031},"_6-主动返回错误",[1033],{"type":20,"tag":33,"props":1034,"children":1035},{},[1036],{"type":25,"value":1037},"6. 主动返回错误",{"type":20,"tag":21,"props":1039,"children":1040},{},[1041,1043,1049,1051,1057],{"type":25,"value":1042},"接口里不要用普通 ",{"type":20,"tag":84,"props":1044,"children":1046},{"className":1045},[],[1047],{"type":25,"value":1048},"return {\"error\": ...}",{"type":25,"value":1050}," 混过去，应该用 ",{"type":20,"tag":84,"props":1052,"children":1054},{"className":1053},[],[1055],{"type":25,"value":1056},"HTTPException",{"type":25,"value":146},{"type":20,"tag":126,"props":1059,"children":1061},{"code":1060,"language":150,"meta":8,"className":151,"style":8},"from fastapi import HTTPException\n\n\n@app.get(\"\u002Fmolecules\u002F{molecule_id}\")\ndef get_molecule(molecule_id: int):\n    if molecule_id \u003C= 0:\n        raise HTTPException(status_code=400, detail=\"molecule_id must be positive\")\n\n    return {\"id\": molecule_id}\n",[1062],{"type":20,"tag":84,"props":1063,"children":1064},{"__ignoreMap":8},[1065,1073,1080,1087,1095,1103,1111,1119,1126],{"type":20,"tag":157,"props":1066,"children":1067},{"class":159,"line":160},[1068],{"type":20,"tag":157,"props":1069,"children":1070},{},[1071],{"type":25,"value":1072},"from fastapi import HTTPException\n",{"type":20,"tag":157,"props":1074,"children":1075},{"class":159,"line":169},[1076],{"type":20,"tag":157,"props":1077,"children":1078},{"emptyLinePlaceholder":173},[1079],{"type":25,"value":176},{"type":20,"tag":157,"props":1081,"children":1082},{"class":159,"line":179},[1083],{"type":20,"tag":157,"props":1084,"children":1085},{"emptyLinePlaceholder":173},[1086],{"type":25,"value":176},{"type":20,"tag":157,"props":1088,"children":1089},{"class":159,"line":188},[1090],{"type":20,"tag":157,"props":1091,"children":1092},{},[1093],{"type":25,"value":1094},"@app.get(\"\u002Fmolecules\u002F{molecule_id}\")\n",{"type":20,"tag":157,"props":1096,"children":1097},{"class":159,"line":196},[1098],{"type":20,"tag":157,"props":1099,"children":1100},{},[1101],{"type":25,"value":1102},"def get_molecule(molecule_id: int):\n",{"type":20,"tag":157,"props":1104,"children":1105},{"class":159,"line":204},[1106],{"type":20,"tag":157,"props":1107,"children":1108},{},[1109],{"type":25,"value":1110},"    if molecule_id \u003C= 0:\n",{"type":20,"tag":157,"props":1112,"children":1113},{"class":159,"line":213},[1114],{"type":20,"tag":157,"props":1115,"children":1116},{},[1117],{"type":25,"value":1118},"        raise HTTPException(status_code=400, detail=\"molecule_id must be positive\")\n",{"type":20,"tag":157,"props":1120,"children":1121},{"class":159,"line":222},[1122],{"type":20,"tag":157,"props":1123,"children":1124},{"emptyLinePlaceholder":173},[1125],{"type":25,"value":176},{"type":20,"tag":157,"props":1127,"children":1128},{"class":159,"line":440},[1129],{"type":20,"tag":157,"props":1130,"children":1131},{},[1132],{"type":25,"value":1133},"    return {\"id\": molecule_id}\n",{"type":20,"tag":21,"props":1135,"children":1136},{},[1137],{"type":25,"value":1138},"常见状态码：",{"type":20,"tag":44,"props":1140,"children":1141},{},[1142,1153,1164,1175,1186,1197,1208],{"type":20,"tag":48,"props":1143,"children":1144},{},[1145,1151],{"type":20,"tag":84,"props":1146,"children":1148},{"className":1147},[],[1149],{"type":25,"value":1150},"200",{"type":25,"value":1152},"：成功",{"type":20,"tag":48,"props":1154,"children":1155},{},[1156,1162],{"type":20,"tag":84,"props":1157,"children":1159},{"className":1158},[],[1160],{"type":25,"value":1161},"201",{"type":25,"value":1163},"：创建成功",{"type":20,"tag":48,"props":1165,"children":1166},{},[1167,1173],{"type":20,"tag":84,"props":1168,"children":1170},{"className":1169},[],[1171],{"type":25,"value":1172},"400",{"type":25,"value":1174},"：请求参数不合法",{"type":20,"tag":48,"props":1176,"children":1177},{},[1178,1184],{"type":20,"tag":84,"props":1179,"children":1181},{"className":1180},[],[1182],{"type":25,"value":1183},"401",{"type":25,"value":1185},"：未登录",{"type":20,"tag":48,"props":1187,"children":1188},{},[1189,1195],{"type":20,"tag":84,"props":1190,"children":1192},{"className":1191},[],[1193],{"type":25,"value":1194},"403",{"type":25,"value":1196},"：无权限",{"type":20,"tag":48,"props":1198,"children":1199},{},[1200,1206],{"type":20,"tag":84,"props":1201,"children":1203},{"className":1202},[],[1204],{"type":25,"value":1205},"404",{"type":25,"value":1207},"：资源不存在",{"type":20,"tag":48,"props":1209,"children":1210},{},[1211,1217],{"type":20,"tag":84,"props":1212,"children":1214},{"className":1213},[],[1215],{"type":25,"value":1216},"500",{"type":25,"value":1218},"：服务端错误",{"type":20,"tag":28,"props":1220,"children":1222},{"id":1221},"_7-async-和普通-def",[1223],{"type":20,"tag":33,"props":1224,"children":1225},{},[1226],{"type":25,"value":1227},"7. async 和普通 def",{"type":20,"tag":21,"props":1229,"children":1230},{},[1231],{"type":25,"value":1232},"两种都能写：",{"type":20,"tag":126,"props":1234,"children":1236},{"code":1235,"language":150,"meta":8,"className":151,"style":8},"@app.get(\"\u002Fsync\")\ndef sync_api():\n    return {\"mode\": \"sync\"}\n\n\n@app.get(\"\u002Fasync\")\nasync def async_api():\n    return {\"mode\": \"async\"}\n",[1237],{"type":20,"tag":84,"props":1238,"children":1239},{"__ignoreMap":8},[1240,1248,1256,1264,1271,1278,1286,1294],{"type":20,"tag":157,"props":1241,"children":1242},{"class":159,"line":160},[1243],{"type":20,"tag":157,"props":1244,"children":1245},{},[1246],{"type":25,"value":1247},"@app.get(\"\u002Fsync\")\n",{"type":20,"tag":157,"props":1249,"children":1250},{"class":159,"line":169},[1251],{"type":20,"tag":157,"props":1252,"children":1253},{},[1254],{"type":25,"value":1255},"def sync_api():\n",{"type":20,"tag":157,"props":1257,"children":1258},{"class":159,"line":179},[1259],{"type":20,"tag":157,"props":1260,"children":1261},{},[1262],{"type":25,"value":1263},"    return {\"mode\": \"sync\"}\n",{"type":20,"tag":157,"props":1265,"children":1266},{"class":159,"line":188},[1267],{"type":20,"tag":157,"props":1268,"children":1269},{"emptyLinePlaceholder":173},[1270],{"type":25,"value":176},{"type":20,"tag":157,"props":1272,"children":1273},{"class":159,"line":196},[1274],{"type":20,"tag":157,"props":1275,"children":1276},{"emptyLinePlaceholder":173},[1277],{"type":25,"value":176},{"type":20,"tag":157,"props":1279,"children":1280},{"class":159,"line":204},[1281],{"type":20,"tag":157,"props":1282,"children":1283},{},[1284],{"type":25,"value":1285},"@app.get(\"\u002Fasync\")\n",{"type":20,"tag":157,"props":1287,"children":1288},{"class":159,"line":213},[1289],{"type":20,"tag":157,"props":1290,"children":1291},{},[1292],{"type":25,"value":1293},"async def async_api():\n",{"type":20,"tag":157,"props":1295,"children":1296},{"class":159,"line":222},[1297],{"type":20,"tag":157,"props":1298,"children":1299},{},[1300],{"type":25,"value":1301},"    return {\"mode\": \"async\"}\n",{"type":20,"tag":21,"props":1303,"children":1304},{},[1305],{"type":25,"value":1306},"简单理解：",{"type":20,"tag":44,"props":1308,"children":1309},{},[1310,1321],{"type":20,"tag":48,"props":1311,"children":1312},{},[1313,1315],{"type":25,"value":1314},"普通计算、调用同步库：用 ",{"type":20,"tag":84,"props":1316,"children":1318},{"className":1317},[],[1319],{"type":25,"value":1320},"def",{"type":20,"tag":48,"props":1322,"children":1323},{},[1324,1326],{"type":25,"value":1325},"调数据库、HTTP 请求、文件 IO，并且库支持 async：用 ",{"type":20,"tag":84,"props":1327,"children":1329},{"className":1328},[],[1330],{"type":25,"value":1331},"async def",{"type":20,"tag":21,"props":1333,"children":1334},{},[1335,1337,1342,1344,1350],{"type":25,"value":1336},"初学阶段先用 ",{"type":20,"tag":84,"props":1338,"children":1340},{"className":1339},[],[1341],{"type":25,"value":1320},{"type":25,"value":1343}," 就够了，别为了“看起来高级”乱用 ",{"type":20,"tag":84,"props":1345,"children":1347},{"className":1346},[],[1348],{"type":25,"value":1349},"async",{"type":25,"value":110},{"type":20,"tag":28,"props":1352,"children":1354},{"id":1353},"一个完整基础例子",[1355],{"type":20,"tag":33,"props":1356,"children":1357},{},[1358],{"type":25,"value":1353},{"type":20,"tag":126,"props":1360,"children":1362},{"code":1361,"language":150,"meta":8,"className":151,"style":8},"from fastapi import FastAPI, HTTPException\nfrom pydantic import BaseModel, Field\n\napp = FastAPI(title=\"Drug Discovery API\")\n\n\nclass SmilesRequest(BaseModel):\n    smiles: str = Field(min_length=1)\n\n\n@app.get(\"\u002Fhealth\")\ndef health():\n    return {\"status\": \"ok\"}\n\n\n@app.get(\"\u002Fmolecules\u002F{molecule_id}\")\ndef get_molecule(molecule_id: int):\n    if molecule_id \u003C= 0:\n        raise HTTPException(status_code=400, detail=\"molecule_id must be positive\")\n\n    return {\n        \"id\": molecule_id,\n        \"name\": \"ethanol\",\n        \"smiles\": \"CCO\",\n    }\n\n\n@app.get(\"\u002Fsearch\")\ndef search_molecules(q: str, limit: int = 10):\n    return {\n        \"query\": q,\n        \"limit\": limit,\n        \"results\": [],\n    }\n\n\n@app.post(\"\u002Fsmiles\u002Fvalidate\")\ndef validate_smiles(data: SmilesRequest):\n    is_valid = data.smiles.strip() != \"\"\n\n    return {\n        \"smiles\": data.smiles,\n        \"is_valid\": is_valid,\n    }\n",[1363],{"type":20,"tag":84,"props":1364,"children":1365},{"__ignoreMap":8},[1366,1374,1382,1389,1397,1404,1411,1419,1427,1434,1441,1448,1455,1462,1469,1476,1483,1490,1497,1505,1513,1521,1530,1539,1548,1556,1564,1572,1580,1589,1597,1606,1615,1624,1632,1640,1648,1657,1666,1675,1683,1691,1699,1708],{"type":20,"tag":157,"props":1367,"children":1368},{"class":159,"line":160},[1369],{"type":20,"tag":157,"props":1370,"children":1371},{},[1372],{"type":25,"value":1373},"from fastapi import FastAPI, HTTPException\n",{"type":20,"tag":157,"props":1375,"children":1376},{"class":159,"line":169},[1377],{"type":20,"tag":157,"props":1378,"children":1379},{},[1380],{"type":25,"value":1381},"from pydantic import BaseModel, Field\n",{"type":20,"tag":157,"props":1383,"children":1384},{"class":159,"line":179},[1385],{"type":20,"tag":157,"props":1386,"children":1387},{"emptyLinePlaceholder":173},[1388],{"type":25,"value":176},{"type":20,"tag":157,"props":1390,"children":1391},{"class":159,"line":188},[1392],{"type":20,"tag":157,"props":1393,"children":1394},{},[1395],{"type":25,"value":1396},"app = FastAPI(title=\"Drug Discovery API\")\n",{"type":20,"tag":157,"props":1398,"children":1399},{"class":159,"line":196},[1400],{"type":20,"tag":157,"props":1401,"children":1402},{"emptyLinePlaceholder":173},[1403],{"type":25,"value":176},{"type":20,"tag":157,"props":1405,"children":1406},{"class":159,"line":204},[1407],{"type":20,"tag":157,"props":1408,"children":1409},{"emptyLinePlaceholder":173},[1410],{"type":25,"value":176},{"type":20,"tag":157,"props":1412,"children":1413},{"class":159,"line":213},[1414],{"type":20,"tag":157,"props":1415,"children":1416},{},[1417],{"type":25,"value":1418},"class SmilesRequest(BaseModel):\n",{"type":20,"tag":157,"props":1420,"children":1421},{"class":159,"line":222},[1422],{"type":20,"tag":157,"props":1423,"children":1424},{},[1425],{"type":25,"value":1426},"    smiles: str = Field(min_length=1)\n",{"type":20,"tag":157,"props":1428,"children":1429},{"class":159,"line":440},[1430],{"type":20,"tag":157,"props":1431,"children":1432},{"emptyLinePlaceholder":173},[1433],{"type":25,"value":176},{"type":20,"tag":157,"props":1435,"children":1436},{"class":159,"line":448},[1437],{"type":20,"tag":157,"props":1438,"children":1439},{"emptyLinePlaceholder":173},[1440],{"type":25,"value":176},{"type":20,"tag":157,"props":1442,"children":1443},{"class":159,"line":456},[1444],{"type":20,"tag":157,"props":1445,"children":1446},{},[1447],{"type":25,"value":383},{"type":20,"tag":157,"props":1449,"children":1450},{"class":159,"line":465},[1451],{"type":20,"tag":157,"props":1452,"children":1453},{},[1454],{"type":25,"value":391},{"type":20,"tag":157,"props":1456,"children":1457},{"class":159,"line":474},[1458],{"type":20,"tag":157,"props":1459,"children":1460},{},[1461],{"type":25,"value":399},{"type":20,"tag":157,"props":1463,"children":1464},{"class":159,"line":483},[1465],{"type":20,"tag":157,"props":1466,"children":1467},{"emptyLinePlaceholder":173},[1468],{"type":25,"value":176},{"type":20,"tag":157,"props":1470,"children":1471},{"class":159,"line":491},[1472],{"type":20,"tag":157,"props":1473,"children":1474},{"emptyLinePlaceholder":173},[1475],{"type":25,"value":176},{"type":20,"tag":157,"props":1477,"children":1478},{"class":159,"line":499},[1479],{"type":20,"tag":157,"props":1480,"children":1481},{},[1482],{"type":25,"value":1094},{"type":20,"tag":157,"props":1484,"children":1485},{"class":159,"line":508},[1486],{"type":20,"tag":157,"props":1487,"children":1488},{},[1489],{"type":25,"value":1102},{"type":20,"tag":157,"props":1491,"children":1492},{"class":159,"line":517},[1493],{"type":20,"tag":157,"props":1494,"children":1495},{},[1496],{"type":25,"value":1110},{"type":20,"tag":157,"props":1498,"children":1500},{"class":159,"line":1499},19,[1501],{"type":20,"tag":157,"props":1502,"children":1503},{},[1504],{"type":25,"value":1118},{"type":20,"tag":157,"props":1506,"children":1508},{"class":159,"line":1507},20,[1509],{"type":20,"tag":157,"props":1510,"children":1511},{"emptyLinePlaceholder":173},[1512],{"type":25,"value":176},{"type":20,"tag":157,"props":1514,"children":1516},{"class":159,"line":1515},21,[1517],{"type":20,"tag":157,"props":1518,"children":1519},{},[1520],{"type":25,"value":848},{"type":20,"tag":157,"props":1522,"children":1524},{"class":159,"line":1523},22,[1525],{"type":20,"tag":157,"props":1526,"children":1527},{},[1528],{"type":25,"value":1529},"        \"id\": molecule_id,\n",{"type":20,"tag":157,"props":1531,"children":1533},{"class":159,"line":1532},23,[1534],{"type":20,"tag":157,"props":1535,"children":1536},{},[1537],{"type":25,"value":1538},"        \"name\": \"ethanol\",\n",{"type":20,"tag":157,"props":1540,"children":1542},{"class":159,"line":1541},24,[1543],{"type":20,"tag":157,"props":1544,"children":1545},{},[1546],{"type":25,"value":1547},"        \"smiles\": \"CCO\",\n",{"type":20,"tag":157,"props":1549,"children":1551},{"class":159,"line":1550},25,[1552],{"type":20,"tag":157,"props":1553,"children":1554},{},[1555],{"type":25,"value":872},{"type":20,"tag":157,"props":1557,"children":1559},{"class":159,"line":1558},26,[1560],{"type":20,"tag":157,"props":1561,"children":1562},{"emptyLinePlaceholder":173},[1563],{"type":25,"value":176},{"type":20,"tag":157,"props":1565,"children":1567},{"class":159,"line":1566},27,[1568],{"type":20,"tag":157,"props":1569,"children":1570},{"emptyLinePlaceholder":173},[1571],{"type":25,"value":176},{"type":20,"tag":157,"props":1573,"children":1575},{"class":159,"line":1574},28,[1576],{"type":20,"tag":157,"props":1577,"children":1578},{},[1579],{"type":25,"value":703},{"type":20,"tag":157,"props":1581,"children":1583},{"class":159,"line":1582},29,[1584],{"type":20,"tag":157,"props":1585,"children":1586},{},[1587],{"type":25,"value":1588},"def search_molecules(q: str, limit: int = 10):\n",{"type":20,"tag":157,"props":1590,"children":1592},{"class":159,"line":1591},30,[1593],{"type":20,"tag":157,"props":1594,"children":1595},{},[1596],{"type":25,"value":848},{"type":20,"tag":157,"props":1598,"children":1600},{"class":159,"line":1599},31,[1601],{"type":20,"tag":157,"props":1602,"children":1603},{},[1604],{"type":25,"value":1605},"        \"query\": q,\n",{"type":20,"tag":157,"props":1607,"children":1609},{"class":159,"line":1608},32,[1610],{"type":20,"tag":157,"props":1611,"children":1612},{},[1613],{"type":25,"value":1614},"        \"limit\": limit,\n",{"type":20,"tag":157,"props":1616,"children":1618},{"class":159,"line":1617},33,[1619],{"type":20,"tag":157,"props":1620,"children":1621},{},[1622],{"type":25,"value":1623},"        \"results\": [],\n",{"type":20,"tag":157,"props":1625,"children":1627},{"class":159,"line":1626},34,[1628],{"type":20,"tag":157,"props":1629,"children":1630},{},[1631],{"type":25,"value":872},{"type":20,"tag":157,"props":1633,"children":1635},{"class":159,"line":1634},35,[1636],{"type":20,"tag":157,"props":1637,"children":1638},{"emptyLinePlaceholder":173},[1639],{"type":25,"value":176},{"type":20,"tag":157,"props":1641,"children":1643},{"class":159,"line":1642},36,[1644],{"type":20,"tag":157,"props":1645,"children":1646},{"emptyLinePlaceholder":173},[1647],{"type":25,"value":176},{"type":20,"tag":157,"props":1649,"children":1651},{"class":159,"line":1650},37,[1652],{"type":20,"tag":157,"props":1653,"children":1654},{},[1655],{"type":25,"value":1656},"@app.post(\"\u002Fsmiles\u002Fvalidate\")\n",{"type":20,"tag":157,"props":1658,"children":1660},{"class":159,"line":1659},38,[1661],{"type":20,"tag":157,"props":1662,"children":1663},{},[1664],{"type":25,"value":1665},"def validate_smiles(data: SmilesRequest):\n",{"type":20,"tag":157,"props":1667,"children":1669},{"class":159,"line":1668},39,[1670],{"type":20,"tag":157,"props":1671,"children":1672},{},[1673],{"type":25,"value":1674},"    is_valid = data.smiles.strip() != \"\"\n",{"type":20,"tag":157,"props":1676,"children":1678},{"class":159,"line":1677},40,[1679],{"type":20,"tag":157,"props":1680,"children":1681},{"emptyLinePlaceholder":173},[1682],{"type":25,"value":176},{"type":20,"tag":157,"props":1684,"children":1686},{"class":159,"line":1685},41,[1687],{"type":20,"tag":157,"props":1688,"children":1689},{},[1690],{"type":25,"value":848},{"type":20,"tag":157,"props":1692,"children":1694},{"class":159,"line":1693},42,[1695],{"type":20,"tag":157,"props":1696,"children":1697},{},[1698],{"type":25,"value":856},{"type":20,"tag":157,"props":1700,"children":1702},{"class":159,"line":1701},43,[1703],{"type":20,"tag":157,"props":1704,"children":1705},{},[1706],{"type":25,"value":1707},"        \"is_valid\": is_valid,\n",{"type":20,"tag":157,"props":1709,"children":1711},{"class":159,"line":1710},44,[1712],{"type":20,"tag":157,"props":1713,"children":1714},{},[1715],{"type":25,"value":872},{"type":20,"tag":21,"props":1717,"children":1718},{},[1719],{"type":25,"value":1720},"你现在只需要掌握这条主线：",{"type":20,"tag":126,"props":1722,"children":1724},{"code":1723,"language":25,"meta":8,"className":324,"style":8},"定义 app\n-> 写路由\n-> 声明参数\n-> 用 Pydantic 接 JSON\n-> 返回 dict\n-> 用 HTTPException 报错\n-> 去 \u002Fdocs 测试\n",[1725],{"type":20,"tag":84,"props":1726,"children":1727},{"__ignoreMap":8},[1728,1736,1744,1752,1760,1768,1776],{"type":20,"tag":157,"props":1729,"children":1730},{"class":159,"line":160},[1731],{"type":20,"tag":157,"props":1732,"children":1733},{},[1734],{"type":25,"value":1735},"定义 app\n",{"type":20,"tag":157,"props":1737,"children":1738},{"class":159,"line":169},[1739],{"type":20,"tag":157,"props":1740,"children":1741},{},[1742],{"type":25,"value":1743},"-> 写路由\n",{"type":20,"tag":157,"props":1745,"children":1746},{"class":159,"line":179},[1747],{"type":20,"tag":157,"props":1748,"children":1749},{},[1750],{"type":25,"value":1751},"-> 声明参数\n",{"type":20,"tag":157,"props":1753,"children":1754},{"class":159,"line":188},[1755],{"type":20,"tag":157,"props":1756,"children":1757},{},[1758],{"type":25,"value":1759},"-> 用 Pydantic 接 JSON\n",{"type":20,"tag":157,"props":1761,"children":1762},{"class":159,"line":196},[1763],{"type":20,"tag":157,"props":1764,"children":1765},{},[1766],{"type":25,"value":1767},"-> 返回 dict\n",{"type":20,"tag":157,"props":1769,"children":1770},{"class":159,"line":204},[1771],{"type":20,"tag":157,"props":1772,"children":1773},{},[1774],{"type":25,"value":1775},"-> 用 HTTPException 报错\n",{"type":20,"tag":157,"props":1777,"children":1778},{"class":159,"line":213},[1779],{"type":20,"tag":157,"props":1780,"children":1781},{},[1782],{"type":25,"value":1783},"-> 去 \u002Fdocs 测试\n",{"type":20,"tag":1785,"props":1786,"children":1787},"style",{},[1788],{"type":25,"value":1789},"html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}",{"title":8,"searchDepth":169,"depth":169,"links":1791},[1792,1793,1794,1795,1796,1797,1798,1799],{"id":30,"depth":179,"text":37},{"id":113,"depth":179,"text":119},{"id":357,"depth":179,"text":363},{"id":586,"depth":179,"text":592},{"id":946,"depth":179,"text":952},{"id":1031,"depth":179,"text":1037},{"id":1221,"depth":179,"text":1227},{"id":1353,"depth":179,"text":1353},"markdown","content:articles:backend:FastAPI.md","content","articles\u002Fbackend\u002FFastAPI.md","articles\u002Fbackend\u002FFastAPI","md",{"_path":1807,"_dir":1808,"_draft":7,"_partial":7,"_locale":8,"title":1809,"description":1810,"date":1811,"tags":1812,"body":1815,"_type":1800,"_id":4981,"_source":1802,"_file":4982,"_stem":4983,"_extension":1805},"\u002Farticles\u002Fai\u002Frdkit-smiles-descriptors-fingerprints","ai","从 SMILES 到分子指纹：用 RDKit 把分子变成机器学习能读懂的向量","以 AI 制药平台项目为背景，梳理 SMILES、Mol 对象、Canonical SMILES、分子描述符、Morgan 指纹和 Tanimoto 相似度，理解 RDKit 如何把化学结构转成可用于检索、建模和预测的数字特征。","2026-05-21",[13,1813,1814],"生物信息学","AI制药",{"type":17,"children":1816,"toc":4966},[1817,1822,1827,1838,1843,1857,1862,1869,1874,1901,2031,2055,2060,2089,2101,2137,2142,2148,2153,2158,2172,2177,2333,2338,2343,2357,2362,2368,2373,2378,2425,2430,2435,2441,2454,2523,2533,2554,2559,2627,2632,2637,2695,2700,2779,2790,2796,2801,2805,2827,2832,2837,2924,2929,2943,2948,2954,2959,2971,3315,3320,3325,3331,3336,3341,3404,3409,3423,3428,3433,3628,3633,3797,3802,3807,3812,3817,3823,3828,3833,3872,3877,3882,3913,3918,3924,3929,3934,3939,3944,3967,3972,3977,4135,4148,4153,4252,4257,4263,4268,4273,4287,4292,4297,4444,4449,4454,4501,4506,4518,4523,4528,4782,4787,4826,4831,4836,4841,4846,4909,4914,4919,4924,4929,4962],{"type":20,"tag":21,"props":1818,"children":1819},{},[1820],{"type":25,"value":1821},"做 AI 制药项目时，第一道门槛不是模型，而是表示。",{"type":20,"tag":21,"props":1823,"children":1824},{},[1825],{"type":25,"value":1826},"模型不能直接理解“阿司匹林”“乙醇”或者课本上的二维结构图。它需要的是数字，是向量，是可以进入表格模型、神经网络或者相似性搜索系统的数据结构。",{"type":20,"tag":21,"props":1828,"children":1829},{},[1830,1832,1837],{"type":25,"value":1831},"这篇文章整理的是我做制药平台项目时前两周的核心笔记：",{"type":20,"tag":33,"props":1833,"children":1834},{},[1835],{"type":25,"value":1836},"SMILES、RDKit、Mol 对象、分子描述符、Morgan 指纹和 Tanimoto 相似度",{"type":25,"value":110},{"type":20,"tag":21,"props":1839,"children":1840},{},[1841],{"type":25,"value":1842},"如果用一句话概括，就是：",{"type":20,"tag":126,"props":1844,"children":1846},{"className":324,"code":1845,"language":25,"meta":8,"style":8},"SMILES 字符串 -> RDKit 解析 -> Mol 对象 -> 描述符 \u002F 指纹 -> 相似性搜索 \u002F 机器学习模型\n",[1847],{"type":20,"tag":84,"props":1848,"children":1849},{"__ignoreMap":8},[1850],{"type":20,"tag":157,"props":1851,"children":1852},{"class":159,"line":160},[1853],{"type":20,"tag":157,"props":1854,"children":1855},{},[1856],{"type":25,"value":1845},{"type":20,"tag":21,"props":1858,"children":1859},{},[1860],{"type":25,"value":1861},"这条链路看起来不长，但它是很多 AI 制药工程系统的起点。",{"type":20,"tag":1863,"props":1864,"children":1866},"h2",{"id":1865},"先把-python-环境管好",[1867],{"type":25,"value":1868},"先把 Python 环境管好",{"type":20,"tag":21,"props":1870,"children":1871},{},[1872],{"type":25,"value":1873},"在写 RDKit 代码之前，先处理一个很现实的问题：项目依赖怎么管理。",{"type":20,"tag":21,"props":1875,"children":1876},{},[1877,1879,1885,1887,1893,1894,1900],{"type":25,"value":1878},"我现在更习惯用 ",{"type":20,"tag":84,"props":1880,"children":1882},{"className":1881},[],[1883],{"type":25,"value":1884},"uv",{"type":25,"value":1886}," 管 Python 项目。可以把它类比成前端里的 ",{"type":20,"tag":84,"props":1888,"children":1890},{"className":1889},[],[1891],{"type":25,"value":1892},"npm",{"type":25,"value":564},{"type":20,"tag":84,"props":1895,"children":1897},{"className":1896},[],[1898],{"type":25,"value":1899},"pnpm",{"type":25,"value":146},{"type":20,"tag":1902,"props":1903,"children":1904},"table",{},[1905,1929],{"type":20,"tag":1906,"props":1907,"children":1908},"thead",{},[1909],{"type":20,"tag":1910,"props":1911,"children":1912},"tr",{},[1913,1919,1924],{"type":20,"tag":1914,"props":1915,"children":1916},"th",{},[1917],{"type":25,"value":1918},"Python 项目",{"type":20,"tag":1914,"props":1920,"children":1921},{},[1922],{"type":25,"value":1923},"前端项目",{"type":20,"tag":1914,"props":1925,"children":1926},{},[1927],{"type":25,"value":1928},"作用",{"type":20,"tag":1930,"props":1931,"children":1932},"tbody",{},[1933,1960,1993,2019],{"type":20,"tag":1910,"props":1934,"children":1935},{},[1936,1946,1955],{"type":20,"tag":1937,"props":1938,"children":1939},"td",{},[1940],{"type":20,"tag":84,"props":1941,"children":1943},{"className":1942},[],[1944],{"type":25,"value":1945},"pyproject.toml",{"type":20,"tag":1937,"props":1947,"children":1948},{},[1949],{"type":20,"tag":84,"props":1950,"children":1952},{"className":1951},[],[1953],{"type":25,"value":1954},"package.json",{"type":20,"tag":1937,"props":1956,"children":1957},{},[1958],{"type":25,"value":1959},"声明项目需要什么依赖，以及大致版本范围",{"type":20,"tag":1910,"props":1961,"children":1962},{},[1963,1972,1988],{"type":20,"tag":1937,"props":1964,"children":1965},{},[1966],{"type":20,"tag":84,"props":1967,"children":1969},{"className":1968},[],[1970],{"type":25,"value":1971},"uv.lock",{"type":20,"tag":1937,"props":1973,"children":1974},{},[1975,1981,1982],{"type":20,"tag":84,"props":1976,"children":1978},{"className":1977},[],[1979],{"type":25,"value":1980},"package-lock.json",{"type":25,"value":564},{"type":20,"tag":84,"props":1983,"children":1985},{"className":1984},[],[1986],{"type":25,"value":1987},"pnpm-lock.yaml",{"type":20,"tag":1937,"props":1989,"children":1990},{},[1991],{"type":25,"value":1992},"记录这次解析出的精确版本",{"type":20,"tag":1910,"props":1994,"children":1995},{},[1996,2005,2014],{"type":20,"tag":1937,"props":1997,"children":1998},{},[1999],{"type":20,"tag":84,"props":2000,"children":2002},{"className":2001},[],[2003],{"type":25,"value":2004},".venv",{"type":20,"tag":1937,"props":2006,"children":2007},{},[2008],{"type":20,"tag":84,"props":2009,"children":2011},{"className":2010},[],[2012],{"type":25,"value":2013},"node_modules",{"type":20,"tag":1937,"props":2015,"children":2016},{},[2017],{"type":25,"value":2018},"当前项目真实可运行的依赖环境",{"type":20,"tag":1910,"props":2020,"children":2021},{},[2022,2025,2028],{"type":20,"tag":1937,"props":2023,"children":2024},{},[],{"type":20,"tag":1937,"props":2026,"children":2027},{},[],{"type":20,"tag":1937,"props":2029,"children":2030},{},[],{"type":20,"tag":21,"props":2032,"children":2033},{},[2034,2039,2041,2046,2048,2053],{"type":20,"tag":84,"props":2035,"children":2037},{"className":2036},[],[2038],{"type":25,"value":1945},{"type":25,"value":2040}," 负责说“我需要 RDKit、Pillow、NumPy 这些包，并且版本要在某个范围内”。",{"type":20,"tag":84,"props":2042,"children":2044},{"className":2043},[],[2045],{"type":25,"value":1971},{"type":25,"value":2047}," 负责记录“这一次具体解析出了哪些版本”。",{"type":20,"tag":84,"props":2049,"children":2051},{"className":2050},[],[2052],{"type":25,"value":2004},{"type":25,"value":2054}," 里放的是当前项目实际运行时会用到的 Python 解释器和依赖包。",{"type":20,"tag":21,"props":2056,"children":2057},{},[2058],{"type":25,"value":2059},"所以运行脚本时，不要直接用系统 Python：",{"type":20,"tag":126,"props":2061,"children":2063},{"className":238,"code":2062,"language":237,"meta":8,"style":8},"python scripts\u002Fvalidate_smiles.py --smiles \"CCO\"\n",[2064],{"type":20,"tag":84,"props":2065,"children":2066},{"__ignoreMap":8},[2067],{"type":20,"tag":157,"props":2068,"children":2069},{"class":159,"line":160},[2070,2074,2079,2084],{"type":20,"tag":157,"props":2071,"children":2072},{"style":248},[2073],{"type":25,"value":150},{"type":20,"tag":157,"props":2075,"children":2076},{"style":254},[2077],{"type":25,"value":2078}," scripts\u002Fvalidate_smiles.py",{"type":20,"tag":157,"props":2080,"children":2081},{"style":260},[2082],{"type":25,"value":2083}," --smiles",{"type":20,"tag":157,"props":2085,"children":2086},{"style":254},[2087],{"type":25,"value":2088}," \"CCO\"\n",{"type":20,"tag":21,"props":2090,"children":2091},{},[2092,2094,2099],{"type":25,"value":2093},"更推荐让 ",{"type":20,"tag":84,"props":2095,"children":2097},{"className":2096},[],[2098],{"type":25,"value":1884},{"type":25,"value":2100}," 使用项目自己的环境：",{"type":20,"tag":126,"props":2102,"children":2104},{"className":238,"code":2103,"language":237,"meta":8,"style":8},"uv run python scripts\u002Fvalidate_smiles.py --smiles \"CCO\"\n",[2105],{"type":20,"tag":84,"props":2106,"children":2107},{"__ignoreMap":8},[2108],{"type":20,"tag":157,"props":2109,"children":2110},{"class":159,"line":160},[2111,2115,2120,2125,2129,2133],{"type":20,"tag":157,"props":2112,"children":2113},{"style":248},[2114],{"type":25,"value":1884},{"type":20,"tag":157,"props":2116,"children":2117},{"style":254},[2118],{"type":25,"value":2119}," run",{"type":20,"tag":157,"props":2121,"children":2122},{"style":254},[2123],{"type":25,"value":2124}," python",{"type":20,"tag":157,"props":2126,"children":2127},{"style":254},[2128],{"type":25,"value":2078},{"type":20,"tag":157,"props":2130,"children":2131},{"style":260},[2132],{"type":25,"value":2083},{"type":20,"tag":157,"props":2134,"children":2135},{"style":254},[2136],{"type":25,"value":2088},{"type":20,"tag":21,"props":2138,"children":2139},{},[2140],{"type":25,"value":2141},"这件事看起来和化学没关系，但它决定了别人拉下项目之后能不能复现你的结果。做 AI 制药平台时，依赖版本、运行环境和数据处理链路本来就是系统的一部分。",{"type":20,"tag":1863,"props":2143,"children":2145},{"id":2144},"smiles把分子写成字符串",[2146],{"type":25,"value":2147},"SMILES：把分子写成字符串",{"type":20,"tag":21,"props":2149,"children":2150},{},[2151],{"type":25,"value":2152},"SMILES 可以理解成“分子的字符串表示”。",{"type":20,"tag":21,"props":2154,"children":2155},{},[2156],{"type":25,"value":2157},"比如乙醇可以写成：",{"type":20,"tag":126,"props":2159,"children":2161},{"className":324,"code":2160,"language":25,"meta":8,"style":8},"CCO\n",[2162],{"type":20,"tag":84,"props":2163,"children":2164},{"__ignoreMap":8},[2165],{"type":20,"tag":157,"props":2166,"children":2167},{"class":159,"line":160},[2168],{"type":20,"tag":157,"props":2169,"children":2170},{},[2171],{"type":25,"value":2160},{"type":20,"tag":21,"props":2173,"children":2174},{},[2175],{"type":25,"value":2176},"这串字符里，每个符号都有化学含义：",{"type":20,"tag":1902,"props":2178,"children":2179},{},[2180,2196],{"type":20,"tag":1906,"props":2181,"children":2182},{},[2183],{"type":20,"tag":1910,"props":2184,"children":2185},{},[2186,2191],{"type":20,"tag":1914,"props":2187,"children":2188},{},[2189],{"type":25,"value":2190},"符号",{"type":20,"tag":1914,"props":2192,"children":2193},{},[2194],{"type":25,"value":2195},"含义",{"type":20,"tag":1930,"props":2197,"children":2198},{},[2199,2216,2233,2250,2267,2284,2301,2314],{"type":20,"tag":1910,"props":2200,"children":2201},{},[2202,2211],{"type":20,"tag":1937,"props":2203,"children":2204},{},[2205],{"type":20,"tag":84,"props":2206,"children":2208},{"className":2207},[],[2209],{"type":25,"value":2210},"C",{"type":20,"tag":1937,"props":2212,"children":2213},{},[2214],{"type":25,"value":2215},"碳原子",{"type":20,"tag":1910,"props":2217,"children":2218},{},[2219,2228],{"type":20,"tag":1937,"props":2220,"children":2221},{},[2222],{"type":20,"tag":84,"props":2223,"children":2225},{"className":2224},[],[2226],{"type":25,"value":2227},"O",{"type":20,"tag":1937,"props":2229,"children":2230},{},[2231],{"type":25,"value":2232},"氧原子",{"type":20,"tag":1910,"props":2234,"children":2235},{},[2236,2245],{"type":20,"tag":1937,"props":2237,"children":2238},{},[2239],{"type":20,"tag":84,"props":2240,"children":2242},{"className":2241},[],[2243],{"type":25,"value":2244},"N",{"type":20,"tag":1937,"props":2246,"children":2247},{},[2248],{"type":25,"value":2249},"氮原子",{"type":20,"tag":1910,"props":2251,"children":2252},{},[2253,2262],{"type":20,"tag":1937,"props":2254,"children":2255},{},[2256],{"type":20,"tag":84,"props":2257,"children":2259},{"className":2258},[],[2260],{"type":25,"value":2261},"=",{"type":20,"tag":1937,"props":2263,"children":2264},{},[2265],{"type":25,"value":2266},"双键",{"type":20,"tag":1910,"props":2268,"children":2269},{},[2270,2279],{"type":20,"tag":1937,"props":2271,"children":2272},{},[2273],{"type":20,"tag":84,"props":2274,"children":2276},{"className":2275},[],[2277],{"type":25,"value":2278},"#",{"type":20,"tag":1937,"props":2280,"children":2281},{},[2282],{"type":25,"value":2283},"三键",{"type":20,"tag":1910,"props":2285,"children":2286},{},[2287,2296],{"type":20,"tag":1937,"props":2288,"children":2289},{},[2290],{"type":20,"tag":84,"props":2291,"children":2293},{"className":2292},[],[2294],{"type":25,"value":2295},"()",{"type":20,"tag":1937,"props":2297,"children":2298},{},[2299],{"type":25,"value":2300},"分支",{"type":20,"tag":1910,"props":2302,"children":2303},{},[2304,2309],{"type":20,"tag":1937,"props":2305,"children":2306},{},[2307],{"type":25,"value":2308},"数字",{"type":20,"tag":1937,"props":2310,"children":2311},{},[2312],{"type":25,"value":2313},"环结构闭合",{"type":20,"tag":1910,"props":2315,"children":2316},{},[2317,2328],{"type":20,"tag":1937,"props":2318,"children":2319},{},[2320,2322],{"type":25,"value":2321},"小写 ",{"type":20,"tag":84,"props":2323,"children":2325},{"className":2324},[],[2326],{"type":25,"value":2327},"c",{"type":20,"tag":1937,"props":2329,"children":2330},{},[2331],{"type":25,"value":2332},"芳香碳，比如苯环",{"type":20,"tag":21,"props":2334,"children":2335},{},[2336],{"type":25,"value":2337},"如果把前端作为类比，SMILES 有点像 HTML：HTML 用文本描述页面结构，SMILES 用文本描述分子结构。",{"type":20,"tag":21,"props":2339,"children":2340},{},[2341],{"type":25,"value":2342},"例如阿司匹林可以写成：",{"type":20,"tag":126,"props":2344,"children":2346},{"className":324,"code":2345,"language":25,"meta":8,"style":8},"CC(=O)Oc1ccccc1C(=O)O\n",[2347],{"type":20,"tag":84,"props":2348,"children":2349},{"__ignoreMap":8},[2350],{"type":20,"tag":157,"props":2351,"children":2352},{"class":159,"line":160},[2353],{"type":20,"tag":157,"props":2354,"children":2355},{},[2356],{"type":25,"value":2345},{"type":20,"tag":21,"props":2358,"children":2359},{},[2360],{"type":25,"value":2361},"这不是给人看的“化学名称”，而是给程序解析的结构描述。RDKit 就是负责把这种字符串变成可计算对象的工具。",{"type":20,"tag":1863,"props":2363,"children":2365},{"id":2364},"rdkit-负责什么",[2366],{"type":25,"value":2367},"RDKit 负责什么",{"type":20,"tag":21,"props":2369,"children":2370},{},[2371],{"type":25,"value":2372},"RDKit 不是模型，也不是分类器。",{"type":20,"tag":21,"props":2374,"children":2375},{},[2376],{"type":25,"value":2377},"它更像一个化学信息学工具箱，可以做这些事情：",{"type":20,"tag":126,"props":2379,"children":2381},{"className":324,"code":2380,"language":25,"meta":8,"style":8},"SMILES -> Mol 对象\nMol 对象 -> 分子图片\nMol 对象 -> 分子描述符\nMol 对象 -> 分子指纹\nMol 对象 -> 标准化 SMILES\n",[2382],{"type":20,"tag":84,"props":2383,"children":2384},{"__ignoreMap":8},[2385,2393,2401,2409,2417],{"type":20,"tag":157,"props":2386,"children":2387},{"class":159,"line":160},[2388],{"type":20,"tag":157,"props":2389,"children":2390},{},[2391],{"type":25,"value":2392},"SMILES -> Mol 对象\n",{"type":20,"tag":157,"props":2394,"children":2395},{"class":159,"line":169},[2396],{"type":20,"tag":157,"props":2397,"children":2398},{},[2399],{"type":25,"value":2400},"Mol 对象 -> 分子图片\n",{"type":20,"tag":157,"props":2402,"children":2403},{"class":159,"line":179},[2404],{"type":20,"tag":157,"props":2405,"children":2406},{},[2407],{"type":25,"value":2408},"Mol 对象 -> 分子描述符\n",{"type":20,"tag":157,"props":2410,"children":2411},{"class":159,"line":188},[2412],{"type":20,"tag":157,"props":2413,"children":2414},{},[2415],{"type":25,"value":2416},"Mol 对象 -> 分子指纹\n",{"type":20,"tag":157,"props":2418,"children":2419},{"class":159,"line":196},[2420],{"type":20,"tag":157,"props":2421,"children":2422},{},[2423],{"type":25,"value":2424},"Mol 对象 -> 标准化 SMILES\n",{"type":20,"tag":21,"props":2426,"children":2427},{},[2428],{"type":25,"value":2429},"在工程链路里，RDKit 通常位于“原始输入”和“机器学习特征”之间。",{"type":20,"tag":21,"props":2431,"children":2432},{},[2433],{"type":25,"value":2434},"用户输入的是 SMILES，模型需要的是向量。RDKit 负责把中间这段路铺出来。",{"type":20,"tag":1863,"props":2436,"children":2438},{"id":2437},"mol-对象rdkit-眼里的分子",[2439],{"type":25,"value":2440},"Mol 对象：RDKit 眼里的分子",{"type":20,"tag":21,"props":2442,"children":2443},{},[2444,2446,2452],{"type":25,"value":2445},"RDKit 解析 SMILES 之后，会得到一个 ",{"type":20,"tag":84,"props":2447,"children":2449},{"className":2448},[],[2450],{"type":25,"value":2451},"Mol",{"type":25,"value":2453}," 对象。",{"type":20,"tag":126,"props":2455,"children":2457},{"className":151,"code":2456,"language":150,"meta":8,"style":8},"from rdkit import Chem\n\nmol = Chem.MolFromSmiles(\"CCO\")\n\nif mol is None:\n    print(\"不合法 SMILES\")\nelse:\n    print(\"合法分子\")\n",[2458],{"type":20,"tag":84,"props":2459,"children":2460},{"__ignoreMap":8},[2461,2469,2476,2484,2491,2499,2507,2515],{"type":20,"tag":157,"props":2462,"children":2463},{"class":159,"line":160},[2464],{"type":20,"tag":157,"props":2465,"children":2466},{},[2467],{"type":25,"value":2468},"from rdkit import Chem\n",{"type":20,"tag":157,"props":2470,"children":2471},{"class":159,"line":169},[2472],{"type":20,"tag":157,"props":2473,"children":2474},{"emptyLinePlaceholder":173},[2475],{"type":25,"value":176},{"type":20,"tag":157,"props":2477,"children":2478},{"class":159,"line":179},[2479],{"type":20,"tag":157,"props":2480,"children":2481},{},[2482],{"type":25,"value":2483},"mol = Chem.MolFromSmiles(\"CCO\")\n",{"type":20,"tag":157,"props":2485,"children":2486},{"class":159,"line":188},[2487],{"type":20,"tag":157,"props":2488,"children":2489},{"emptyLinePlaceholder":173},[2490],{"type":25,"value":176},{"type":20,"tag":157,"props":2492,"children":2493},{"class":159,"line":196},[2494],{"type":20,"tag":157,"props":2495,"children":2496},{},[2497],{"type":25,"value":2498},"if mol is None:\n",{"type":20,"tag":157,"props":2500,"children":2501},{"class":159,"line":204},[2502],{"type":20,"tag":157,"props":2503,"children":2504},{},[2505],{"type":25,"value":2506},"    print(\"不合法 SMILES\")\n",{"type":20,"tag":157,"props":2508,"children":2509},{"class":159,"line":213},[2510],{"type":20,"tag":157,"props":2511,"children":2512},{},[2513],{"type":25,"value":2514},"else:\n",{"type":20,"tag":157,"props":2516,"children":2517},{"class":159,"line":222},[2518],{"type":20,"tag":157,"props":2519,"children":2520},{},[2521],{"type":25,"value":2522},"    print(\"合法分子\")\n",{"type":20,"tag":21,"props":2524,"children":2525},{},[2526,2531],{"type":20,"tag":84,"props":2527,"children":2529},{"className":2528},[],[2530],{"type":25,"value":2451},{"type":25,"value":2532}," 对象可以理解成 RDKit 内部的分子结构图。它不只是保存一串文本，而是保存了原子、化学键、芳香性、价态等信息。",{"type":20,"tag":21,"props":2534,"children":2535},{},[2536,2538,2544,2546,2552],{"type":25,"value":2537},"如果 ",{"type":20,"tag":84,"props":2539,"children":2541},{"className":2540},[],[2542],{"type":25,"value":2543},"Chem.MolFromSmiles()",{"type":25,"value":2545}," 解析失败，会返回 ",{"type":20,"tag":84,"props":2547,"children":2549},{"className":2548},[],[2550],{"type":25,"value":2551},"None",{"type":25,"value":2553},"。所以只要系统入口允许用户输入 SMILES，就应该先做合法性检查。",{"type":20,"tag":21,"props":2555,"children":2556},{},[2557],{"type":25,"value":2558},"在工具库里，可以直接抛异常：",{"type":20,"tag":126,"props":2560,"children":2562},{"className":151,"code":2561,"language":150,"meta":8,"style":8},"from rdkit import Chem\n\n\ndef parse_smiles(smiles: str):\n    mol = Chem.MolFromSmiles(smiles)\n    if mol is None:\n        raise ValueError(f\"无效的 SMILES: {smiles}\")\n    return mol\n",[2563],{"type":20,"tag":84,"props":2564,"children":2565},{"__ignoreMap":8},[2566,2573,2580,2587,2595,2603,2611,2619],{"type":20,"tag":157,"props":2567,"children":2568},{"class":159,"line":160},[2569],{"type":20,"tag":157,"props":2570,"children":2571},{},[2572],{"type":25,"value":2468},{"type":20,"tag":157,"props":2574,"children":2575},{"class":159,"line":169},[2576],{"type":20,"tag":157,"props":2577,"children":2578},{"emptyLinePlaceholder":173},[2579],{"type":25,"value":176},{"type":20,"tag":157,"props":2581,"children":2582},{"class":159,"line":179},[2583],{"type":20,"tag":157,"props":2584,"children":2585},{"emptyLinePlaceholder":173},[2586],{"type":25,"value":176},{"type":20,"tag":157,"props":2588,"children":2589},{"class":159,"line":188},[2590],{"type":20,"tag":157,"props":2591,"children":2592},{},[2593],{"type":25,"value":2594},"def parse_smiles(smiles: str):\n",{"type":20,"tag":157,"props":2596,"children":2597},{"class":159,"line":196},[2598],{"type":20,"tag":157,"props":2599,"children":2600},{},[2601],{"type":25,"value":2602},"    mol = Chem.MolFromSmiles(smiles)\n",{"type":20,"tag":157,"props":2604,"children":2605},{"class":159,"line":204},[2606],{"type":20,"tag":157,"props":2607,"children":2608},{},[2609],{"type":25,"value":2610},"    if mol is None:\n",{"type":20,"tag":157,"props":2612,"children":2613},{"class":159,"line":213},[2614],{"type":20,"tag":157,"props":2615,"children":2616},{},[2617],{"type":25,"value":2618},"        raise ValueError(f\"无效的 SMILES: {smiles}\")\n",{"type":20,"tag":157,"props":2620,"children":2621},{"class":159,"line":222},[2622],{"type":20,"tag":157,"props":2623,"children":2624},{},[2625],{"type":25,"value":2626},"    return mol\n",{"type":20,"tag":21,"props":2628,"children":2629},{},[2630],{"type":25,"value":2631},"如果放到 Web API 里，就不应该把 Python 异常原样甩给前端，而是要转成稳定的业务错误。",{"type":20,"tag":21,"props":2633,"children":2634},{},[2635],{"type":25,"value":2636},"FastAPI 里可能是：",{"type":20,"tag":126,"props":2638,"children":2640},{"className":882,"code":2639,"language":881,"meta":8,"style":8},"{\n  \"code\": 400,\n  \"detail\": \"无效的 SMILES\"\n}\n",[2641],{"type":20,"tag":84,"props":2642,"children":2643},{"__ignoreMap":8},[2644,2651,2671,2688],{"type":20,"tag":157,"props":2645,"children":2646},{"class":159,"line":160},[2647],{"type":20,"tag":157,"props":2648,"children":2649},{"style":892},[2650],{"type":25,"value":895},{"type":20,"tag":157,"props":2652,"children":2653},{"class":159,"line":169},[2654,2659,2663,2667],{"type":20,"tag":157,"props":2655,"children":2656},{"style":260},[2657],{"type":25,"value":2658},"  \"code\"",{"type":20,"tag":157,"props":2660,"children":2661},{"style":892},[2662],{"type":25,"value":908},{"type":20,"tag":157,"props":2664,"children":2665},{"style":260},[2666],{"type":25,"value":1172},{"type":20,"tag":157,"props":2668,"children":2669},{"style":892},[2670],{"type":25,"value":918},{"type":20,"tag":157,"props":2672,"children":2673},{"class":159,"line":179},[2674,2679,2683],{"type":20,"tag":157,"props":2675,"children":2676},{"style":260},[2677],{"type":25,"value":2678},"  \"detail\"",{"type":20,"tag":157,"props":2680,"children":2681},{"style":892},[2682],{"type":25,"value":908},{"type":20,"tag":157,"props":2684,"children":2685},{"style":254},[2686],{"type":25,"value":2687},"\"无效的 SMILES\"\n",{"type":20,"tag":157,"props":2689,"children":2690},{"class":159,"line":188},[2691],{"type":20,"tag":157,"props":2692,"children":2693},{"style":892},[2694],{"type":25,"value":943},{"type":20,"tag":21,"props":2696,"children":2697},{},[2698],{"type":25,"value":2699},"如果后端是 Spring Boot，也可以设计成更明确的错误结构：",{"type":20,"tag":126,"props":2701,"children":2703},{"className":882,"code":2702,"language":881,"meta":8,"style":8},"{\n  \"code\": \"INVALID_SMILES\",\n  \"message\": \"您输入的分子式格式不正确\",\n  \"timestamp\": \"2026-05-18T10:00:00\"\n}\n",[2704],{"type":20,"tag":84,"props":2705,"children":2706},{"__ignoreMap":8},[2707,2714,2734,2755,2772],{"type":20,"tag":157,"props":2708,"children":2709},{"class":159,"line":160},[2710],{"type":20,"tag":157,"props":2711,"children":2712},{"style":892},[2713],{"type":25,"value":895},{"type":20,"tag":157,"props":2715,"children":2716},{"class":159,"line":169},[2717,2721,2725,2730],{"type":20,"tag":157,"props":2718,"children":2719},{"style":260},[2720],{"type":25,"value":2658},{"type":20,"tag":157,"props":2722,"children":2723},{"style":892},[2724],{"type":25,"value":908},{"type":20,"tag":157,"props":2726,"children":2727},{"style":254},[2728],{"type":25,"value":2729},"\"INVALID_SMILES\"",{"type":20,"tag":157,"props":2731,"children":2732},{"style":892},[2733],{"type":25,"value":918},{"type":20,"tag":157,"props":2735,"children":2736},{"class":159,"line":179},[2737,2742,2746,2751],{"type":20,"tag":157,"props":2738,"children":2739},{"style":260},[2740],{"type":25,"value":2741},"  \"message\"",{"type":20,"tag":157,"props":2743,"children":2744},{"style":892},[2745],{"type":25,"value":908},{"type":20,"tag":157,"props":2747,"children":2748},{"style":254},[2749],{"type":25,"value":2750},"\"您输入的分子式格式不正确\"",{"type":20,"tag":157,"props":2752,"children":2753},{"style":892},[2754],{"type":25,"value":918},{"type":20,"tag":157,"props":2756,"children":2757},{"class":159,"line":188},[2758,2763,2767],{"type":20,"tag":157,"props":2759,"children":2760},{"style":260},[2761],{"type":25,"value":2762},"  \"timestamp\"",{"type":20,"tag":157,"props":2764,"children":2765},{"style":892},[2766],{"type":25,"value":908},{"type":20,"tag":157,"props":2768,"children":2769},{"style":254},[2770],{"type":25,"value":2771},"\"2026-05-18T10:00:00\"\n",{"type":20,"tag":157,"props":2773,"children":2774},{"class":159,"line":196},[2775],{"type":20,"tag":157,"props":2776,"children":2777},{"style":892},[2778],{"type":25,"value":943},{"type":20,"tag":21,"props":2780,"children":2781},{},[2782,2784,2789],{"type":25,"value":2783},"这就是从“写脚本”走向“做平台”时必须补上的一层：",{"type":20,"tag":33,"props":2785,"children":2786},{},[2787],{"type":25,"value":2788},"底层库负责发现错误，业务接口负责表达错误",{"type":25,"value":110},{"type":20,"tag":1863,"props":2791,"children":2793},{"id":2792},"canonical-smiles统一同一个分子的不同写法",[2794],{"type":25,"value":2795},"Canonical SMILES：统一同一个分子的不同写法",{"type":20,"tag":21,"props":2797,"children":2798},{},[2799],{"type":25,"value":2800},"同一个分子可能有多种 SMILES 写法。",{"type":20,"tag":21,"props":2802,"children":2803},{},[2804],{"type":25,"value":2157},{"type":20,"tag":126,"props":2806,"children":2808},{"className":324,"code":2807,"language":25,"meta":8,"style":8},"CCO\nOCC\n",[2809],{"type":20,"tag":84,"props":2810,"children":2811},{"__ignoreMap":8},[2812,2819],{"type":20,"tag":157,"props":2813,"children":2814},{"class":159,"line":160},[2815],{"type":20,"tag":157,"props":2816,"children":2817},{},[2818],{"type":25,"value":2160},{"type":20,"tag":157,"props":2820,"children":2821},{"class":159,"line":169},[2822],{"type":20,"tag":157,"props":2823,"children":2824},{},[2825],{"type":25,"value":2826},"OCC\n",{"type":20,"tag":21,"props":2828,"children":2829},{},[2830],{"type":25,"value":2831},"对人来说都能看懂，但对系统来说，如果不做标准化，它们就是两条不同的字符串。这会影响去重、缓存、数据库索引和相似性搜索。",{"type":20,"tag":21,"props":2833,"children":2834},{},[2835],{"type":25,"value":2836},"RDKit 可以把不同写法统一成标准形式：",{"type":20,"tag":126,"props":2838,"children":2840},{"className":151,"code":2839,"language":150,"meta":8,"style":8},"from rdkit import Chem\n\n\ndef canonicalize_smiles(smiles: str) -> str:\n    mol = Chem.MolFromSmiles(smiles)\n    if mol is None:\n        raise ValueError(f\"无效的 SMILES: {smiles}\")\n    return Chem.MolToSmiles(mol, canonical=True)\n\n\nprint(canonicalize_smiles(\"OCC\"))\n",[2841],{"type":20,"tag":84,"props":2842,"children":2843},{"__ignoreMap":8},[2844,2851,2858,2865,2873,2880,2887,2894,2902,2909,2916],{"type":20,"tag":157,"props":2845,"children":2846},{"class":159,"line":160},[2847],{"type":20,"tag":157,"props":2848,"children":2849},{},[2850],{"type":25,"value":2468},{"type":20,"tag":157,"props":2852,"children":2853},{"class":159,"line":169},[2854],{"type":20,"tag":157,"props":2855,"children":2856},{"emptyLinePlaceholder":173},[2857],{"type":25,"value":176},{"type":20,"tag":157,"props":2859,"children":2860},{"class":159,"line":179},[2861],{"type":20,"tag":157,"props":2862,"children":2863},{"emptyLinePlaceholder":173},[2864],{"type":25,"value":176},{"type":20,"tag":157,"props":2866,"children":2867},{"class":159,"line":188},[2868],{"type":20,"tag":157,"props":2869,"children":2870},{},[2871],{"type":25,"value":2872},"def canonicalize_smiles(smiles: str) -> str:\n",{"type":20,"tag":157,"props":2874,"children":2875},{"class":159,"line":196},[2876],{"type":20,"tag":157,"props":2877,"children":2878},{},[2879],{"type":25,"value":2602},{"type":20,"tag":157,"props":2881,"children":2882},{"class":159,"line":204},[2883],{"type":20,"tag":157,"props":2884,"children":2885},{},[2886],{"type":25,"value":2610},{"type":20,"tag":157,"props":2888,"children":2889},{"class":159,"line":213},[2890],{"type":20,"tag":157,"props":2891,"children":2892},{},[2893],{"type":25,"value":2618},{"type":20,"tag":157,"props":2895,"children":2896},{"class":159,"line":222},[2897],{"type":20,"tag":157,"props":2898,"children":2899},{},[2900],{"type":25,"value":2901},"    return Chem.MolToSmiles(mol, canonical=True)\n",{"type":20,"tag":157,"props":2903,"children":2904},{"class":159,"line":440},[2905],{"type":20,"tag":157,"props":2906,"children":2907},{"emptyLinePlaceholder":173},[2908],{"type":25,"value":176},{"type":20,"tag":157,"props":2910,"children":2911},{"class":159,"line":448},[2912],{"type":20,"tag":157,"props":2913,"children":2914},{"emptyLinePlaceholder":173},[2915],{"type":25,"value":176},{"type":20,"tag":157,"props":2917,"children":2918},{"class":159,"line":456},[2919],{"type":20,"tag":157,"props":2920,"children":2921},{},[2922],{"type":25,"value":2923},"print(canonicalize_smiles(\"OCC\"))\n",{"type":20,"tag":21,"props":2925,"children":2926},{},[2927],{"type":25,"value":2928},"在平台里，一个常见策略是：",{"type":20,"tag":126,"props":2930,"children":2932},{"className":324,"code":2931,"language":25,"meta":8,"style":8},"用户输入 SMILES -> RDKit 解析 -> Canonical SMILES -> 入库 \u002F 检索 \u002F 建模\n",[2933],{"type":20,"tag":84,"props":2934,"children":2935},{"__ignoreMap":8},[2936],{"type":20,"tag":157,"props":2937,"children":2938},{"class":159,"line":160},[2939],{"type":20,"tag":157,"props":2940,"children":2941},{},[2942],{"type":25,"value":2931},{"type":20,"tag":21,"props":2944,"children":2945},{},[2946],{"type":25,"value":2947},"这样用户输入的格式可以灵活，但系统内部使用统一表示。",{"type":20,"tag":1863,"props":2949,"children":2951},{"id":2950},"生成分子图片让结构可视化",[2952],{"type":25,"value":2953},"生成分子图片：让结构可视化",{"type":20,"tag":21,"props":2955,"children":2956},{},[2957],{"type":25,"value":2958},"分子进入系统后，除了用于计算，也常常需要展示给用户。",{"type":20,"tag":21,"props":2960,"children":2961},{},[2962,2964,2969],{"type":25,"value":2963},"RDKit 可以直接把 ",{"type":20,"tag":84,"props":2965,"children":2967},{"className":2966},[],[2968],{"type":25,"value":2451},{"type":25,"value":2970}," 对象画成图片：",{"type":20,"tag":126,"props":2972,"children":2974},{"className":151,"code":2973,"language":150,"meta":8,"style":8},"from pathlib import Path\n\nfrom PIL.Image import Image\nfrom rdkit import Chem, RDLogger\nfrom rdkit.Chem import Draw\n\nRDLogger.DisableLog(\"rdApp.*\")\n\nDEFAULT_SIZE = (300, 300)\n\n\ndef smiles_to_image(smiles: str, size: tuple[int, int] = DEFAULT_SIZE) -> Image:\n    mol = Chem.MolFromSmiles(smiles)\n    if mol is None:\n        raise ValueError(f\"无效的 SMILES: {smiles}\")\n\n    return Draw.MolToImage(mol, size=size)\n\n\ndef save_smiles_image(\n    smiles: str,\n    output_path: str | Path,\n    name: str = \"分子图片\",\n    size: tuple[int, int] = DEFAULT_SIZE,\n) -> Path:\n    path = Path(output_path)\n\n    if path.suffix == \"\":\n        path.mkdir(parents=True, exist_ok=True)\n        path = path \u002F f\"{name}.png\"\n    else:\n        path.parent.mkdir(parents=True, exist_ok=True)\n\n    image = smiles_to_image(smiles, size=size)\n    image.save(path)\n    return path\n\n\nif __name__ == \"__main__\":\n    save_smiles_image(\n        \"CC(=O)Oc1ccccc1C(=O)O\",\n        \"..\u002Foutputs\u002Fimages\u002F\",\n        name=\"阿司匹林\",\n    )\n",[2975],{"type":20,"tag":84,"props":2976,"children":2977},{"__ignoreMap":8},[2978,2986,2993,3001,3009,3017,3024,3032,3039,3047,3054,3061,3069,3076,3083,3090,3097,3105,3112,3119,3127,3135,3143,3151,3159,3167,3175,3182,3190,3198,3206,3214,3222,3229,3237,3245,3253,3260,3267,3275,3283,3291,3299,3307],{"type":20,"tag":157,"props":2979,"children":2980},{"class":159,"line":160},[2981],{"type":20,"tag":157,"props":2982,"children":2983},{},[2984],{"type":25,"value":2985},"from pathlib import Path\n",{"type":20,"tag":157,"props":2987,"children":2988},{"class":159,"line":169},[2989],{"type":20,"tag":157,"props":2990,"children":2991},{"emptyLinePlaceholder":173},[2992],{"type":25,"value":176},{"type":20,"tag":157,"props":2994,"children":2995},{"class":159,"line":179},[2996],{"type":20,"tag":157,"props":2997,"children":2998},{},[2999],{"type":25,"value":3000},"from PIL.Image import Image\n",{"type":20,"tag":157,"props":3002,"children":3003},{"class":159,"line":188},[3004],{"type":20,"tag":157,"props":3005,"children":3006},{},[3007],{"type":25,"value":3008},"from rdkit import Chem, RDLogger\n",{"type":20,"tag":157,"props":3010,"children":3011},{"class":159,"line":196},[3012],{"type":20,"tag":157,"props":3013,"children":3014},{},[3015],{"type":25,"value":3016},"from rdkit.Chem import Draw\n",{"type":20,"tag":157,"props":3018,"children":3019},{"class":159,"line":204},[3020],{"type":20,"tag":157,"props":3021,"children":3022},{"emptyLinePlaceholder":173},[3023],{"type":25,"value":176},{"type":20,"tag":157,"props":3025,"children":3026},{"class":159,"line":213},[3027],{"type":20,"tag":157,"props":3028,"children":3029},{},[3030],{"type":25,"value":3031},"RDLogger.DisableLog(\"rdApp.*\")\n",{"type":20,"tag":157,"props":3033,"children":3034},{"class":159,"line":222},[3035],{"type":20,"tag":157,"props":3036,"children":3037},{"emptyLinePlaceholder":173},[3038],{"type":25,"value":176},{"type":20,"tag":157,"props":3040,"children":3041},{"class":159,"line":440},[3042],{"type":20,"tag":157,"props":3043,"children":3044},{},[3045],{"type":25,"value":3046},"DEFAULT_SIZE = (300, 300)\n",{"type":20,"tag":157,"props":3048,"children":3049},{"class":159,"line":448},[3050],{"type":20,"tag":157,"props":3051,"children":3052},{"emptyLinePlaceholder":173},[3053],{"type":25,"value":176},{"type":20,"tag":157,"props":3055,"children":3056},{"class":159,"line":456},[3057],{"type":20,"tag":157,"props":3058,"children":3059},{"emptyLinePlaceholder":173},[3060],{"type":25,"value":176},{"type":20,"tag":157,"props":3062,"children":3063},{"class":159,"line":465},[3064],{"type":20,"tag":157,"props":3065,"children":3066},{},[3067],{"type":25,"value":3068},"def smiles_to_image(smiles: str, size: tuple[int, int] = DEFAULT_SIZE) -> Image:\n",{"type":20,"tag":157,"props":3070,"children":3071},{"class":159,"line":474},[3072],{"type":20,"tag":157,"props":3073,"children":3074},{},[3075],{"type":25,"value":2602},{"type":20,"tag":157,"props":3077,"children":3078},{"class":159,"line":483},[3079],{"type":20,"tag":157,"props":3080,"children":3081},{},[3082],{"type":25,"value":2610},{"type":20,"tag":157,"props":3084,"children":3085},{"class":159,"line":491},[3086],{"type":20,"tag":157,"props":3087,"children":3088},{},[3089],{"type":25,"value":2618},{"type":20,"tag":157,"props":3091,"children":3092},{"class":159,"line":499},[3093],{"type":20,"tag":157,"props":3094,"children":3095},{"emptyLinePlaceholder":173},[3096],{"type":25,"value":176},{"type":20,"tag":157,"props":3098,"children":3099},{"class":159,"line":508},[3100],{"type":20,"tag":157,"props":3101,"children":3102},{},[3103],{"type":25,"value":3104},"    return Draw.MolToImage(mol, size=size)\n",{"type":20,"tag":157,"props":3106,"children":3107},{"class":159,"line":517},[3108],{"type":20,"tag":157,"props":3109,"children":3110},{"emptyLinePlaceholder":173},[3111],{"type":25,"value":176},{"type":20,"tag":157,"props":3113,"children":3114},{"class":159,"line":1499},[3115],{"type":20,"tag":157,"props":3116,"children":3117},{"emptyLinePlaceholder":173},[3118],{"type":25,"value":176},{"type":20,"tag":157,"props":3120,"children":3121},{"class":159,"line":1507},[3122],{"type":20,"tag":157,"props":3123,"children":3124},{},[3125],{"type":25,"value":3126},"def save_smiles_image(\n",{"type":20,"tag":157,"props":3128,"children":3129},{"class":159,"line":1515},[3130],{"type":20,"tag":157,"props":3131,"children":3132},{},[3133],{"type":25,"value":3134},"    smiles: str,\n",{"type":20,"tag":157,"props":3136,"children":3137},{"class":159,"line":1523},[3138],{"type":20,"tag":157,"props":3139,"children":3140},{},[3141],{"type":25,"value":3142},"    output_path: str | Path,\n",{"type":20,"tag":157,"props":3144,"children":3145},{"class":159,"line":1532},[3146],{"type":20,"tag":157,"props":3147,"children":3148},{},[3149],{"type":25,"value":3150},"    name: str = \"分子图片\",\n",{"type":20,"tag":157,"props":3152,"children":3153},{"class":159,"line":1541},[3154],{"type":20,"tag":157,"props":3155,"children":3156},{},[3157],{"type":25,"value":3158},"    size: tuple[int, int] = DEFAULT_SIZE,\n",{"type":20,"tag":157,"props":3160,"children":3161},{"class":159,"line":1550},[3162],{"type":20,"tag":157,"props":3163,"children":3164},{},[3165],{"type":25,"value":3166},") -> Path:\n",{"type":20,"tag":157,"props":3168,"children":3169},{"class":159,"line":1558},[3170],{"type":20,"tag":157,"props":3171,"children":3172},{},[3173],{"type":25,"value":3174},"    path = Path(output_path)\n",{"type":20,"tag":157,"props":3176,"children":3177},{"class":159,"line":1566},[3178],{"type":20,"tag":157,"props":3179,"children":3180},{"emptyLinePlaceholder":173},[3181],{"type":25,"value":176},{"type":20,"tag":157,"props":3183,"children":3184},{"class":159,"line":1574},[3185],{"type":20,"tag":157,"props":3186,"children":3187},{},[3188],{"type":25,"value":3189},"    if path.suffix == \"\":\n",{"type":20,"tag":157,"props":3191,"children":3192},{"class":159,"line":1582},[3193],{"type":20,"tag":157,"props":3194,"children":3195},{},[3196],{"type":25,"value":3197},"        path.mkdir(parents=True, exist_ok=True)\n",{"type":20,"tag":157,"props":3199,"children":3200},{"class":159,"line":1591},[3201],{"type":20,"tag":157,"props":3202,"children":3203},{},[3204],{"type":25,"value":3205},"        path = path \u002F f\"{name}.png\"\n",{"type":20,"tag":157,"props":3207,"children":3208},{"class":159,"line":1599},[3209],{"type":20,"tag":157,"props":3210,"children":3211},{},[3212],{"type":25,"value":3213},"    else:\n",{"type":20,"tag":157,"props":3215,"children":3216},{"class":159,"line":1608},[3217],{"type":20,"tag":157,"props":3218,"children":3219},{},[3220],{"type":25,"value":3221},"        path.parent.mkdir(parents=True, exist_ok=True)\n",{"type":20,"tag":157,"props":3223,"children":3224},{"class":159,"line":1617},[3225],{"type":20,"tag":157,"props":3226,"children":3227},{"emptyLinePlaceholder":173},[3228],{"type":25,"value":176},{"type":20,"tag":157,"props":3230,"children":3231},{"class":159,"line":1626},[3232],{"type":20,"tag":157,"props":3233,"children":3234},{},[3235],{"type":25,"value":3236},"    image = smiles_to_image(smiles, size=size)\n",{"type":20,"tag":157,"props":3238,"children":3239},{"class":159,"line":1634},[3240],{"type":20,"tag":157,"props":3241,"children":3242},{},[3243],{"type":25,"value":3244},"    image.save(path)\n",{"type":20,"tag":157,"props":3246,"children":3247},{"class":159,"line":1642},[3248],{"type":20,"tag":157,"props":3249,"children":3250},{},[3251],{"type":25,"value":3252},"    return path\n",{"type":20,"tag":157,"props":3254,"children":3255},{"class":159,"line":1650},[3256],{"type":20,"tag":157,"props":3257,"children":3258},{"emptyLinePlaceholder":173},[3259],{"type":25,"value":176},{"type":20,"tag":157,"props":3261,"children":3262},{"class":159,"line":1659},[3263],{"type":20,"tag":157,"props":3264,"children":3265},{"emptyLinePlaceholder":173},[3266],{"type":25,"value":176},{"type":20,"tag":157,"props":3268,"children":3269},{"class":159,"line":1668},[3270],{"type":20,"tag":157,"props":3271,"children":3272},{},[3273],{"type":25,"value":3274},"if __name__ == \"__main__\":\n",{"type":20,"tag":157,"props":3276,"children":3277},{"class":159,"line":1677},[3278],{"type":20,"tag":157,"props":3279,"children":3280},{},[3281],{"type":25,"value":3282},"    save_smiles_image(\n",{"type":20,"tag":157,"props":3284,"children":3285},{"class":159,"line":1685},[3286],{"type":20,"tag":157,"props":3287,"children":3288},{},[3289],{"type":25,"value":3290},"        \"CC(=O)Oc1ccccc1C(=O)O\",\n",{"type":20,"tag":157,"props":3292,"children":3293},{"class":159,"line":1693},[3294],{"type":20,"tag":157,"props":3295,"children":3296},{},[3297],{"type":25,"value":3298},"        \"..\u002Foutputs\u002Fimages\u002F\",\n",{"type":20,"tag":157,"props":3300,"children":3301},{"class":159,"line":1701},[3302],{"type":20,"tag":157,"props":3303,"children":3304},{},[3305],{"type":25,"value":3306},"        name=\"阿司匹林\",\n",{"type":20,"tag":157,"props":3308,"children":3309},{"class":159,"line":1710},[3310],{"type":20,"tag":157,"props":3311,"children":3312},{},[3313],{"type":25,"value":3314},"    )\n",{"type":20,"tag":21,"props":3316,"children":3317},{},[3318],{"type":25,"value":3319},"到这里，SMILES 已经不只是字符串了。它可以被验证、标准化，也可以被画出来。",{"type":20,"tag":21,"props":3321,"children":3322},{},[3323],{"type":25,"value":3324},"下一步是把它变成模型能用的数字。",{"type":20,"tag":1863,"props":3326,"children":3328},{"id":3327},"分子描述符可解释的低维特征",[3329],{"type":25,"value":3330},"分子描述符：可解释的低维特征",{"type":20,"tag":21,"props":3332,"children":3333},{},[3334],{"type":25,"value":3335},"分子描述符是对一个分子的数值化描述。",{"type":20,"tag":21,"props":3337,"children":3338},{},[3339],{"type":25,"value":3340},"比如阿司匹林可以被描述成一组性质：",{"type":20,"tag":126,"props":3342,"children":3344},{"className":324,"code":3343,"language":25,"meta":8,"style":8},"分子量：180.16\nLogP：1.31\nTPSA：63.60\n氢键供体数量：1\n氢键受体数量：4\n可旋转键数量：2\n芳香环数量：1\n",[3345],{"type":20,"tag":84,"props":3346,"children":3347},{"__ignoreMap":8},[3348,3356,3364,3372,3380,3388,3396],{"type":20,"tag":157,"props":3349,"children":3350},{"class":159,"line":160},[3351],{"type":20,"tag":157,"props":3352,"children":3353},{},[3354],{"type":25,"value":3355},"分子量：180.16\n",{"type":20,"tag":157,"props":3357,"children":3358},{"class":159,"line":169},[3359],{"type":20,"tag":157,"props":3360,"children":3361},{},[3362],{"type":25,"value":3363},"LogP：1.31\n",{"type":20,"tag":157,"props":3365,"children":3366},{"class":159,"line":179},[3367],{"type":20,"tag":157,"props":3368,"children":3369},{},[3370],{"type":25,"value":3371},"TPSA：63.60\n",{"type":20,"tag":157,"props":3373,"children":3374},{"class":159,"line":188},[3375],{"type":20,"tag":157,"props":3376,"children":3377},{},[3378],{"type":25,"value":3379},"氢键供体数量：1\n",{"type":20,"tag":157,"props":3381,"children":3382},{"class":159,"line":196},[3383],{"type":20,"tag":157,"props":3384,"children":3385},{},[3386],{"type":25,"value":3387},"氢键受体数量：4\n",{"type":20,"tag":157,"props":3389,"children":3390},{"class":159,"line":204},[3391],{"type":20,"tag":157,"props":3392,"children":3393},{},[3394],{"type":25,"value":3395},"可旋转键数量：2\n",{"type":20,"tag":157,"props":3397,"children":3398},{"class":159,"line":213},[3399],{"type":20,"tag":157,"props":3400,"children":3401},{},[3402],{"type":25,"value":3403},"芳香环数量：1\n",{"type":20,"tag":21,"props":3405,"children":3406},{},[3407],{"type":25,"value":3408},"这些数值可以组成一个向量：",{"type":20,"tag":126,"props":3410,"children":3412},{"className":324,"code":3411,"language":25,"meta":8,"style":8},"[180.16, 1.31, 63.60, 1, 4, 2, 1]\n",[3413],{"type":20,"tag":84,"props":3414,"children":3415},{"__ignoreMap":8},[3416],{"type":20,"tag":157,"props":3417,"children":3418},{"class":159,"line":160},[3419],{"type":20,"tag":157,"props":3420,"children":3421},{},[3422],{"type":25,"value":3411},{"type":20,"tag":21,"props":3424,"children":3425},{},[3426],{"type":25,"value":3427},"模型并不理解“阿司匹林”这个名字，但它可以处理这样的数字向量。",{"type":20,"tag":21,"props":3429,"children":3430},{},[3431],{"type":25,"value":3432},"常见描述符包括：",{"type":20,"tag":1902,"props":3434,"children":3435},{},[3436,3456],{"type":20,"tag":1906,"props":3437,"children":3438},{},[3439],{"type":20,"tag":1910,"props":3440,"children":3441},{},[3442,3447,3451],{"type":20,"tag":1914,"props":3443,"children":3444},{},[3445],{"type":25,"value":3446},"描述符",{"type":20,"tag":1914,"props":3448,"children":3449},{},[3450],{"type":25,"value":2195},{"type":20,"tag":1914,"props":3452,"children":3453},{},[3454],{"type":25,"value":3455},"直觉",{"type":20,"tag":1930,"props":3457,"children":3458},{},[3459,3481,3503,3525,3562,3584,3606],{"type":20,"tag":1910,"props":3460,"children":3461},{},[3462,3471,3476],{"type":20,"tag":1937,"props":3463,"children":3464},{},[3465],{"type":20,"tag":84,"props":3466,"children":3468},{"className":3467},[],[3469],{"type":25,"value":3470},"MolWt",{"type":20,"tag":1937,"props":3472,"children":3473},{},[3474],{"type":25,"value":3475},"分子量",{"type":20,"tag":1937,"props":3477,"children":3478},{},[3479],{"type":25,"value":3480},"分子越大，吸收、代谢、穿膜等性质通常会受影响",{"type":20,"tag":1910,"props":3482,"children":3483},{},[3484,3493,3498],{"type":20,"tag":1937,"props":3485,"children":3486},{},[3487],{"type":20,"tag":84,"props":3488,"children":3490},{"className":3489},[],[3491],{"type":25,"value":3492},"LogP",{"type":20,"tag":1937,"props":3494,"children":3495},{},[3496],{"type":25,"value":3497},"脂水分配系数",{"type":20,"tag":1937,"props":3499,"children":3500},{},[3501],{"type":25,"value":3502},"越高越偏亲脂，越低越偏亲水",{"type":20,"tag":1910,"props":3504,"children":3505},{},[3506,3515,3520],{"type":20,"tag":1937,"props":3507,"children":3508},{},[3509],{"type":20,"tag":84,"props":3510,"children":3512},{"className":3511},[],[3513],{"type":25,"value":3514},"TPSA",{"type":20,"tag":1937,"props":3516,"children":3517},{},[3518],{"type":25,"value":3519},"拓扑极性表面积",{"type":20,"tag":1937,"props":3521,"children":3522},{},[3523],{"type":25,"value":3524},"越大通常越不容易穿过脂质膜或血脑屏障",{"type":20,"tag":1910,"props":3526,"children":3527},{},[3528,3537,3542],{"type":20,"tag":1937,"props":3529,"children":3530},{},[3531],{"type":20,"tag":84,"props":3532,"children":3534},{"className":3533},[],[3535],{"type":25,"value":3536},"HBD",{"type":20,"tag":1937,"props":3538,"children":3539},{},[3540],{"type":25,"value":3541},"氢键供体数量",{"type":20,"tag":1937,"props":3543,"children":3544},{},[3545,3547,3553,3554,3560],{"type":25,"value":3546},"比如 ",{"type":20,"tag":84,"props":3548,"children":3550},{"className":3549},[],[3551],{"type":25,"value":3552},"-OH",{"type":25,"value":965},{"type":20,"tag":84,"props":3555,"children":3557},{"className":3556},[],[3558],{"type":25,"value":3559},"-NH",{"type":25,"value":3561}," 这类能给出氢键的结构",{"type":20,"tag":1910,"props":3563,"children":3564},{},[3565,3574,3579],{"type":20,"tag":1937,"props":3566,"children":3567},{},[3568],{"type":20,"tag":84,"props":3569,"children":3571},{"className":3570},[],[3572],{"type":25,"value":3573},"HBA",{"type":20,"tag":1937,"props":3575,"children":3576},{},[3577],{"type":25,"value":3578},"氢键受体数量",{"type":20,"tag":1937,"props":3580,"children":3581},{},[3582],{"type":25,"value":3583},"比如 O、N 这类能接受氢键的原子",{"type":20,"tag":1910,"props":3585,"children":3586},{},[3587,3596,3601],{"type":20,"tag":1937,"props":3588,"children":3589},{},[3590],{"type":20,"tag":84,"props":3591,"children":3593},{"className":3592},[],[3594],{"type":25,"value":3595},"Rotatable Bonds",{"type":20,"tag":1937,"props":3597,"children":3598},{},[3599],{"type":25,"value":3600},"可旋转键数量",{"type":20,"tag":1937,"props":3602,"children":3603},{},[3604],{"type":25,"value":3605},"越多，分子越柔软，构象空间越大",{"type":20,"tag":1910,"props":3607,"children":3608},{},[3609,3618,3623],{"type":20,"tag":1937,"props":3610,"children":3611},{},[3612],{"type":20,"tag":84,"props":3613,"children":3615},{"className":3614},[],[3616],{"type":25,"value":3617},"Ring Count",{"type":20,"tag":1937,"props":3619,"children":3620},{},[3621],{"type":25,"value":3622},"环数量",{"type":20,"tag":1937,"props":3624,"children":3625},{},[3626],{"type":25,"value":3627},"环结构影响分子的刚性和结合方式",{"type":20,"tag":21,"props":3629,"children":3630},{},[3631],{"type":25,"value":3632},"用 RDKit 计算这些描述符：",{"type":20,"tag":126,"props":3634,"children":3636},{"className":151,"code":3635,"language":150,"meta":8,"style":8},"from rdkit import Chem\nfrom rdkit.Chem import Descriptors, Lipinski, rdMolDescriptors\n\n\ndef calc_basic_descriptors(smiles: str) -> dict[str, float | int]:\n    mol = Chem.MolFromSmiles(smiles)\n    if mol is None:\n        raise ValueError(f\"无效的 SMILES: {smiles}\")\n\n    return {\n        \"mol_wt\": round(Descriptors.MolWt(mol), 2),\n        \"logp\": round(Descriptors.MolLogP(mol), 2),\n        \"tpsa\": round(Descriptors.TPSA(mol), 2),\n        \"hbd\": Lipinski.NumHDonors(mol),\n        \"hba\": Lipinski.NumHAcceptors(mol),\n        \"rotatable_bonds\": Lipinski.NumRotatableBonds(mol),\n        \"ring_count\": rdMolDescriptors.CalcNumRings(mol),\n    }\n\n\nprint(calc_basic_descriptors(\"CC(=O)Oc1ccccc1C(=O)O\"))\n",[3637],{"type":20,"tag":84,"props":3638,"children":3639},{"__ignoreMap":8},[3640,3647,3655,3662,3669,3677,3684,3691,3698,3705,3712,3720,3728,3736,3744,3752,3760,3768,3775,3782,3789],{"type":20,"tag":157,"props":3641,"children":3642},{"class":159,"line":160},[3643],{"type":20,"tag":157,"props":3644,"children":3645},{},[3646],{"type":25,"value":2468},{"type":20,"tag":157,"props":3648,"children":3649},{"class":159,"line":169},[3650],{"type":20,"tag":157,"props":3651,"children":3652},{},[3653],{"type":25,"value":3654},"from rdkit.Chem import Descriptors, Lipinski, rdMolDescriptors\n",{"type":20,"tag":157,"props":3656,"children":3657},{"class":159,"line":179},[3658],{"type":20,"tag":157,"props":3659,"children":3660},{"emptyLinePlaceholder":173},[3661],{"type":25,"value":176},{"type":20,"tag":157,"props":3663,"children":3664},{"class":159,"line":188},[3665],{"type":20,"tag":157,"props":3666,"children":3667},{"emptyLinePlaceholder":173},[3668],{"type":25,"value":176},{"type":20,"tag":157,"props":3670,"children":3671},{"class":159,"line":196},[3672],{"type":20,"tag":157,"props":3673,"children":3674},{},[3675],{"type":25,"value":3676},"def calc_basic_descriptors(smiles: str) -> dict[str, float | int]:\n",{"type":20,"tag":157,"props":3678,"children":3679},{"class":159,"line":204},[3680],{"type":20,"tag":157,"props":3681,"children":3682},{},[3683],{"type":25,"value":2602},{"type":20,"tag":157,"props":3685,"children":3686},{"class":159,"line":213},[3687],{"type":20,"tag":157,"props":3688,"children":3689},{},[3690],{"type":25,"value":2610},{"type":20,"tag":157,"props":3692,"children":3693},{"class":159,"line":222},[3694],{"type":20,"tag":157,"props":3695,"children":3696},{},[3697],{"type":25,"value":2618},{"type":20,"tag":157,"props":3699,"children":3700},{"class":159,"line":440},[3701],{"type":20,"tag":157,"props":3702,"children":3703},{"emptyLinePlaceholder":173},[3704],{"type":25,"value":176},{"type":20,"tag":157,"props":3706,"children":3707},{"class":159,"line":448},[3708],{"type":20,"tag":157,"props":3709,"children":3710},{},[3711],{"type":25,"value":848},{"type":20,"tag":157,"props":3713,"children":3714},{"class":159,"line":456},[3715],{"type":20,"tag":157,"props":3716,"children":3717},{},[3718],{"type":25,"value":3719},"        \"mol_wt\": round(Descriptors.MolWt(mol), 2),\n",{"type":20,"tag":157,"props":3721,"children":3722},{"class":159,"line":465},[3723],{"type":20,"tag":157,"props":3724,"children":3725},{},[3726],{"type":25,"value":3727},"        \"logp\": round(Descriptors.MolLogP(mol), 2),\n",{"type":20,"tag":157,"props":3729,"children":3730},{"class":159,"line":474},[3731],{"type":20,"tag":157,"props":3732,"children":3733},{},[3734],{"type":25,"value":3735},"        \"tpsa\": round(Descriptors.TPSA(mol), 2),\n",{"type":20,"tag":157,"props":3737,"children":3738},{"class":159,"line":483},[3739],{"type":20,"tag":157,"props":3740,"children":3741},{},[3742],{"type":25,"value":3743},"        \"hbd\": Lipinski.NumHDonors(mol),\n",{"type":20,"tag":157,"props":3745,"children":3746},{"class":159,"line":491},[3747],{"type":20,"tag":157,"props":3748,"children":3749},{},[3750],{"type":25,"value":3751},"        \"hba\": Lipinski.NumHAcceptors(mol),\n",{"type":20,"tag":157,"props":3753,"children":3754},{"class":159,"line":499},[3755],{"type":20,"tag":157,"props":3756,"children":3757},{},[3758],{"type":25,"value":3759},"        \"rotatable_bonds\": Lipinski.NumRotatableBonds(mol),\n",{"type":20,"tag":157,"props":3761,"children":3762},{"class":159,"line":508},[3763],{"type":20,"tag":157,"props":3764,"children":3765},{},[3766],{"type":25,"value":3767},"        \"ring_count\": rdMolDescriptors.CalcNumRings(mol),\n",{"type":20,"tag":157,"props":3769,"children":3770},{"class":159,"line":517},[3771],{"type":20,"tag":157,"props":3772,"children":3773},{},[3774],{"type":25,"value":872},{"type":20,"tag":157,"props":3776,"children":3777},{"class":159,"line":1499},[3778],{"type":20,"tag":157,"props":3779,"children":3780},{"emptyLinePlaceholder":173},[3781],{"type":25,"value":176},{"type":20,"tag":157,"props":3783,"children":3784},{"class":159,"line":1507},[3785],{"type":20,"tag":157,"props":3786,"children":3787},{"emptyLinePlaceholder":173},[3788],{"type":25,"value":176},{"type":20,"tag":157,"props":3790,"children":3791},{"class":159,"line":1515},[3792],{"type":20,"tag":157,"props":3793,"children":3794},{},[3795],{"type":25,"value":3796},"print(calc_basic_descriptors(\"CC(=O)Oc1ccccc1C(=O)O\"))\n",{"type":20,"tag":21,"props":3798,"children":3799},{},[3800],{"type":25,"value":3801},"描述符最大的优点是可解释。",{"type":20,"tag":21,"props":3803,"children":3804},{},[3805],{"type":25,"value":3806},"当模型预测一个分子溶解性不好时，我们至少可以回头看：是不是 LogP 太高？是不是 TPSA 太大？是不是氢键供受体数量异常？这些特征和化学直觉之间有对应关系。",{"type":20,"tag":21,"props":3808,"children":3809},{},[3810],{"type":25,"value":3811},"它的缺点也很明显：描述符是人工设计的汇总特征，表达能力有限。两个分子的分子量、LogP、TPSA 可能很接近，但局部结构完全不同。",{"type":20,"tag":21,"props":3813,"children":3814},{},[3815],{"type":25,"value":3816},"这就需要分子指纹。",{"type":20,"tag":1863,"props":3818,"children":3820},{"id":3819},"lipinski-五规则一个经典但不能迷信的经验规则",[3821],{"type":25,"value":3822},"Lipinski 五规则：一个经典但不能迷信的经验规则",{"type":20,"tag":21,"props":3824,"children":3825},{},[3826],{"type":25,"value":3827},"在小分子药物里，经常会看到 Lipinski 五规则。它用于粗略判断一个分子是否可能具备较好的口服成药性。",{"type":20,"tag":21,"props":3829,"children":3830},{},[3831],{"type":25,"value":3832},"常见阈值是：",{"type":20,"tag":126,"props":3834,"children":3836},{"className":324,"code":3835,"language":25,"meta":8,"style":8},"分子量 \u003C= 500\nLogP \u003C= 5\n氢键供体 HBD \u003C= 5\n氢键受体 HBA \u003C= 10\n",[3837],{"type":20,"tag":84,"props":3838,"children":3839},{"__ignoreMap":8},[3840,3848,3856,3864],{"type":20,"tag":157,"props":3841,"children":3842},{"class":159,"line":160},[3843],{"type":20,"tag":157,"props":3844,"children":3845},{},[3846],{"type":25,"value":3847},"分子量 \u003C= 500\n",{"type":20,"tag":157,"props":3849,"children":3850},{"class":159,"line":169},[3851],{"type":20,"tag":157,"props":3852,"children":3853},{},[3854],{"type":25,"value":3855},"LogP \u003C= 5\n",{"type":20,"tag":157,"props":3857,"children":3858},{"class":159,"line":179},[3859],{"type":20,"tag":157,"props":3860,"children":3861},{},[3862],{"type":25,"value":3863},"氢键供体 HBD \u003C= 5\n",{"type":20,"tag":157,"props":3865,"children":3866},{"class":159,"line":188},[3867],{"type":20,"tag":157,"props":3868,"children":3869},{},[3870],{"type":25,"value":3871},"氢键受体 HBA \u003C= 10\n",{"type":20,"tag":21,"props":3873,"children":3874},{},[3875],{"type":25,"value":3876},"虽然叫“五规则”，但它不是一个模型，也不是药物能否成功的判决书。它更像早期过滤条件：如果一个分子严重违反这些规则，后续 ADMET 风险可能更高。",{"type":20,"tag":21,"props":3878,"children":3879},{},[3880],{"type":25,"value":3881},"在工程系统里，我更倾向于把它当成一个解释性指标，而不是硬性真理：",{"type":20,"tag":126,"props":3883,"children":3885},{"className":324,"code":3884,"language":25,"meta":8,"style":8},"这个分子违反了几条规则？\n违反的是分子量、LogP，还是氢键供受体？\n是否需要结合具体靶点、给药方式和实验数据重新判断？\n",[3886],{"type":20,"tag":84,"props":3887,"children":3888},{"__ignoreMap":8},[3889,3897,3905],{"type":20,"tag":157,"props":3890,"children":3891},{"class":159,"line":160},[3892],{"type":20,"tag":157,"props":3893,"children":3894},{},[3895],{"type":25,"value":3896},"这个分子违反了几条规则？\n",{"type":20,"tag":157,"props":3898,"children":3899},{"class":159,"line":169},[3900],{"type":20,"tag":157,"props":3901,"children":3902},{},[3903],{"type":25,"value":3904},"违反的是分子量、LogP，还是氢键供受体？\n",{"type":20,"tag":157,"props":3906,"children":3907},{"class":159,"line":179},[3908],{"type":20,"tag":157,"props":3909,"children":3910},{},[3911],{"type":25,"value":3912},"是否需要结合具体靶点、给药方式和实验数据重新判断？\n",{"type":20,"tag":21,"props":3914,"children":3915},{},[3916],{"type":25,"value":3917},"AI 制药里很容易把计算结果说得太满。但描述符和经验规则只能提供线索，不能替代实验验证。",{"type":20,"tag":1863,"props":3919,"children":3921},{"id":3920},"morgan-指纹把局部结构编码成高维向量",[3922],{"type":25,"value":3923},"Morgan 指纹：把局部结构编码成高维向量",{"type":20,"tag":21,"props":3925,"children":3926},{},[3927],{"type":25,"value":3928},"分子描述符像是在回答：“这个分子总体上是什么性质？”",{"type":20,"tag":21,"props":3930,"children":3931},{},[3932],{"type":25,"value":3933},"Morgan 指纹更像是在回答：“这个分子里出现过哪些局部结构？”",{"type":20,"tag":21,"props":3935,"children":3936},{},[3937],{"type":25,"value":3938},"RDKit 里的 Morgan 指纹属于 circular fingerprints。它会围绕每个原子，在一定半径内观察局部化学环境，然后把这些局部环境编码到固定长度的向量里。",{"type":20,"tag":21,"props":3940,"children":3941},{},[3942],{"type":25,"value":3943},"一个常见设置是：",{"type":20,"tag":126,"props":3945,"children":3947},{"className":324,"code":3946,"language":25,"meta":8,"style":8},"radius = 2\nfpSize = 2048\n",[3948],{"type":20,"tag":84,"props":3949,"children":3950},{"__ignoreMap":8},[3951,3959],{"type":20,"tag":157,"props":3952,"children":3953},{"class":159,"line":160},[3954],{"type":20,"tag":157,"props":3955,"children":3956},{},[3957],{"type":25,"value":3958},"radius = 2\n",{"type":20,"tag":157,"props":3960,"children":3961},{"class":159,"line":169},[3962],{"type":20,"tag":157,"props":3963,"children":3964},{},[3965],{"type":25,"value":3966},"fpSize = 2048\n",{"type":20,"tag":21,"props":3968,"children":3969},{},[3970],{"type":25,"value":3971},"也就是生成一个 2048 位的 bit vector。每一位可以粗略理解为某种结构模式是否出现过。",{"type":20,"tag":21,"props":3973,"children":3974},{},[3975],{"type":25,"value":3976},"新版 RDKit 文档中，更推荐用 fingerprint generator 的方式生成 Morgan 指纹：",{"type":20,"tag":126,"props":3978,"children":3980},{"className":151,"code":3979,"language":150,"meta":8,"style":8},"import numpy as np\nfrom rdkit import Chem, DataStructs\nfrom rdkit.Chem import AllChem\n\n\ndef morgan_fingerprint(smiles: str, radius: int = 2, fp_size: int = 2048):\n    mol = Chem.MolFromSmiles(smiles)\n    if mol is None:\n        raise ValueError(f\"无效的 SMILES: {smiles}\")\n\n    fpgen = AllChem.GetMorganGenerator(radius=radius, fpSize=fp_size)\n    fingerprint = fpgen.GetFingerprint(mol)\n\n    array = np.zeros((fp_size,), dtype=np.int8)\n    DataStructs.ConvertToNumpyArray(fingerprint, array)\n    return array\n\n\nfp = morgan_fingerprint(\"CC(=O)Oc1ccccc1C(=O)O\")\nprint(fp.shape)\n",[3981],{"type":20,"tag":84,"props":3982,"children":3983},{"__ignoreMap":8},[3984,3992,4000,4008,4015,4022,4030,4037,4044,4051,4058,4066,4074,4081,4089,4097,4105,4112,4119,4127],{"type":20,"tag":157,"props":3985,"children":3986},{"class":159,"line":160},[3987],{"type":20,"tag":157,"props":3988,"children":3989},{},[3990],{"type":25,"value":3991},"import numpy as np\n",{"type":20,"tag":157,"props":3993,"children":3994},{"class":159,"line":169},[3995],{"type":20,"tag":157,"props":3996,"children":3997},{},[3998],{"type":25,"value":3999},"from rdkit import Chem, DataStructs\n",{"type":20,"tag":157,"props":4001,"children":4002},{"class":159,"line":179},[4003],{"type":20,"tag":157,"props":4004,"children":4005},{},[4006],{"type":25,"value":4007},"from rdkit.Chem import AllChem\n",{"type":20,"tag":157,"props":4009,"children":4010},{"class":159,"line":188},[4011],{"type":20,"tag":157,"props":4012,"children":4013},{"emptyLinePlaceholder":173},[4014],{"type":25,"value":176},{"type":20,"tag":157,"props":4016,"children":4017},{"class":159,"line":196},[4018],{"type":20,"tag":157,"props":4019,"children":4020},{"emptyLinePlaceholder":173},[4021],{"type":25,"value":176},{"type":20,"tag":157,"props":4023,"children":4024},{"class":159,"line":204},[4025],{"type":20,"tag":157,"props":4026,"children":4027},{},[4028],{"type":25,"value":4029},"def morgan_fingerprint(smiles: str, radius: int = 2, fp_size: int = 2048):\n",{"type":20,"tag":157,"props":4031,"children":4032},{"class":159,"line":213},[4033],{"type":20,"tag":157,"props":4034,"children":4035},{},[4036],{"type":25,"value":2602},{"type":20,"tag":157,"props":4038,"children":4039},{"class":159,"line":222},[4040],{"type":20,"tag":157,"props":4041,"children":4042},{},[4043],{"type":25,"value":2610},{"type":20,"tag":157,"props":4045,"children":4046},{"class":159,"line":440},[4047],{"type":20,"tag":157,"props":4048,"children":4049},{},[4050],{"type":25,"value":2618},{"type":20,"tag":157,"props":4052,"children":4053},{"class":159,"line":448},[4054],{"type":20,"tag":157,"props":4055,"children":4056},{"emptyLinePlaceholder":173},[4057],{"type":25,"value":176},{"type":20,"tag":157,"props":4059,"children":4060},{"class":159,"line":456},[4061],{"type":20,"tag":157,"props":4062,"children":4063},{},[4064],{"type":25,"value":4065},"    fpgen = AllChem.GetMorganGenerator(radius=radius, fpSize=fp_size)\n",{"type":20,"tag":157,"props":4067,"children":4068},{"class":159,"line":465},[4069],{"type":20,"tag":157,"props":4070,"children":4071},{},[4072],{"type":25,"value":4073},"    fingerprint = fpgen.GetFingerprint(mol)\n",{"type":20,"tag":157,"props":4075,"children":4076},{"class":159,"line":474},[4077],{"type":20,"tag":157,"props":4078,"children":4079},{"emptyLinePlaceholder":173},[4080],{"type":25,"value":176},{"type":20,"tag":157,"props":4082,"children":4083},{"class":159,"line":483},[4084],{"type":20,"tag":157,"props":4085,"children":4086},{},[4087],{"type":25,"value":4088},"    array = np.zeros((fp_size,), dtype=np.int8)\n",{"type":20,"tag":157,"props":4090,"children":4091},{"class":159,"line":491},[4092],{"type":20,"tag":157,"props":4093,"children":4094},{},[4095],{"type":25,"value":4096},"    DataStructs.ConvertToNumpyArray(fingerprint, array)\n",{"type":20,"tag":157,"props":4098,"children":4099},{"class":159,"line":499},[4100],{"type":20,"tag":157,"props":4101,"children":4102},{},[4103],{"type":25,"value":4104},"    return array\n",{"type":20,"tag":157,"props":4106,"children":4107},{"class":159,"line":508},[4108],{"type":20,"tag":157,"props":4109,"children":4110},{"emptyLinePlaceholder":173},[4111],{"type":25,"value":176},{"type":20,"tag":157,"props":4113,"children":4114},{"class":159,"line":517},[4115],{"type":20,"tag":157,"props":4116,"children":4117},{"emptyLinePlaceholder":173},[4118],{"type":25,"value":176},{"type":20,"tag":157,"props":4120,"children":4121},{"class":159,"line":1499},[4122],{"type":20,"tag":157,"props":4123,"children":4124},{},[4125],{"type":25,"value":4126},"fp = morgan_fingerprint(\"CC(=O)Oc1ccccc1C(=O)O\")\n",{"type":20,"tag":157,"props":4128,"children":4129},{"class":159,"line":1507},[4130],{"type":20,"tag":157,"props":4131,"children":4132},{},[4133],{"type":25,"value":4134},"print(fp.shape)\n",{"type":20,"tag":21,"props":4136,"children":4137},{},[4138,4140,4146],{"type":25,"value":4139},"输出的 ",{"type":20,"tag":84,"props":4141,"children":4143},{"className":4142},[],[4144],{"type":25,"value":4145},"fp",{"type":25,"value":4147}," 就可以作为机器学习模型的输入特征。",{"type":20,"tag":21,"props":4149,"children":4150},{},[4151],{"type":25,"value":4152},"和描述符相比，Morgan 指纹的特点是：",{"type":20,"tag":1902,"props":4154,"children":4155},{},[4156,4177],{"type":20,"tag":1906,"props":4157,"children":4158},{},[4159],{"type":20,"tag":1910,"props":4160,"children":4161},{},[4162,4167,4172],{"type":20,"tag":1914,"props":4163,"children":4164},{},[4165],{"type":25,"value":4166},"特征",{"type":20,"tag":1914,"props":4168,"children":4169},{},[4170],{"type":25,"value":4171},"分子描述符",{"type":20,"tag":1914,"props":4173,"children":4174},{},[4175],{"type":25,"value":4176},"Morgan 指纹",{"type":20,"tag":1930,"props":4178,"children":4179},{},[4180,4198,4216,4234],{"type":20,"tag":1910,"props":4181,"children":4182},{},[4183,4188,4193],{"type":20,"tag":1937,"props":4184,"children":4185},{},[4186],{"type":25,"value":4187},"维度",{"type":20,"tag":1937,"props":4189,"children":4190},{},[4191],{"type":25,"value":4192},"通常较少",{"type":20,"tag":1937,"props":4194,"children":4195},{},[4196],{"type":25,"value":4197},"通常较高，比如 2048 位",{"type":20,"tag":1910,"props":4199,"children":4200},{},[4201,4206,4211],{"type":20,"tag":1937,"props":4202,"children":4203},{},[4204],{"type":25,"value":4205},"可解释性",{"type":20,"tag":1937,"props":4207,"children":4208},{},[4209],{"type":25,"value":4210},"强",{"type":20,"tag":1937,"props":4212,"children":4213},{},[4214],{"type":25,"value":4215},"弱一些",{"type":20,"tag":1910,"props":4217,"children":4218},{},[4219,4224,4229],{"type":20,"tag":1937,"props":4220,"children":4221},{},[4222],{"type":25,"value":4223},"表达重点",{"type":20,"tag":1937,"props":4225,"children":4226},{},[4227],{"type":25,"value":4228},"整体物化性质",{"type":20,"tag":1937,"props":4230,"children":4231},{},[4232],{"type":25,"value":4233},"局部结构模式",{"type":20,"tag":1910,"props":4235,"children":4236},{},[4237,4242,4247],{"type":20,"tag":1937,"props":4238,"children":4239},{},[4240],{"type":25,"value":4241},"适合场景",{"type":20,"tag":1937,"props":4243,"children":4244},{},[4245],{"type":25,"value":4246},"表格模型、baseline、解释分析",{"type":20,"tag":1937,"props":4248,"children":4249},{},[4250],{"type":25,"value":4251},"相似性搜索、QSAR、机器学习输入",{"type":20,"tag":21,"props":4253,"children":4254},{},[4255],{"type":25,"value":4256},"真实项目里，两者不一定二选一。很多 baseline 会把描述符和指纹都算出来，再比较不同特征组合的效果。",{"type":20,"tag":1863,"props":4258,"children":4260},{"id":4259},"tanimoto-相似度用指纹做分子相似性搜索",[4261],{"type":25,"value":4262},"Tanimoto 相似度：用指纹做分子相似性搜索",{"type":20,"tag":21,"props":4264,"children":4265},{},[4266],{"type":25,"value":4267},"有了分子指纹之后，就可以比较两个分子有多像。",{"type":20,"tag":21,"props":4269,"children":4270},{},[4271],{"type":25,"value":4272},"化学信息学里常用的指标之一是 Tanimoto similarity。对于 bit vector 来说，它可以粗略理解成：",{"type":20,"tag":126,"props":4274,"children":4276},{"className":324,"code":4275,"language":25,"meta":8,"style":8},"两个分子共同打开的 bit 数量 \u002F 两个分子总共打开的 bit 数量\n",[4277],{"type":20,"tag":84,"props":4278,"children":4279},{"__ignoreMap":8},[4280],{"type":20,"tag":157,"props":4281,"children":4282},{"class":159,"line":160},[4283],{"type":20,"tag":157,"props":4284,"children":4285},{},[4286],{"type":25,"value":4275},{"type":20,"tag":21,"props":4288,"children":4289},{},[4290],{"type":25,"value":4291},"值越接近 1，说明两个指纹越相似；越接近 0，说明差异越大。",{"type":20,"tag":21,"props":4293,"children":4294},{},[4295],{"type":25,"value":4296},"用 RDKit 计算两个分子的 Tanimoto 相似度：",{"type":20,"tag":126,"props":4298,"children":4300},{"className":151,"code":4299,"language":150,"meta":8,"style":8},"from rdkit import Chem, DataStructs\nfrom rdkit.Chem import AllChem\n\n\nfpgen = AllChem.GetMorganGenerator(radius=2, fpSize=2048)\n\n\ndef get_fp(smiles: str):\n    mol = Chem.MolFromSmiles(smiles)\n    if mol is None:\n        raise ValueError(f\"无效的 SMILES: {smiles}\")\n    return fpgen.GetFingerprint(mol)\n\n\naspirin = get_fp(\"CC(=O)Oc1ccccc1C(=O)O\")\nbenzoic_acid = get_fp(\"O=C(O)c1ccccc1\")\n\nsimilarity = DataStructs.TanimotoSimilarity(aspirin, benzoic_acid)\nprint(similarity)\n",[4301],{"type":20,"tag":84,"props":4302,"children":4303},{"__ignoreMap":8},[4304,4311,4318,4325,4332,4340,4347,4354,4362,4369,4376,4383,4391,4398,4405,4413,4421,4428,4436],{"type":20,"tag":157,"props":4305,"children":4306},{"class":159,"line":160},[4307],{"type":20,"tag":157,"props":4308,"children":4309},{},[4310],{"type":25,"value":3999},{"type":20,"tag":157,"props":4312,"children":4313},{"class":159,"line":169},[4314],{"type":20,"tag":157,"props":4315,"children":4316},{},[4317],{"type":25,"value":4007},{"type":20,"tag":157,"props":4319,"children":4320},{"class":159,"line":179},[4321],{"type":20,"tag":157,"props":4322,"children":4323},{"emptyLinePlaceholder":173},[4324],{"type":25,"value":176},{"type":20,"tag":157,"props":4326,"children":4327},{"class":159,"line":188},[4328],{"type":20,"tag":157,"props":4329,"children":4330},{"emptyLinePlaceholder":173},[4331],{"type":25,"value":176},{"type":20,"tag":157,"props":4333,"children":4334},{"class":159,"line":196},[4335],{"type":20,"tag":157,"props":4336,"children":4337},{},[4338],{"type":25,"value":4339},"fpgen = AllChem.GetMorganGenerator(radius=2, fpSize=2048)\n",{"type":20,"tag":157,"props":4341,"children":4342},{"class":159,"line":204},[4343],{"type":20,"tag":157,"props":4344,"children":4345},{"emptyLinePlaceholder":173},[4346],{"type":25,"value":176},{"type":20,"tag":157,"props":4348,"children":4349},{"class":159,"line":213},[4350],{"type":20,"tag":157,"props":4351,"children":4352},{"emptyLinePlaceholder":173},[4353],{"type":25,"value":176},{"type":20,"tag":157,"props":4355,"children":4356},{"class":159,"line":222},[4357],{"type":20,"tag":157,"props":4358,"children":4359},{},[4360],{"type":25,"value":4361},"def get_fp(smiles: str):\n",{"type":20,"tag":157,"props":4363,"children":4364},{"class":159,"line":440},[4365],{"type":20,"tag":157,"props":4366,"children":4367},{},[4368],{"type":25,"value":2602},{"type":20,"tag":157,"props":4370,"children":4371},{"class":159,"line":448},[4372],{"type":20,"tag":157,"props":4373,"children":4374},{},[4375],{"type":25,"value":2610},{"type":20,"tag":157,"props":4377,"children":4378},{"class":159,"line":456},[4379],{"type":20,"tag":157,"props":4380,"children":4381},{},[4382],{"type":25,"value":2618},{"type":20,"tag":157,"props":4384,"children":4385},{"class":159,"line":465},[4386],{"type":20,"tag":157,"props":4387,"children":4388},{},[4389],{"type":25,"value":4390},"    return fpgen.GetFingerprint(mol)\n",{"type":20,"tag":157,"props":4392,"children":4393},{"class":159,"line":474},[4394],{"type":20,"tag":157,"props":4395,"children":4396},{"emptyLinePlaceholder":173},[4397],{"type":25,"value":176},{"type":20,"tag":157,"props":4399,"children":4400},{"class":159,"line":483},[4401],{"type":20,"tag":157,"props":4402,"children":4403},{"emptyLinePlaceholder":173},[4404],{"type":25,"value":176},{"type":20,"tag":157,"props":4406,"children":4407},{"class":159,"line":491},[4408],{"type":20,"tag":157,"props":4409,"children":4410},{},[4411],{"type":25,"value":4412},"aspirin = get_fp(\"CC(=O)Oc1ccccc1C(=O)O\")\n",{"type":20,"tag":157,"props":4414,"children":4415},{"class":159,"line":499},[4416],{"type":20,"tag":157,"props":4417,"children":4418},{},[4419],{"type":25,"value":4420},"benzoic_acid = get_fp(\"O=C(O)c1ccccc1\")\n",{"type":20,"tag":157,"props":4422,"children":4423},{"class":159,"line":508},[4424],{"type":20,"tag":157,"props":4425,"children":4426},{"emptyLinePlaceholder":173},[4427],{"type":25,"value":176},{"type":20,"tag":157,"props":4429,"children":4430},{"class":159,"line":517},[4431],{"type":20,"tag":157,"props":4432,"children":4433},{},[4434],{"type":25,"value":4435},"similarity = DataStructs.TanimotoSimilarity(aspirin, benzoic_acid)\n",{"type":20,"tag":157,"props":4437,"children":4438},{"class":159,"line":1499},[4439],{"type":20,"tag":157,"props":4440,"children":4441},{},[4442],{"type":25,"value":4443},"print(similarity)\n",{"type":20,"tag":21,"props":4445,"children":4446},{},[4447],{"type":25,"value":4448},"这就是分子相似性搜索的基础。",{"type":20,"tag":21,"props":4450,"children":4451},{},[4452],{"type":25,"value":4453},"一个简单的检索系统可以这样设计：",{"type":20,"tag":126,"props":4455,"children":4457},{"className":324,"code":4456,"language":25,"meta":8,"style":8},"1. 用户输入一个 SMILES\n2. RDKit 解析成 Mol\n3. 生成 Morgan 指纹\n4. 和数据库里已有分子的指纹逐个计算 Tanimoto similarity\n5. 返回相似度最高的 Top K 分子\n",[4458],{"type":20,"tag":84,"props":4459,"children":4460},{"__ignoreMap":8},[4461,4469,4477,4485,4493],{"type":20,"tag":157,"props":4462,"children":4463},{"class":159,"line":160},[4464],{"type":20,"tag":157,"props":4465,"children":4466},{},[4467],{"type":25,"value":4468},"1. 用户输入一个 SMILES\n",{"type":20,"tag":157,"props":4470,"children":4471},{"class":159,"line":169},[4472],{"type":20,"tag":157,"props":4473,"children":4474},{},[4475],{"type":25,"value":4476},"2. RDKit 解析成 Mol\n",{"type":20,"tag":157,"props":4478,"children":4479},{"class":159,"line":179},[4480],{"type":20,"tag":157,"props":4481,"children":4482},{},[4483],{"type":25,"value":4484},"3. 生成 Morgan 指纹\n",{"type":20,"tag":157,"props":4486,"children":4487},{"class":159,"line":188},[4488],{"type":20,"tag":157,"props":4489,"children":4490},{},[4491],{"type":25,"value":4492},"4. 和数据库里已有分子的指纹逐个计算 Tanimoto similarity\n",{"type":20,"tag":157,"props":4494,"children":4495},{"class":159,"line":196},[4496],{"type":20,"tag":157,"props":4497,"children":4498},{},[4499],{"type":25,"value":4500},"5. 返回相似度最高的 Top K 分子\n",{"type":20,"tag":21,"props":4502,"children":4503},{},[4504],{"type":25,"value":4505},"这类能力在药物发现里很常见。比如你已经知道一个 hit 分子，希望找到结构相似的候选分子；或者你想在分子库里快速找出一批和目标结构接近的化合物。",{"type":20,"tag":21,"props":4507,"children":4508},{},[4509,4511,4516],{"type":25,"value":4510},"但还是要加一句限制：",{"type":20,"tag":33,"props":4512,"children":4513},{},[4514],{"type":25,"value":4515},"结构相似不等于药效相同",{"type":25,"value":4517},"。相似性搜索只能提供候选方向，不能证明活性、毒性、选择性或成药性。",{"type":20,"tag":1863,"props":4519,"children":4521},{"id":4520},"一条最小可用的分子特征流水线",[4522],{"type":25,"value":4520},{"type":20,"tag":21,"props":4524,"children":4525},{},[4526],{"type":25,"value":4527},"把前面的内容合起来，可以写出一条最小可用的分子特征流水线：",{"type":20,"tag":126,"props":4529,"children":4531},{"className":151,"code":4530,"language":150,"meta":8,"style":8},"import numpy as np\nfrom rdkit import Chem, DataStructs\nfrom rdkit.Chem import AllChem, Descriptors, Lipinski, rdMolDescriptors\n\n\nfpgen = AllChem.GetMorganGenerator(radius=2, fpSize=2048)\n\n\ndef featurize_smiles(smiles: str) -> dict[str, object]:\n    mol = Chem.MolFromSmiles(smiles)\n    if mol is None:\n        raise ValueError(f\"无效的 SMILES: {smiles}\")\n\n    canonical_smiles = Chem.MolToSmiles(mol, canonical=True)\n\n    descriptors = {\n        \"mol_wt\": round(Descriptors.MolWt(mol), 2),\n        \"logp\": round(Descriptors.MolLogP(mol), 2),\n        \"tpsa\": round(Descriptors.TPSA(mol), 2),\n        \"hbd\": Lipinski.NumHDonors(mol),\n        \"hba\": Lipinski.NumHAcceptors(mol),\n        \"rotatable_bonds\": Lipinski.NumRotatableBonds(mol),\n        \"ring_count\": rdMolDescriptors.CalcNumRings(mol),\n    }\n\n    fingerprint = fpgen.GetFingerprint(mol)\n    fp_array = np.zeros((2048,), dtype=np.int8)\n    DataStructs.ConvertToNumpyArray(fingerprint, fp_array)\n\n    return {\n        \"canonical_smiles\": canonical_smiles,\n        \"descriptors\": descriptors,\n        \"morgan_fingerprint\": fp_array,\n    }\n",[4532],{"type":20,"tag":84,"props":4533,"children":4534},{"__ignoreMap":8},[4535,4542,4549,4557,4564,4571,4578,4585,4592,4600,4607,4614,4621,4628,4636,4643,4651,4658,4665,4672,4679,4686,4693,4700,4707,4714,4721,4729,4737,4744,4751,4759,4767,4775],{"type":20,"tag":157,"props":4536,"children":4537},{"class":159,"line":160},[4538],{"type":20,"tag":157,"props":4539,"children":4540},{},[4541],{"type":25,"value":3991},{"type":20,"tag":157,"props":4543,"children":4544},{"class":159,"line":169},[4545],{"type":20,"tag":157,"props":4546,"children":4547},{},[4548],{"type":25,"value":3999},{"type":20,"tag":157,"props":4550,"children":4551},{"class":159,"line":179},[4552],{"type":20,"tag":157,"props":4553,"children":4554},{},[4555],{"type":25,"value":4556},"from rdkit.Chem import AllChem, Descriptors, Lipinski, rdMolDescriptors\n",{"type":20,"tag":157,"props":4558,"children":4559},{"class":159,"line":188},[4560],{"type":20,"tag":157,"props":4561,"children":4562},{"emptyLinePlaceholder":173},[4563],{"type":25,"value":176},{"type":20,"tag":157,"props":4565,"children":4566},{"class":159,"line":196},[4567],{"type":20,"tag":157,"props":4568,"children":4569},{"emptyLinePlaceholder":173},[4570],{"type":25,"value":176},{"type":20,"tag":157,"props":4572,"children":4573},{"class":159,"line":204},[4574],{"type":20,"tag":157,"props":4575,"children":4576},{},[4577],{"type":25,"value":4339},{"type":20,"tag":157,"props":4579,"children":4580},{"class":159,"line":213},[4581],{"type":20,"tag":157,"props":4582,"children":4583},{"emptyLinePlaceholder":173},[4584],{"type":25,"value":176},{"type":20,"tag":157,"props":4586,"children":4587},{"class":159,"line":222},[4588],{"type":20,"tag":157,"props":4589,"children":4590},{"emptyLinePlaceholder":173},[4591],{"type":25,"value":176},{"type":20,"tag":157,"props":4593,"children":4594},{"class":159,"line":440},[4595],{"type":20,"tag":157,"props":4596,"children":4597},{},[4598],{"type":25,"value":4599},"def featurize_smiles(smiles: str) -> dict[str, object]:\n",{"type":20,"tag":157,"props":4601,"children":4602},{"class":159,"line":448},[4603],{"type":20,"tag":157,"props":4604,"children":4605},{},[4606],{"type":25,"value":2602},{"type":20,"tag":157,"props":4608,"children":4609},{"class":159,"line":456},[4610],{"type":20,"tag":157,"props":4611,"children":4612},{},[4613],{"type":25,"value":2610},{"type":20,"tag":157,"props":4615,"children":4616},{"class":159,"line":465},[4617],{"type":20,"tag":157,"props":4618,"children":4619},{},[4620],{"type":25,"value":2618},{"type":20,"tag":157,"props":4622,"children":4623},{"class":159,"line":474},[4624],{"type":20,"tag":157,"props":4625,"children":4626},{"emptyLinePlaceholder":173},[4627],{"type":25,"value":176},{"type":20,"tag":157,"props":4629,"children":4630},{"class":159,"line":483},[4631],{"type":20,"tag":157,"props":4632,"children":4633},{},[4634],{"type":25,"value":4635},"    canonical_smiles = Chem.MolToSmiles(mol, canonical=True)\n",{"type":20,"tag":157,"props":4637,"children":4638},{"class":159,"line":491},[4639],{"type":20,"tag":157,"props":4640,"children":4641},{"emptyLinePlaceholder":173},[4642],{"type":25,"value":176},{"type":20,"tag":157,"props":4644,"children":4645},{"class":159,"line":499},[4646],{"type":20,"tag":157,"props":4647,"children":4648},{},[4649],{"type":25,"value":4650},"    descriptors = {\n",{"type":20,"tag":157,"props":4652,"children":4653},{"class":159,"line":508},[4654],{"type":20,"tag":157,"props":4655,"children":4656},{},[4657],{"type":25,"value":3719},{"type":20,"tag":157,"props":4659,"children":4660},{"class":159,"line":517},[4661],{"type":20,"tag":157,"props":4662,"children":4663},{},[4664],{"type":25,"value":3727},{"type":20,"tag":157,"props":4666,"children":4667},{"class":159,"line":1499},[4668],{"type":20,"tag":157,"props":4669,"children":4670},{},[4671],{"type":25,"value":3735},{"type":20,"tag":157,"props":4673,"children":4674},{"class":159,"line":1507},[4675],{"type":20,"tag":157,"props":4676,"children":4677},{},[4678],{"type":25,"value":3743},{"type":20,"tag":157,"props":4680,"children":4681},{"class":159,"line":1515},[4682],{"type":20,"tag":157,"props":4683,"children":4684},{},[4685],{"type":25,"value":3751},{"type":20,"tag":157,"props":4687,"children":4688},{"class":159,"line":1523},[4689],{"type":20,"tag":157,"props":4690,"children":4691},{},[4692],{"type":25,"value":3759},{"type":20,"tag":157,"props":4694,"children":4695},{"class":159,"line":1532},[4696],{"type":20,"tag":157,"props":4697,"children":4698},{},[4699],{"type":25,"value":3767},{"type":20,"tag":157,"props":4701,"children":4702},{"class":159,"line":1541},[4703],{"type":20,"tag":157,"props":4704,"children":4705},{},[4706],{"type":25,"value":872},{"type":20,"tag":157,"props":4708,"children":4709},{"class":159,"line":1550},[4710],{"type":20,"tag":157,"props":4711,"children":4712},{"emptyLinePlaceholder":173},[4713],{"type":25,"value":176},{"type":20,"tag":157,"props":4715,"children":4716},{"class":159,"line":1558},[4717],{"type":20,"tag":157,"props":4718,"children":4719},{},[4720],{"type":25,"value":4073},{"type":20,"tag":157,"props":4722,"children":4723},{"class":159,"line":1566},[4724],{"type":20,"tag":157,"props":4725,"children":4726},{},[4727],{"type":25,"value":4728},"    fp_array = np.zeros((2048,), dtype=np.int8)\n",{"type":20,"tag":157,"props":4730,"children":4731},{"class":159,"line":1574},[4732],{"type":20,"tag":157,"props":4733,"children":4734},{},[4735],{"type":25,"value":4736},"    DataStructs.ConvertToNumpyArray(fingerprint, fp_array)\n",{"type":20,"tag":157,"props":4738,"children":4739},{"class":159,"line":1582},[4740],{"type":20,"tag":157,"props":4741,"children":4742},{"emptyLinePlaceholder":173},[4743],{"type":25,"value":176},{"type":20,"tag":157,"props":4745,"children":4746},{"class":159,"line":1591},[4747],{"type":20,"tag":157,"props":4748,"children":4749},{},[4750],{"type":25,"value":848},{"type":20,"tag":157,"props":4752,"children":4753},{"class":159,"line":1599},[4754],{"type":20,"tag":157,"props":4755,"children":4756},{},[4757],{"type":25,"value":4758},"        \"canonical_smiles\": canonical_smiles,\n",{"type":20,"tag":157,"props":4760,"children":4761},{"class":159,"line":1608},[4762],{"type":20,"tag":157,"props":4763,"children":4764},{},[4765],{"type":25,"value":4766},"        \"descriptors\": descriptors,\n",{"type":20,"tag":157,"props":4768,"children":4769},{"class":159,"line":1617},[4770],{"type":20,"tag":157,"props":4771,"children":4772},{},[4773],{"type":25,"value":4774},"        \"morgan_fingerprint\": fp_array,\n",{"type":20,"tag":157,"props":4776,"children":4777},{"class":159,"line":1626},[4778],{"type":20,"tag":157,"props":4779,"children":4780},{},[4781],{"type":25,"value":872},{"type":20,"tag":21,"props":4783,"children":4784},{},[4785],{"type":25,"value":4786},"这段代码已经覆盖了一个小型 AI 制药平台的基础能力：",{"type":20,"tag":126,"props":4788,"children":4790},{"className":324,"code":4789,"language":25,"meta":8,"style":8},"输入校验\n标准化\n特征提取\n向量生成\n",[4791],{"type":20,"tag":84,"props":4792,"children":4793},{"__ignoreMap":8},[4794,4802,4810,4818],{"type":20,"tag":157,"props":4795,"children":4796},{"class":159,"line":160},[4797],{"type":20,"tag":157,"props":4798,"children":4799},{},[4800],{"type":25,"value":4801},"输入校验\n",{"type":20,"tag":157,"props":4803,"children":4804},{"class":159,"line":169},[4805],{"type":20,"tag":157,"props":4806,"children":4807},{},[4808],{"type":25,"value":4809},"标准化\n",{"type":20,"tag":157,"props":4811,"children":4812},{"class":159,"line":179},[4813],{"type":20,"tag":157,"props":4814,"children":4815},{},[4816],{"type":25,"value":4817},"特征提取\n",{"type":20,"tag":157,"props":4819,"children":4820},{"class":159,"line":188},[4821],{"type":20,"tag":157,"props":4822,"children":4823},{},[4824],{"type":25,"value":4825},"向量生成\n",{"type":20,"tag":21,"props":4827,"children":4828},{},[4829],{"type":25,"value":4830},"后面无论是做分子相似性搜索、性质预测、分类模型，还是接入更复杂的深度学习模型，这条链路都可以继续扩展。",{"type":20,"tag":1863,"props":4832,"children":4834},{"id":4833},"我对这两周内容的理解",[4835],{"type":25,"value":4833},{"type":20,"tag":21,"props":4837,"children":4838},{},[4839],{"type":25,"value":4840},"学 RDKit 和 SMILES 时，很容易陷入 API 细节：这个函数怎么调，那个参数怎么写。",{"type":20,"tag":21,"props":4842,"children":4843},{},[4844],{"type":25,"value":4845},"但真正重要的是建立一张工程地图：",{"type":20,"tag":126,"props":4847,"children":4849},{"className":324,"code":4848,"language":25,"meta":8,"style":8},"SMILES 是输入格式\nMol 是 RDKit 的内部分子对象\nCanonical SMILES 用来统一表示\n分子图片用于展示\n分子描述符提供可解释特征\nMorgan 指纹提供高维结构特征\nTanimoto 相似度用于分子相似性搜索\n",[4850],{"type":20,"tag":84,"props":4851,"children":4852},{"__ignoreMap":8},[4853,4861,4869,4877,4885,4893,4901],{"type":20,"tag":157,"props":4854,"children":4855},{"class":159,"line":160},[4856],{"type":20,"tag":157,"props":4857,"children":4858},{},[4859],{"type":25,"value":4860},"SMILES 是输入格式\n",{"type":20,"tag":157,"props":4862,"children":4863},{"class":159,"line":169},[4864],{"type":20,"tag":157,"props":4865,"children":4866},{},[4867],{"type":25,"value":4868},"Mol 是 RDKit 的内部分子对象\n",{"type":20,"tag":157,"props":4870,"children":4871},{"class":159,"line":179},[4872],{"type":20,"tag":157,"props":4873,"children":4874},{},[4875],{"type":25,"value":4876},"Canonical SMILES 用来统一表示\n",{"type":20,"tag":157,"props":4878,"children":4879},{"class":159,"line":188},[4880],{"type":20,"tag":157,"props":4881,"children":4882},{},[4883],{"type":25,"value":4884},"分子图片用于展示\n",{"type":20,"tag":157,"props":4886,"children":4887},{"class":159,"line":196},[4888],{"type":20,"tag":157,"props":4889,"children":4890},{},[4891],{"type":25,"value":4892},"分子描述符提供可解释特征\n",{"type":20,"tag":157,"props":4894,"children":4895},{"class":159,"line":204},[4896],{"type":20,"tag":157,"props":4897,"children":4898},{},[4899],{"type":25,"value":4900},"Morgan 指纹提供高维结构特征\n",{"type":20,"tag":157,"props":4902,"children":4903},{"class":159,"line":213},[4904],{"type":20,"tag":157,"props":4905,"children":4906},{},[4907],{"type":25,"value":4908},"Tanimoto 相似度用于分子相似性搜索\n",{"type":20,"tag":21,"props":4910,"children":4911},{},[4912],{"type":25,"value":4913},"这张地图一旦建立起来，后面的机器学习部分会顺很多。",{"type":20,"tag":21,"props":4915,"children":4916},{},[4917],{"type":25,"value":4918},"因为你会知道：模型不是凭空预测的。它吃进去的每一个数字，都来自前面某一步对分子结构的编码。",{"type":20,"tag":21,"props":4920,"children":4921},{},[4922],{"type":25,"value":4923},"对 AI 制药项目来说，这也是一个很好的提醒：不要一上来就追复杂模型。先把分子表示、数据清洗、特征提取和错误处理做好，系统才有继续长大的基础。",{"type":20,"tag":1863,"props":4925,"children":4927},{"id":4926},"参考资料",[4928],{"type":25,"value":4926},{"type":20,"tag":44,"props":4930,"children":4931},{},[4932,4942,4952],{"type":20,"tag":48,"props":4933,"children":4934},{},[4935],{"type":20,"tag":101,"props":4936,"children":4939},{"href":4937,"rel":4938},"https:\u002F\u002Fwww.rdkit.org\u002Fdocs\u002Findex.html",[105],[4940],{"type":25,"value":4941},"RDKit 2026.03.2 Documentation",{"type":20,"tag":48,"props":4943,"children":4944},{},[4945],{"type":20,"tag":101,"props":4946,"children":4949},{"href":4947,"rel":4948},"https:\u002F\u002Fwww.rdkit.org\u002Fdocs\u002FGettingStartedInPython.html",[105],[4950],{"type":25,"value":4951},"Getting Started with the RDKit in Python",{"type":20,"tag":48,"props":4953,"children":4954},{},[4955],{"type":20,"tag":101,"props":4956,"children":4959},{"href":4957,"rel":4958},"https:\u002F\u002Fwww.rdkit.org\u002Fdocs\u002Fsource\u002Frdkit.Chem.Descriptors.html",[105],[4960],{"type":25,"value":4961},"RDKit Descriptors 文档",{"type":20,"tag":1785,"props":4963,"children":4964},{},[4965],{"type":25,"value":1789},{"title":8,"searchDepth":169,"depth":169,"links":4967},[4968,4969,4970,4971,4972,4973,4974,4975,4976,4977,4978,4979,4980],{"id":1865,"depth":169,"text":1868},{"id":2144,"depth":169,"text":2147},{"id":2364,"depth":169,"text":2367},{"id":2437,"depth":169,"text":2440},{"id":2792,"depth":169,"text":2795},{"id":2950,"depth":169,"text":2953},{"id":3327,"depth":169,"text":3330},{"id":3819,"depth":169,"text":3822},{"id":3920,"depth":169,"text":3923},{"id":4259,"depth":169,"text":4262},{"id":4520,"depth":169,"text":4520},{"id":4833,"depth":169,"text":4833},{"id":4926,"depth":169,"text":4926},"content:articles:AI制药理论:rdkit-smiles-descriptors-fingerprints.md","articles\u002FAI制药理论\u002Frdkit-smiles-descriptors-fingerprints.md","articles\u002FAI制药理论\u002Frdkit-smiles-descriptors-fingerprints",{"_path":4985,"_dir":1808,"_draft":7,"_partial":7,"_locale":8,"title":4986,"description":4987,"date":4988,"tags":4989,"body":4991,"_type":1800,"_id":6771,"_source":1802,"_file":6772,"_stem":6773,"_extension":1805},"\u002Farticles\u002Fai\u002Feverything-claude-codecodex","everything-claude-code 在 Codex 的应用：不要照搬全家桶，而是做一套更聪明的增强层","从 Codex 原生能力出发，拆解 everything-claude-code 中哪些规则、skills 和工作流值得迁移，哪些配置不应该原样照搬。","2026-05-18",[4990,14],"AI工具",{"type":17,"children":4992,"toc":6758},[4993,4998,5003,5016,5021,5032,5045,5055,5059,5065,5070,5224,5236,5246,5251,5254,5259,5264,5307,5312,5317,5329,5341,5353,5358,5361,5366,5371,5506,5511,5556,5561,5579,5591,5594,5599,5604,5643,5648,5653,5713,5718,5737,5749,5752,5758,5763,5768,5862,5867,6127,6132,6137,6151,6156,6178,6183,6197,6202,6216,6221,6252,6257,6260,6265,6270,6301,6306,6318,6443,6461,6466,6469,6475,6480,6490,6507,6517,6527,6530,6536,6541,6564,6569,6616,6621,6631,6634,6639,6644,6649,6654,6659,6664,6673,6676,6681,6686,6695,6698,6702,6754],{"type":20,"tag":21,"props":4994,"children":4995},{},[4996],{"type":25,"value":4997},"一个典型场景是这样。",{"type":20,"tag":21,"props":4999,"children":5000},{},[5001],{"type":25,"value":5002},"你让 Codex 修复 CI 里的 typecheck 报错。没有额外规则时，它可能会找到报错文件，改掉类型定义，然后停在“看起来已经修了”的状态。CI 再跑一次，还是红的，因为它没有在本地复现，也没有重新跑 typecheck 验证。",{"type":20,"tag":21,"props":5004,"children":5005},{},[5006,5008,5014],{"type":25,"value":5007},"同一个任务，如果项目里放了一套精简过的工程规则，行为会更稳定：先用 ",{"type":20,"tag":84,"props":5009,"children":5011},{"className":5010},[],[5012],{"type":25,"value":5013},"npx tsc --noEmit",{"type":25,"value":5015}," 复现错误，定位第一个有意义的失败，改根因，再跑一次验证，通过后才收工。",{"type":20,"tag":21,"props":5017,"children":5018},{},[5019],{"type":25,"value":5020},"多了一步，但少了返工。",{"type":20,"tag":21,"props":5022,"children":5023},{},[5024,5026,5031],{"type":25,"value":5025},"区别不在于 Codex 突然“变聪明”了，而是它被明确提醒了一件工程里很普通、但 AI 编程助手经常漏掉的事：",{"type":20,"tag":33,"props":5027,"children":5028},{},[5029],{"type":25,"value":5030},"改完要验证",{"type":25,"value":110},{"type":20,"tag":21,"props":5033,"children":5034},{},[5035,5037,5043],{"type":25,"value":5036},"这就是 ",{"type":20,"tag":84,"props":5038,"children":5040},{"className":5039},[],[5041],{"type":25,"value":5042},"everything-claude-code",{"type":25,"value":5044},"（下面简称 ECC）对 Codex 的真正价值。它不是一个应该被完整复制进项目的全家桶，而是一套可以被提炼的工程习惯：规则怎么写、任务怎么拆、什么时候验证、什么时候做安全检查。",{"type":20,"tag":21,"props":5046,"children":5047},{},[5048,5050],{"type":25,"value":5049},"这篇文章想讲清楚一件事：",{"type":20,"tag":33,"props":5051,"children":5052},{},[5053],{"type":25,"value":5054},"在 Codex 里用 ECC，重点不是搬运，而是翻译。",{"type":20,"tag":5056,"props":5057,"children":5058},"hr",{},[],{"type":20,"tag":1863,"props":5060,"children":5062},{"id":5061},"codex-本来就能做什么",[5063],{"type":25,"value":5064},"Codex 本来就能做什么",{"type":20,"tag":21,"props":5066,"children":5067},{},[5068],{"type":25,"value":5069},"先把底座说清楚。Codex 原生已经有一套项目级能力，不需要 ECC 也能工作：",{"type":20,"tag":1902,"props":5071,"children":5072},{},[5073,5094],{"type":20,"tag":1906,"props":5074,"children":5075},{},[5076],{"type":20,"tag":1910,"props":5077,"children":5078},{},[5079,5084,5089],{"type":20,"tag":1914,"props":5080,"children":5081},{},[5082],{"type":25,"value":5083},"能力",{"type":20,"tag":1914,"props":5085,"children":5086},{},[5087],{"type":25,"value":5088},"Codex 原生入口",{"type":20,"tag":1914,"props":5090,"children":5091},{},[5092],{"type":25,"value":5093},"适合承载什么",{"type":20,"tag":1930,"props":5095,"children":5096},{},[5097,5119,5141,5165,5183,5206],{"type":20,"tag":1910,"props":5098,"children":5099},{},[5100,5105,5114],{"type":20,"tag":1937,"props":5101,"children":5102},{},[5103],{"type":25,"value":5104},"项目规则",{"type":20,"tag":1937,"props":5106,"children":5107},{},[5108],{"type":20,"tag":84,"props":5109,"children":5111},{"className":5110},[],[5112],{"type":25,"value":5113},"AGENTS.md",{"type":20,"tag":1937,"props":5115,"children":5116},{},[5117],{"type":25,"value":5118},"项目规范、测试命令、协作方式、禁止事项",{"type":20,"tag":1910,"props":5120,"children":5121},{},[5122,5127,5136],{"type":20,"tag":1937,"props":5123,"children":5124},{},[5125],{"type":25,"value":5126},"项目配置",{"type":20,"tag":1937,"props":5128,"children":5129},{},[5130],{"type":20,"tag":84,"props":5131,"children":5133},{"className":5132},[],[5134],{"type":25,"value":5135},".codex\u002Fconfig.toml",{"type":20,"tag":1937,"props":5137,"children":5138},{},[5139],{"type":25,"value":5140},"sandbox、approval、MCP、profile 等配置",{"type":20,"tag":1910,"props":5142,"children":5143},{},[5144,5149,5160],{"type":20,"tag":1937,"props":5145,"children":5146},{},[5147],{"type":25,"value":5148},"Skills",{"type":20,"tag":1937,"props":5150,"children":5151},{},[5152,5158],{"type":20,"tag":84,"props":5153,"children":5155},{"className":5154},[],[5156],{"type":25,"value":5157},".agents\u002Fskills\u002F*\u002FSKILL.md",{"type":25,"value":5159}," 或已安装 skills",{"type":20,"tag":1937,"props":5161,"children":5162},{},[5163],{"type":25,"value":5164},"可复用的任务工作流",{"type":20,"tag":1910,"props":5166,"children":5167},{},[5168,5173,5178],{"type":20,"tag":1937,"props":5169,"children":5170},{},[5171],{"type":25,"value":5172},"Subagents",{"type":20,"tag":1937,"props":5174,"children":5175},{},[5176],{"type":25,"value":5177},"Codex 内置或项目配置",{"type":20,"tag":1937,"props":5179,"children":5180},{},[5181],{"type":25,"value":5182},"并行探索、实现、审查",{"type":20,"tag":1910,"props":5184,"children":5185},{},[5186,5191,5201],{"type":20,"tag":1937,"props":5187,"children":5188},{},[5189],{"type":25,"value":5190},"MCP",{"type":20,"tag":1937,"props":5192,"children":5193},{},[5194,5199],{"type":20,"tag":84,"props":5195,"children":5197},{"className":5196},[],[5198],{"type":25,"value":5135},{"type":25,"value":5200}," 或全局配置",{"type":20,"tag":1937,"props":5202,"children":5203},{},[5204],{"type":25,"value":5205},"接入 GitHub、Playwright、文档检索等工具",{"type":20,"tag":1910,"props":5207,"children":5208},{},[5209,5214,5219],{"type":20,"tag":1937,"props":5210,"children":5211},{},[5212],{"type":25,"value":5213},"Hooks \u002F Commands",{"type":20,"tag":1937,"props":5215,"children":5216},{},[5217],{"type":25,"value":5218},"Codex 原生配置或命令机制",{"type":20,"tag":1937,"props":5220,"children":5221},{},[5222],{"type":25,"value":5223},"做流程自动化、权限检查、命令入口",{"type":20,"tag":21,"props":5225,"children":5226},{},[5227,5229,5234],{"type":25,"value":5228},"OpenAI 官方文档里，",{"type":20,"tag":84,"props":5230,"children":5232},{"className":5231},[],[5233],{"type":25,"value":5113},{"type":25,"value":5235}," 是 Codex 的项目级说明入口；Skills 是 Codex 可发现、可调用的任务能力；Hooks 也已经有 Codex 自己的配置方式。",{"type":20,"tag":21,"props":5237,"children":5238},{},[5239,5241],{"type":25,"value":5240},"所以问题不是“Codex 有没有这些能力”。真正的问题是：",{"type":20,"tag":33,"props":5242,"children":5243},{},[5244],{"type":25,"value":5245},"ECC 里的 Claude Code 配置，不能默认等价于 Codex 配置。",{"type":20,"tag":21,"props":5247,"children":5248},{},[5249],{"type":25,"value":5250},"Codex 是引擎，ECC 更像一套驾驶习惯。把驾驶习惯移植过来有价值，但你不能把另一辆车的按钮面板直接粘到方向盘上。",{"type":20,"tag":5056,"props":5252,"children":5253},{},[],{"type":20,"tag":1863,"props":5255,"children":5257},{"id":5256},"为什么不能全量搬",[5258],{"type":25,"value":5256},{"type":20,"tag":21,"props":5260,"children":5261},{},[5262],{"type":25,"value":5263},"ECC 最早的语境更偏 Claude Code。社区项目里常见的内容包括：",{"type":20,"tag":44,"props":5265,"children":5266},{},[5267,5272,5277,5282,5287,5292,5297,5302],{"type":20,"tag":48,"props":5268,"children":5269},{},[5270],{"type":25,"value":5271},"agents",{"type":20,"tag":48,"props":5273,"children":5274},{},[5275],{"type":25,"value":5276},"skills",{"type":20,"tag":48,"props":5278,"children":5279},{},[5280],{"type":25,"value":5281},"rules",{"type":20,"tag":48,"props":5283,"children":5284},{},[5285],{"type":25,"value":5286},"hooks",{"type":20,"tag":48,"props":5288,"children":5289},{},[5290],{"type":25,"value":5291},"slash commands",{"type":20,"tag":48,"props":5293,"children":5294},{},[5295],{"type":25,"value":5296},"MCP 配置",{"type":20,"tag":48,"props":5298,"children":5299},{},[5300],{"type":25,"value":5301},"installer \u002F sync 脚本",{"type":20,"tag":48,"props":5303,"children":5304},{},[5305],{"type":25,"value":5306},"安全扫描或 dashboard 辅助工具",{"type":20,"tag":21,"props":5308,"children":5309},{},[5310],{"type":25,"value":5311},"这些东西有些能直接变成 Codex 规则，有些需要改写，有些则不应该进普通项目。",{"type":20,"tag":21,"props":5313,"children":5314},{},[5315],{"type":25,"value":5316},"直接把整个 ECC 仓库复制进 Codex 项目，通常会遇到三个问题。",{"type":20,"tag":21,"props":5318,"children":5319},{},[5320,5322,5327],{"type":25,"value":5321},"第一是 ",{"type":20,"tag":33,"props":5323,"children":5324},{},[5325],{"type":25,"value":5326},"上下文膨胀",{"type":25,"value":5328},"。规则越多，不代表 Codex 越稳定。几十个规则文件一起塞进去，很容易让真正关键的指令被噪声淹没。你想要的是“每次任务都能读到关键规则”，不是“仓库里有很多漂亮的规则”。",{"type":20,"tag":21,"props":5330,"children":5331},{},[5332,5334,5339],{"type":25,"value":5333},"第二是 ",{"type":20,"tag":33,"props":5335,"children":5336},{},[5337],{"type":25,"value":5338},"语义错位",{"type":25,"value":5340},"。Codex 有自己的 Hooks、Commands、Slash commands 和 Skills，但 Claude Code 里的 hooks 或 slash commands 不会因为文件名一样就自动变成 Codex 原生能力。能复用的是思想和流程，不是所有配置格式。",{"type":20,"tag":21,"props":5342,"children":5343},{},[5344,5346,5351],{"type":25,"value":5345},"第三是 ",{"type":20,"tag":33,"props":5347,"children":5348},{},[5349],{"type":25,"value":5350},"配置污染",{"type":25,"value":5352},"。全局 MCP、权限规则、安装脚本、安全扫描工具一旦混进项目，后面很难判断哪些是当前项目真正需要的，哪些只是跟着全家桶一起进来的。",{"type":20,"tag":21,"props":5354,"children":5355},{},[5356],{"type":25,"value":5357},"所以不是“搬家”，是“提炼”。",{"type":20,"tag":5056,"props":5359,"children":5360},{},[],{"type":20,"tag":1863,"props":5362,"children":5364},{"id":5363},"先提炼四样东西",[5365],{"type":25,"value":5363},{"type":20,"tag":21,"props":5367,"children":5368},{},[5369],{"type":25,"value":5370},"对个人项目和中小型团队来说，最值得先落地的是这四类：",{"type":20,"tag":126,"props":5372,"children":5374},{"className":324,"code":5373,"language":25,"meta":8,"style":8},"your-project\u002F\n  AGENTS.md\n  .codex\u002F\n    config.toml\n    ecc\u002F\n      rules.md\n      workflow.md\n      security.md\n  .agents\u002F\n    skills\u002F\n      search-first\u002F\n      tdd-workflow\u002F\n      verification-loop\u002F\n      security-review\u002F\n      code-review\u002F\n      ...（按需增加）\n",[5375],{"type":20,"tag":84,"props":5376,"children":5377},{"__ignoreMap":8},[5378,5386,5394,5402,5410,5418,5426,5434,5442,5450,5458,5466,5474,5482,5490,5498],{"type":20,"tag":157,"props":5379,"children":5380},{"class":159,"line":160},[5381],{"type":20,"tag":157,"props":5382,"children":5383},{},[5384],{"type":25,"value":5385},"your-project\u002F\n",{"type":20,"tag":157,"props":5387,"children":5388},{"class":159,"line":169},[5389],{"type":20,"tag":157,"props":5390,"children":5391},{},[5392],{"type":25,"value":5393},"  AGENTS.md\n",{"type":20,"tag":157,"props":5395,"children":5396},{"class":159,"line":179},[5397],{"type":20,"tag":157,"props":5398,"children":5399},{},[5400],{"type":25,"value":5401},"  .codex\u002F\n",{"type":20,"tag":157,"props":5403,"children":5404},{"class":159,"line":188},[5405],{"type":20,"tag":157,"props":5406,"children":5407},{},[5408],{"type":25,"value":5409},"    config.toml\n",{"type":20,"tag":157,"props":5411,"children":5412},{"class":159,"line":196},[5413],{"type":20,"tag":157,"props":5414,"children":5415},{},[5416],{"type":25,"value":5417},"    ecc\u002F\n",{"type":20,"tag":157,"props":5419,"children":5420},{"class":159,"line":204},[5421],{"type":20,"tag":157,"props":5422,"children":5423},{},[5424],{"type":25,"value":5425},"      rules.md\n",{"type":20,"tag":157,"props":5427,"children":5428},{"class":159,"line":213},[5429],{"type":20,"tag":157,"props":5430,"children":5431},{},[5432],{"type":25,"value":5433},"      workflow.md\n",{"type":20,"tag":157,"props":5435,"children":5436},{"class":159,"line":222},[5437],{"type":20,"tag":157,"props":5438,"children":5439},{},[5440],{"type":25,"value":5441},"      security.md\n",{"type":20,"tag":157,"props":5443,"children":5444},{"class":159,"line":440},[5445],{"type":20,"tag":157,"props":5446,"children":5447},{},[5448],{"type":25,"value":5449},"  .agents\u002F\n",{"type":20,"tag":157,"props":5451,"children":5452},{"class":159,"line":448},[5453],{"type":20,"tag":157,"props":5454,"children":5455},{},[5456],{"type":25,"value":5457},"    skills\u002F\n",{"type":20,"tag":157,"props":5459,"children":5460},{"class":159,"line":456},[5461],{"type":20,"tag":157,"props":5462,"children":5463},{},[5464],{"type":25,"value":5465},"      search-first\u002F\n",{"type":20,"tag":157,"props":5467,"children":5468},{"class":159,"line":465},[5469],{"type":20,"tag":157,"props":5470,"children":5471},{},[5472],{"type":25,"value":5473},"      tdd-workflow\u002F\n",{"type":20,"tag":157,"props":5475,"children":5476},{"class":159,"line":474},[5477],{"type":20,"tag":157,"props":5478,"children":5479},{},[5480],{"type":25,"value":5481},"      verification-loop\u002F\n",{"type":20,"tag":157,"props":5483,"children":5484},{"class":159,"line":483},[5485],{"type":20,"tag":157,"props":5486,"children":5487},{},[5488],{"type":25,"value":5489},"      security-review\u002F\n",{"type":20,"tag":157,"props":5491,"children":5492},{"class":159,"line":491},[5493],{"type":20,"tag":157,"props":5494,"children":5495},{},[5496],{"type":25,"value":5497},"      code-review\u002F\n",{"type":20,"tag":157,"props":5499,"children":5500},{"class":159,"line":499},[5501],{"type":20,"tag":157,"props":5502,"children":5503},{},[5504],{"type":25,"value":5505},"      ...（按需增加）\n",{"type":20,"tag":21,"props":5507,"children":5508},{},[5509],{"type":25,"value":5510},"它们分别解决四个问题：",{"type":20,"tag":44,"props":5512,"children":5513},{},[5514,5524,5534,5545],{"type":20,"tag":48,"props":5515,"children":5516},{},[5517,5522],{"type":20,"tag":84,"props":5518,"children":5520},{"className":5519},[],[5521],{"type":25,"value":5113},{"type":25,"value":5523},"：项目入口。只放最短、最稳定、每次任务都值得看的规则。",{"type":20,"tag":48,"props":5525,"children":5526},{},[5527,5532],{"type":20,"tag":84,"props":5528,"children":5530},{"className":5529},[],[5531],{"type":25,"value":5135},{"type":25,"value":5533},"：项目配置。把 sandbox、approval、MCP 等配置和全局配置区分开。",{"type":20,"tag":48,"props":5535,"children":5536},{},[5537,5543],{"type":20,"tag":84,"props":5538,"children":5540},{"className":5539},[],[5541],{"type":25,"value":5542},".codex\u002Fecc\u002F*.md",{"type":25,"value":5544},"：长期工程规则。比如工作流、安全边界、协作约定。",{"type":20,"tag":48,"props":5546,"children":5547},{},[5548,5554],{"type":20,"tag":84,"props":5549,"children":5551},{"className":5550},[],[5552],{"type":25,"value":5553},".agents\u002Fskills\u002F",{"type":25,"value":5555},"：可复用任务流程。比如修构建、做代码审查、写测试、更新文档。",{"type":20,"tag":21,"props":5557,"children":5558},{},[5559],{"type":25,"value":5560},"这里的关键是分层。",{"type":20,"tag":21,"props":5562,"children":5563},{},[5564,5569,5571,5577],{"type":20,"tag":84,"props":5565,"children":5567},{"className":5566},[],[5568],{"type":25,"value":5113},{"type":25,"value":5570}," 不应该变成一本小册子。它更适合做入口：告诉 Codex 这个项目有什么规则，以及复杂任务需要去哪里读更详细的流程。真正的大段规则放到 ",{"type":20,"tag":84,"props":5572,"children":5574},{"className":5573},[],[5575],{"type":25,"value":5576},".codex\u002Fecc\u002F",{"type":25,"value":5578},"，可复用任务步骤放到 skills。",{"type":20,"tag":21,"props":5580,"children":5581},{},[5582,5584,5589],{"type":25,"value":5583},"这样做有一个实际好处：规则能被维护。一个 500 行的 ",{"type":20,"tag":84,"props":5585,"children":5587},{"className":5586},[],[5588],{"type":25,"value":5113},{"type":25,"value":5590},"，最后往往没人敢改；三个职责清楚的规则文件，后续增删都更容易。",{"type":20,"tag":5056,"props":5592,"children":5593},{},[],{"type":20,"tag":1863,"props":5595,"children":5597},{"id":5596},"为什么要做中文友好版本",[5598],{"type":25,"value":5596},{"type":20,"tag":21,"props":5600,"children":5601},{},[5602],{"type":25,"value":5603},"Codex 能理解中文，也能读英文配置。但如果你的日常指令是中文：",{"type":20,"tag":126,"props":5605,"children":5607},{"className":324,"code":5606,"language":25,"meta":8,"style":8},"帮我做一个登录页\n帮我修复这个构建错误\n帮我审查这个 PR 有没有安全问题\n帮我把 README 更新一下\n",[5608],{"type":20,"tag":84,"props":5609,"children":5610},{"__ignoreMap":8},[5611,5619,5627,5635],{"type":20,"tag":157,"props":5612,"children":5613},{"class":159,"line":160},[5614],{"type":20,"tag":157,"props":5615,"children":5616},{},[5617],{"type":25,"value":5618},"帮我做一个登录页\n",{"type":20,"tag":157,"props":5620,"children":5621},{"class":159,"line":169},[5622],{"type":20,"tag":157,"props":5623,"children":5624},{},[5625],{"type":25,"value":5626},"帮我修复这个构建错误\n",{"type":20,"tag":157,"props":5628,"children":5629},{"class":159,"line":179},[5630],{"type":20,"tag":157,"props":5631,"children":5632},{},[5633],{"type":25,"value":5634},"帮我审查这个 PR 有没有安全问题\n",{"type":20,"tag":157,"props":5636,"children":5637},{"class":159,"line":188},[5638],{"type":20,"tag":157,"props":5639,"children":5640},{},[5641],{"type":25,"value":5642},"帮我把 README 更新一下\n",{"type":20,"tag":21,"props":5644,"children":5645},{},[5646],{"type":25,"value":5647},"那么 skills 的触发描述最好也对中文友好。",{"type":20,"tag":21,"props":5649,"children":5650},{},[5651],{"type":25,"value":5652},"我更推荐这种写法：英文 skill name 保持不变，英文关键词保留，同时补中文触发描述。",{"type":20,"tag":126,"props":5654,"children":5658},{"className":5655,"code":5656,"language":5657,"meta":8,"style":8},"language-yaml shiki shiki-themes github-dark","name: build-fix\ndescription: Use when fixing CI, build, typecheck, compile, or test failures.\n  当用户用中文要求修复 CI 失败、构建错误、typecheck 报错、\n  编译失败或测试失败时使用。\n","yaml",[5659],{"type":20,"tag":84,"props":5660,"children":5661},{"__ignoreMap":8},[5662,5680,5697,5705],{"type":20,"tag":157,"props":5663,"children":5664},{"class":159,"line":160},[5665,5671,5675],{"type":20,"tag":157,"props":5666,"children":5668},{"style":5667},"--shiki-default:#85E89D",[5669],{"type":25,"value":5670},"name",{"type":20,"tag":157,"props":5672,"children":5673},{"style":892},[5674],{"type":25,"value":908},{"type":20,"tag":157,"props":5676,"children":5677},{"style":254},[5678],{"type":25,"value":5679},"build-fix\n",{"type":20,"tag":157,"props":5681,"children":5682},{"class":159,"line":169},[5683,5688,5692],{"type":20,"tag":157,"props":5684,"children":5685},{"style":5667},[5686],{"type":25,"value":5687},"description",{"type":20,"tag":157,"props":5689,"children":5690},{"style":892},[5691],{"type":25,"value":908},{"type":20,"tag":157,"props":5693,"children":5694},{"style":254},[5695],{"type":25,"value":5696},"Use when fixing CI, build, typecheck, compile, or test failures.\n",{"type":20,"tag":157,"props":5698,"children":5699},{"class":159,"line":179},[5700],{"type":20,"tag":157,"props":5701,"children":5702},{"style":254},[5703],{"type":25,"value":5704},"  当用户用中文要求修复 CI 失败、构建错误、typecheck 报错、\n",{"type":20,"tag":157,"props":5706,"children":5707},{"class":159,"line":188},[5708],{"type":20,"tag":157,"props":5709,"children":5710},{"style":254},[5711],{"type":25,"value":5712},"  编译失败或测试失败时使用。\n",{"type":20,"tag":21,"props":5714,"children":5715},{},[5716],{"type":25,"value":5717},"这样有三个好处：",{"type":20,"tag":5719,"props":5720,"children":5721},"ol",{},[5722,5727,5732],{"type":20,"tag":48,"props":5723,"children":5724},{},[5725],{"type":25,"value":5726},"兼容英文生态，后续迁移和分享成本低。",{"type":20,"tag":48,"props":5728,"children":5729},{},[5730],{"type":25,"value":5731},"中文任务更容易命中合适的 skill。",{"type":20,"tag":48,"props":5733,"children":5734},{},[5735],{"type":25,"value":5736},"人自己打开文件维护时，不需要在英文抽象描述里来回猜。",{"type":20,"tag":21,"props":5738,"children":5739},{},[5740,5742,5747],{"type":25,"value":5741},"skill 的名字可以短，描述必须准。因为 Codex 是否自动选择一个 skill，很大程度上取决于 ",{"type":20,"tag":84,"props":5743,"children":5745},{"className":5744},[],[5746],{"type":25,"value":5687},{"type":25,"value":5748}," 是否把触发场景写清楚。",{"type":20,"tag":5056,"props":5750,"children":5751},{},[],{"type":20,"tag":1863,"props":5753,"children":5755},{"id":5754},"level-2日常开发够用的中间层",[5756],{"type":25,"value":5757},"Level 2：日常开发够用的中间层",{"type":20,"tag":21,"props":5759,"children":5760},{},[5761],{"type":25,"value":5762},"我更倾向于把这套模板做成 Clean ECC for Codex Level 2。",{"type":20,"tag":21,"props":5764,"children":5765},{},[5766],{"type":25,"value":5767},"它不是最小版，也不是全家桶，而是一个适合真实项目长期使用的中间层：",{"type":20,"tag":1902,"props":5769,"children":5770},{},[5771,5792],{"type":20,"tag":1906,"props":5772,"children":5773},{},[5774],{"type":20,"tag":1910,"props":5775,"children":5776},{},[5777,5782,5787],{"type":20,"tag":1914,"props":5778,"children":5779},{},[5780],{"type":25,"value":5781},"层级",{"type":20,"tag":1914,"props":5783,"children":5784},{},[5785],{"type":25,"value":5786},"内容",{"type":20,"tag":1914,"props":5788,"children":5789},{},[5790],{"type":25,"value":5791},"适合谁",{"type":20,"tag":1930,"props":5793,"children":5794},{},[5795,5818,5844],{"type":20,"tag":1910,"props":5796,"children":5797},{},[5798,5803,5813],{"type":20,"tag":1937,"props":5799,"children":5800},{},[5801],{"type":25,"value":5802},"Level 1",{"type":20,"tag":1937,"props":5804,"children":5805},{},[5806,5811],{"type":20,"tag":84,"props":5807,"children":5809},{"className":5808},[],[5810],{"type":25,"value":5113},{"type":25,"value":5812}," + 少量基础 skills",{"type":20,"tag":1937,"props":5814,"children":5815},{},[5816],{"type":25,"value":5817},"想试试，不想改太多项目结构",{"type":20,"tag":1910,"props":5819,"children":5820},{},[5821,5826,5839],{"type":20,"tag":1937,"props":5822,"children":5823},{},[5824],{"type":25,"value":5825},"Level 2",{"type":20,"tag":1937,"props":5827,"children":5828},{},[5829,5831,5837],{"type":25,"value":5830},"14 个工程工作流 skills + ",{"type":20,"tag":84,"props":5832,"children":5834},{"className":5833},[],[5835],{"type":25,"value":5836},".codex\u002Fecc",{"type":25,"value":5838}," 规则",{"type":20,"tag":1937,"props":5840,"children":5841},{},[5842],{"type":25,"value":5843},"日常用 Codex 做真实开发",{"type":20,"tag":1910,"props":5845,"children":5846},{},[5847,5852,5857],{"type":20,"tag":1937,"props":5848,"children":5849},{},[5850],{"type":25,"value":5851},"Full-ish",{"type":20,"tag":1937,"props":5853,"children":5854},{},[5855],{"type":25,"value":5856},"MCP、subagents、hooks、安全扫描等更重能力",{"type":20,"tag":1937,"props":5858,"children":5859},{},[5860],{"type":25,"value":5861},"团队级、重度自动化用户",{"type":20,"tag":21,"props":5863,"children":5864},{},[5865],{"type":25,"value":5866},"Level 2 重点不是“装得多”，而是覆盖日常开发里最容易出问题的环节：",{"type":20,"tag":1902,"props":5868,"children":5869},{},[5870,5886],{"type":20,"tag":1906,"props":5871,"children":5872},{},[5873],{"type":20,"tag":1910,"props":5874,"children":5875},{},[5876,5881],{"type":20,"tag":1914,"props":5877,"children":5878},{},[5879],{"type":25,"value":5880},"skill",{"type":20,"tag":1914,"props":5882,"children":5883},{},[5884],{"type":25,"value":5885},"解决的问题",{"type":20,"tag":1930,"props":5887,"children":5888},{},[5889,5906,5923,5940,5957,5974,5991,6008,6025,6042,6059,6076,6093,6110],{"type":20,"tag":1910,"props":5890,"children":5891},{},[5892,5901],{"type":20,"tag":1937,"props":5893,"children":5894},{},[5895],{"type":20,"tag":84,"props":5896,"children":5898},{"className":5897},[],[5899],{"type":25,"value":5900},"search-first",{"type":20,"tag":1937,"props":5902,"children":5903},{},[5904],{"type":25,"value":5905},"先查项目和文档，再动手写",{"type":20,"tag":1910,"props":5907,"children":5908},{},[5909,5918],{"type":20,"tag":1937,"props":5910,"children":5911},{},[5912],{"type":20,"tag":84,"props":5913,"children":5915},{"className":5914},[],[5916],{"type":25,"value":5917},"tdd-workflow",{"type":20,"tag":1937,"props":5919,"children":5920},{},[5921],{"type":25,"value":5922},"适合有明确行为边界的功能改动",{"type":20,"tag":1910,"props":5924,"children":5925},{},[5926,5935],{"type":20,"tag":1937,"props":5927,"children":5928},{},[5929],{"type":20,"tag":84,"props":5930,"children":5932},{"className":5931},[],[5933],{"type":25,"value":5934},"verification-loop",{"type":20,"tag":1937,"props":5936,"children":5937},{},[5938],{"type":25,"value":5939},"改完跑测试、typecheck、lint 或构建",{"type":20,"tag":1910,"props":5941,"children":5942},{},[5943,5952],{"type":20,"tag":1937,"props":5944,"children":5945},{},[5946],{"type":20,"tag":84,"props":5947,"children":5949},{"className":5948},[],[5950],{"type":25,"value":5951},"security-review",{"type":20,"tag":1937,"props":5953,"children":5954},{},[5955],{"type":25,"value":5956},"权限、凭证、输入校验、危险操作检查",{"type":20,"tag":1910,"props":5958,"children":5959},{},[5960,5969],{"type":20,"tag":1937,"props":5961,"children":5962},{},[5963],{"type":20,"tag":84,"props":5964,"children":5966},{"className":5965},[],[5967],{"type":25,"value":5968},"code-review",{"type":20,"tag":1937,"props":5970,"children":5971},{},[5972],{"type":25,"value":5973},"站在 reviewer 角度找 bug 和回归",{"type":20,"tag":1910,"props":5975,"children":5976},{},[5977,5986],{"type":20,"tag":1937,"props":5978,"children":5979},{},[5980],{"type":20,"tag":84,"props":5981,"children":5983},{"className":5982},[],[5984],{"type":25,"value":5985},"frontend-patterns",{"type":20,"tag":1937,"props":5987,"children":5988},{},[5989],{"type":25,"value":5990},"前端组件、交互、响应式和可访问性",{"type":20,"tag":1910,"props":5992,"children":5993},{},[5994,6003],{"type":20,"tag":1937,"props":5995,"children":5996},{},[5997],{"type":20,"tag":84,"props":5998,"children":6000},{"className":5999},[],[6001],{"type":25,"value":6002},"backend-patterns",{"type":20,"tag":1937,"props":6004,"children":6005},{},[6006],{"type":25,"value":6007},"后端边界、错误处理、数据一致性",{"type":20,"tag":1910,"props":6009,"children":6010},{},[6011,6020],{"type":20,"tag":1937,"props":6012,"children":6013},{},[6014],{"type":20,"tag":84,"props":6015,"children":6017},{"className":6016},[],[6018],{"type":25,"value":6019},"api-design",{"type":20,"tag":1937,"props":6021,"children":6022},{},[6023],{"type":25,"value":6024},"请求响应结构、错误码、分页、幂等性",{"type":20,"tag":1910,"props":6026,"children":6027},{},[6028,6037],{"type":20,"tag":1937,"props":6029,"children":6030},{},[6031],{"type":20,"tag":84,"props":6032,"children":6034},{"className":6033},[],[6035],{"type":25,"value":6036},"docker-patterns",{"type":20,"tag":1937,"props":6038,"children":6039},{},[6040],{"type":25,"value":6041},"Dockerfile、镜像构建、端口、体积和安全",{"type":20,"tag":1910,"props":6043,"children":6044},{},[6045,6054],{"type":20,"tag":1937,"props":6046,"children":6047},{},[6048],{"type":20,"tag":84,"props":6049,"children":6051},{"className":6050},[],[6052],{"type":25,"value":6053},"deployment-patterns",{"type":20,"tag":1937,"props":6055,"children":6056},{},[6057],{"type":25,"value":6058},"部署、回滚、环境变量、日志和健康检查",{"type":20,"tag":1910,"props":6060,"children":6061},{},[6062,6071],{"type":20,"tag":1937,"props":6063,"children":6064},{},[6065],{"type":20,"tag":84,"props":6066,"children":6068},{"className":6067},[],[6069],{"type":25,"value":6070},"deep-research",{"type":20,"tag":1937,"props":6072,"children":6073},{},[6074],{"type":25,"value":6075},"需要多轮查证和材料整理的任务",{"type":20,"tag":1910,"props":6077,"children":6078},{},[6079,6088],{"type":20,"tag":1937,"props":6080,"children":6081},{},[6082],{"type":20,"tag":84,"props":6083,"children":6085},{"className":6084},[],[6086],{"type":25,"value":6087},"docs-update",{"type":20,"tag":1937,"props":6089,"children":6090},{},[6091],{"type":25,"value":6092},"代码变更后同步 README、注释或文档",{"type":20,"tag":1910,"props":6094,"children":6095},{},[6096,6105],{"type":20,"tag":1937,"props":6097,"children":6098},{},[6099],{"type":20,"tag":84,"props":6100,"children":6102},{"className":6101},[],[6103],{"type":25,"value":6104},"build-fix",{"type":20,"tag":1937,"props":6106,"children":6107},{},[6108],{"type":25,"value":6109},"CI、编译、typecheck、测试失败修复",{"type":20,"tag":1910,"props":6111,"children":6112},{},[6113,6122],{"type":20,"tag":1937,"props":6114,"children":6115},{},[6116],{"type":20,"tag":84,"props":6117,"children":6119},{"className":6118},[],[6120],{"type":25,"value":6121},"refactor-clean",{"type":20,"tag":1937,"props":6123,"children":6124},{},[6125],{"type":25,"value":6126},"小步重构，保持行为不变",{"type":20,"tag":21,"props":6128,"children":6129},{},[6130],{"type":25,"value":6131},"这些 skills 的价值不是让 Codex 多背几句口号，而是把任务流程固定下来。",{"type":20,"tag":21,"props":6133,"children":6134},{},[6135],{"type":25,"value":6136},"比如用户说：",{"type":20,"tag":126,"props":6138,"children":6140},{"className":324,"code":6139,"language":25,"meta":8,"style":8},"帮我修复 CI 里的 typecheck 报错\n",[6141],{"type":20,"tag":84,"props":6142,"children":6143},{"__ignoreMap":8},[6144],{"type":20,"tag":157,"props":6145,"children":6146},{"class":159,"line":160},[6147],{"type":20,"tag":157,"props":6148,"children":6149},{},[6150],{"type":25,"value":6139},{"type":20,"tag":21,"props":6152,"children":6153},{},[6154],{"type":25,"value":6155},"Codex 更容易匹配到：",{"type":20,"tag":126,"props":6157,"children":6159},{"className":324,"code":6158,"language":25,"meta":8,"style":8},"build-fix\nverification-loop\n",[6160],{"type":20,"tag":84,"props":6161,"children":6162},{"__ignoreMap":8},[6163,6170],{"type":20,"tag":157,"props":6164,"children":6165},{"class":159,"line":160},[6166],{"type":20,"tag":157,"props":6167,"children":6168},{},[6169],{"type":25,"value":5679},{"type":20,"tag":157,"props":6171,"children":6172},{"class":159,"line":169},[6173],{"type":20,"tag":157,"props":6174,"children":6175},{},[6176],{"type":25,"value":6177},"verification-loop\n",{"type":20,"tag":21,"props":6179,"children":6180},{},[6181],{"type":25,"value":6182},"于是任务流程会变成：",{"type":20,"tag":126,"props":6184,"children":6186},{"className":324,"code":6185,"language":25,"meta":8,"style":8},"复现失败 -> 找第一个有意义的错误 -> 修根因 -> 重新验证 -> 汇报结果\n",[6187],{"type":20,"tag":84,"props":6188,"children":6189},{"__ignoreMap":8},[6190],{"type":20,"tag":157,"props":6191,"children":6192},{"class":159,"line":160},[6193],{"type":20,"tag":157,"props":6194,"children":6195},{},[6196],{"type":25,"value":6185},{"type":20,"tag":21,"props":6198,"children":6199},{},[6200],{"type":25,"value":6201},"再比如用户说：",{"type":20,"tag":126,"props":6203,"children":6205},{"className":324,"code":6204,"language":25,"meta":8,"style":8},"帮我设计一个订单 API\n",[6206],{"type":20,"tag":84,"props":6207,"children":6208},{"__ignoreMap":8},[6209],{"type":20,"tag":157,"props":6210,"children":6211},{"class":159,"line":160},[6212],{"type":20,"tag":157,"props":6213,"children":6214},{},[6215],{"type":25,"value":6204},{"type":20,"tag":21,"props":6217,"children":6218},{},[6219],{"type":25,"value":6220},"Codex 可以匹配：",{"type":20,"tag":126,"props":6222,"children":6224},{"className":324,"code":6223,"language":25,"meta":8,"style":8},"api-design\nbackend-patterns\nsecurity-review\n",[6225],{"type":20,"tag":84,"props":6226,"children":6227},{"__ignoreMap":8},[6228,6236,6244],{"type":20,"tag":157,"props":6229,"children":6230},{"class":159,"line":160},[6231],{"type":20,"tag":157,"props":6232,"children":6233},{},[6234],{"type":25,"value":6235},"api-design\n",{"type":20,"tag":157,"props":6237,"children":6238},{"class":159,"line":169},[6239],{"type":20,"tag":157,"props":6240,"children":6241},{},[6242],{"type":25,"value":6243},"backend-patterns\n",{"type":20,"tag":157,"props":6245,"children":6246},{"class":159,"line":179},[6247],{"type":20,"tag":157,"props":6248,"children":6249},{},[6250],{"type":25,"value":6251},"security-review\n",{"type":20,"tag":21,"props":6253,"children":6254},{},[6255],{"type":25,"value":6256},"这样它更可能同时考虑请求响应结构、错误码、分页、权限、幂等性和兼容性，而不是只写一个看起来能跑的 endpoint。",{"type":20,"tag":5056,"props":6258,"children":6259},{},[],{"type":20,"tag":1863,"props":6261,"children":6263},{"id":6262},"怎么用",[6264],{"type":25,"value":6262},{"type":20,"tag":21,"props":6266,"children":6267},{},[6268],{"type":25,"value":6269},"最小使用方式很简单：把三样东西放到项目根目录。",{"type":20,"tag":126,"props":6271,"children":6273},{"className":324,"code":6272,"language":25,"meta":8,"style":8},"AGENTS.md\n.codex\u002F\n.agents\u002F\n",[6274],{"type":20,"tag":84,"props":6275,"children":6276},{"__ignoreMap":8},[6277,6285,6293],{"type":20,"tag":157,"props":6278,"children":6279},{"class":159,"line":160},[6280],{"type":20,"tag":157,"props":6281,"children":6282},{},[6283],{"type":25,"value":6284},"AGENTS.md\n",{"type":20,"tag":157,"props":6286,"children":6287},{"class":159,"line":169},[6288],{"type":20,"tag":157,"props":6289,"children":6290},{},[6291],{"type":25,"value":6292},".codex\u002F\n",{"type":20,"tag":157,"props":6294,"children":6295},{"class":159,"line":179},[6296],{"type":20,"tag":157,"props":6297,"children":6298},{},[6299],{"type":25,"value":6300},".agents\u002F\n",{"type":20,"tag":21,"props":6302,"children":6303},{},[6304],{"type":25,"value":6305},"然后用 Codex 打开这个项目。",{"type":20,"tag":21,"props":6307,"children":6308},{},[6309,6311,6316],{"type":25,"value":6310},"推荐的 ",{"type":20,"tag":84,"props":6312,"children":6314},{"className":6313},[],[6315],{"type":25,"value":5113},{"type":25,"value":6317}," 不需要很长：",{"type":20,"tag":126,"props":6319,"children":6322},{"className":6320,"code":6321,"language":1805,"meta":8,"style":8},"language-md shiki shiki-themes github-dark","# Project Agent Instructions\n\n本项目使用 Clean ECC for Codex Level 2 模板。\n\n详细规则：`.codex\u002Fecc\u002F`\n可复用工作流：`.agents\u002Fskills\u002F`\n\n处理非简单任务前，按需阅读：\n\n- `.codex\u002Fecc\u002Fworkflow.md`\n- `.codex\u002Fecc\u002Frules.md`\n- `.codex\u002Fecc\u002Fsecurity.md`\n",[6323],{"type":20,"tag":84,"props":6324,"children":6325},{"__ignoreMap":8},[6326,6335,6342,6350,6357,6370,6383,6390,6398,6405,6419,6431],{"type":20,"tag":157,"props":6327,"children":6328},{"class":159,"line":160},[6329],{"type":20,"tag":157,"props":6330,"children":6332},{"style":6331},"--shiki-default:#79B8FF;--shiki-default-font-weight:bold",[6333],{"type":25,"value":6334},"# Project Agent Instructions\n",{"type":20,"tag":157,"props":6336,"children":6337},{"class":159,"line":169},[6338],{"type":20,"tag":157,"props":6339,"children":6340},{"emptyLinePlaceholder":173},[6341],{"type":25,"value":176},{"type":20,"tag":157,"props":6343,"children":6344},{"class":159,"line":179},[6345],{"type":20,"tag":157,"props":6346,"children":6347},{"style":892},[6348],{"type":25,"value":6349},"本项目使用 Clean ECC for Codex Level 2 模板。\n",{"type":20,"tag":157,"props":6351,"children":6352},{"class":159,"line":188},[6353],{"type":20,"tag":157,"props":6354,"children":6355},{"emptyLinePlaceholder":173},[6356],{"type":25,"value":176},{"type":20,"tag":157,"props":6358,"children":6359},{"class":159,"line":196},[6360,6365],{"type":20,"tag":157,"props":6361,"children":6362},{"style":892},[6363],{"type":25,"value":6364},"详细规则：",{"type":20,"tag":157,"props":6366,"children":6367},{"style":260},[6368],{"type":25,"value":6369},"`.codex\u002Fecc\u002F`\n",{"type":20,"tag":157,"props":6371,"children":6372},{"class":159,"line":204},[6373,6378],{"type":20,"tag":157,"props":6374,"children":6375},{"style":892},[6376],{"type":25,"value":6377},"可复用工作流：",{"type":20,"tag":157,"props":6379,"children":6380},{"style":260},[6381],{"type":25,"value":6382},"`.agents\u002Fskills\u002F`\n",{"type":20,"tag":157,"props":6384,"children":6385},{"class":159,"line":213},[6386],{"type":20,"tag":157,"props":6387,"children":6388},{"emptyLinePlaceholder":173},[6389],{"type":25,"value":176},{"type":20,"tag":157,"props":6391,"children":6392},{"class":159,"line":222},[6393],{"type":20,"tag":157,"props":6394,"children":6395},{"style":892},[6396],{"type":25,"value":6397},"处理非简单任务前，按需阅读：\n",{"type":20,"tag":157,"props":6399,"children":6400},{"class":159,"line":440},[6401],{"type":20,"tag":157,"props":6402,"children":6403},{"emptyLinePlaceholder":173},[6404],{"type":25,"value":176},{"type":20,"tag":157,"props":6406,"children":6407},{"class":159,"line":448},[6408,6414],{"type":20,"tag":157,"props":6409,"children":6411},{"style":6410},"--shiki-default:#FFAB70",[6412],{"type":25,"value":6413},"-",{"type":20,"tag":157,"props":6415,"children":6416},{"style":260},[6417],{"type":25,"value":6418}," `.codex\u002Fecc\u002Fworkflow.md`\n",{"type":20,"tag":157,"props":6420,"children":6421},{"class":159,"line":456},[6422,6426],{"type":20,"tag":157,"props":6423,"children":6424},{"style":6410},[6425],{"type":25,"value":6413},{"type":20,"tag":157,"props":6427,"children":6428},{"style":260},[6429],{"type":25,"value":6430}," `.codex\u002Fecc\u002Frules.md`\n",{"type":20,"tag":157,"props":6432,"children":6433},{"class":159,"line":465},[6434,6438],{"type":20,"tag":157,"props":6435,"children":6436},{"style":6410},[6437],{"type":25,"value":6413},{"type":20,"tag":157,"props":6439,"children":6440},{"style":260},[6441],{"type":25,"value":6442}," `.codex\u002Fecc\u002Fsecurity.md`\n",{"type":20,"tag":21,"props":6444,"children":6445},{},[6446,6448,6453,6455,6460],{"type":25,"value":6447},"这个文件只做入口，不负责承载所有细节。长期规则放 ",{"type":20,"tag":84,"props":6449,"children":6451},{"className":6450},[],[6452],{"type":25,"value":5576},{"type":25,"value":6454},"，任务工作流放 ",{"type":20,"tag":84,"props":6456,"children":6458},{"className":6457},[],[6459],{"type":25,"value":5553},{"type":25,"value":110},{"type":20,"tag":21,"props":6462,"children":6463},{},[6464],{"type":25,"value":6465},"如果后面确实需要更强的自动化，再逐步加 MCP、subagents 或 Codex 原生 hooks。不要第一天就把所有东西打开。",{"type":20,"tag":5056,"props":6467,"children":6468},{},[],{"type":20,"tag":1863,"props":6470,"children":6472},{"id":6471},"什么时候用什么时候不用",[6473],{"type":25,"value":6474},"什么时候用，什么时候不用",{"type":20,"tag":21,"props":6476,"children":6477},{},[6478],{"type":25,"value":6479},"不是所有项目都需要这套东西。",{"type":20,"tag":21,"props":6481,"children":6482},{},[6483,6488],{"type":20,"tag":33,"props":6484,"children":6485},{},[6486],{"type":25,"value":6487},"不需要",{"type":25,"value":6489},"：改一个小脚本、写一次性 demo、生成一段 SQL。原生 Codex 足够，额外规则反而会增加负担。",{"type":20,"tag":21,"props":6491,"children":6492},{},[6493,6498,6500,6505],{"type":20,"tag":33,"props":6494,"children":6495},{},[6496],{"type":25,"value":6497},"可以试试 Level 1",{"type":25,"value":6499},"：项目很小，但你希望 Codex 每次都记住测试命令、代码风格和不要覆盖用户改动。一个短 ",{"type":20,"tag":84,"props":6501,"children":6503},{"className":6502},[],[6504],{"type":25,"value":5113},{"type":25,"value":6506}," 加两三个 skills 就够了。",{"type":20,"tag":21,"props":6508,"children":6509},{},[6510,6515],{"type":20,"tag":33,"props":6511,"children":6512},{},[6513],{"type":25,"value":6514},"适合上 Level 2",{"type":25,"value":6516},"：长期维护的项目，有测试、构建、部署流程；或者项目里同时有前端、后端、API、Docker、CI\u002FCD。这个阶段，Codex 的问题通常不是“不会写代码”，而是“收尾不稳定”。",{"type":20,"tag":21,"props":6518,"children":6519},{},[6520,6525],{"type":20,"tag":33,"props":6521,"children":6522},{},[6523],{"type":25,"value":6524},"再考虑 Full-ish",{"type":25,"value":6526},"：团队协作、多仓库、多工具集成、需要权限控制或审计。这个阶段再上 MCP、subagents、hooks 和安全扫描更合理。",{"type":20,"tag":5056,"props":6528,"children":6529},{},[],{"type":20,"tag":1863,"props":6531,"children":6533},{"id":6532},"一条建议不要贪多",[6534],{"type":25,"value":6535},"一条建议：不要贪多",{"type":20,"tag":21,"props":6537,"children":6538},{},[6539],{"type":25,"value":6540},"真正有效的不是规则数量，而是四件事：",{"type":20,"tag":44,"props":6542,"children":6543},{},[6544,6549,6554,6559],{"type":20,"tag":48,"props":6545,"children":6546},{},[6547],{"type":25,"value":6548},"规则是否会被读到",{"type":20,"tag":48,"props":6550,"children":6551},{},[6552],{"type":25,"value":6553},"skill 是否能被正确触发",{"type":20,"tag":48,"props":6555,"children":6556},{},[6557],{"type":25,"value":6558},"上下文是否干净",{"type":20,"tag":48,"props":6560,"children":6561},{},[6562],{"type":25,"value":6563},"验证动作是否真的发生",{"type":20,"tag":21,"props":6565,"children":6566},{},[6567],{"type":25,"value":6568},"我更推荐这条路线：",{"type":20,"tag":126,"props":6570,"children":6572},{"className":324,"code":6571,"language":25,"meta":8,"style":8},"Codex 原生能力\n  + 精简 AGENTS.md\n  + 项目本地 .codex\u002Fecc 规则\n  + 少量高价值 skills\n  + 需要时再加 MCP \u002F subagents \u002F hooks\n",[6573],{"type":20,"tag":84,"props":6574,"children":6575},{"__ignoreMap":8},[6576,6584,6592,6600,6608],{"type":20,"tag":157,"props":6577,"children":6578},{"class":159,"line":160},[6579],{"type":20,"tag":157,"props":6580,"children":6581},{},[6582],{"type":25,"value":6583},"Codex 原生能力\n",{"type":20,"tag":157,"props":6585,"children":6586},{"class":159,"line":169},[6587],{"type":20,"tag":157,"props":6588,"children":6589},{},[6590],{"type":25,"value":6591},"  + 精简 AGENTS.md\n",{"type":20,"tag":157,"props":6593,"children":6594},{"class":159,"line":179},[6595],{"type":20,"tag":157,"props":6596,"children":6597},{},[6598],{"type":25,"value":6599},"  + 项目本地 .codex\u002Fecc 规则\n",{"type":20,"tag":157,"props":6601,"children":6602},{"class":159,"line":188},[6603],{"type":20,"tag":157,"props":6604,"children":6605},{},[6606],{"type":25,"value":6607},"  + 少量高价值 skills\n",{"type":20,"tag":157,"props":6609,"children":6610},{"class":159,"line":196},[6611],{"type":20,"tag":157,"props":6612,"children":6613},{},[6614],{"type":25,"value":6615},"  + 需要时再加 MCP \u002F subagents \u002F hooks\n",{"type":20,"tag":21,"props":6617,"children":6618},{},[6619],{"type":25,"value":6620},"先让 Codex 在日常开发中变稳定。等你真的遇到“需要接入外部工具”“需要并行审查”“需要强制执行命令策略”的问题，再把更重的能力加进来。",{"type":20,"tag":21,"props":6622,"children":6623},{},[6624,6626],{"type":25,"value":6625},"这也是 Clean ECC for Codex 的核心取舍：",{"type":20,"tag":33,"props":6627,"children":6628},{},[6629],{"type":25,"value":6630},"先可靠，再复杂。",{"type":20,"tag":5056,"props":6632,"children":6633},{},[],{"type":20,"tag":1863,"props":6635,"children":6637},{"id":6636},"从聪明到可靠",[6638],{"type":25,"value":6636},{"type":20,"tag":21,"props":6640,"children":6641},{},[6642],{"type":25,"value":6643},"AI 编程工具写代码快，这是最容易让人兴奋的地方。",{"type":20,"tag":21,"props":6645,"children":6646},{},[6647],{"type":25,"value":6648},"但真正让人愿意长期用的，是可靠：知道什么时候先查资料，什么时候该写测试，改完要验证，安全敏感改动要小心，不要覆盖已有工作，任务结束时要汇报做了什么、验证了什么、还剩什么风险。",{"type":20,"tag":21,"props":6650,"children":6651},{},[6652],{"type":25,"value":6653},"ECC 对 Codex 的价值就在这里。",{"type":20,"tag":21,"props":6655,"children":6656},{},[6657],{"type":25,"value":6658},"不是把 Claude Code 全家桶塞给 Codex，而是把其中最有价值的工程习惯，重新组织成 Codex 原生能理解的形式。",{"type":20,"tag":21,"props":6660,"children":6661},{},[6662],{"type":25,"value":6663},"最终得到的不是一个更重的项目模板，而是一套更稳的工作方式：",{"type":20,"tag":6665,"props":6666,"children":6667},"blockquote",{},[6668],{"type":20,"tag":21,"props":6669,"children":6670},{},[6671],{"type":25,"value":6672},"用 Codex 的原生能力做底座，用 ECC 的工程化思想做增强。",{"type":20,"tag":5056,"props":6674,"children":6675},{},[],{"type":20,"tag":1863,"props":6677,"children":6679},{"id":6678},"项目地址",[6680],{"type":25,"value":6678},{"type":20,"tag":21,"props":6682,"children":6683},{},[6684],{"type":25,"value":6685},"我整理的 Level 1、Level 2、Level 3 模板放在这里，可以直接使用：",{"type":20,"tag":21,"props":6687,"children":6688},{},[6689],{"type":20,"tag":101,"props":6690,"children":6693},{"href":6691,"rel":6692},"https:\u002F\u002Fgitee.com\u002Fo_insist\u002Fecc-work-in-codex",[105],[6694],{"type":25,"value":6691},{"type":20,"tag":5056,"props":6696,"children":6697},{},[],{"type":20,"tag":1863,"props":6699,"children":6700},{"id":4926},[6701],{"type":25,"value":4926},{"type":20,"tag":44,"props":6703,"children":6704},{},[6705,6715,6725,6735,6745],{"type":20,"tag":48,"props":6706,"children":6707},{},[6708],{"type":20,"tag":101,"props":6709,"children":6712},{"href":6710,"rel":6711},"https:\u002F\u002Fdevelopers.openai.com\u002Fcodex\u002Fguides\u002Fagents-md",[105],[6713],{"type":25,"value":6714},"OpenAI Codex：AGENTS.md",{"type":20,"tag":48,"props":6716,"children":6717},{},[6718],{"type":20,"tag":101,"props":6719,"children":6722},{"href":6720,"rel":6721},"https:\u002F\u002Fdevelopers.openai.com\u002Fcodex\u002Fskills",[105],[6723],{"type":25,"value":6724},"OpenAI Codex：Agent Skills",{"type":20,"tag":48,"props":6726,"children":6727},{},[6728],{"type":20,"tag":101,"props":6729,"children":6732},{"href":6730,"rel":6731},"https:\u002F\u002Fdevelopers.openai.com\u002Fcodex\u002Fhooks",[105],[6733],{"type":25,"value":6734},"OpenAI Codex：Hooks",{"type":20,"tag":48,"props":6736,"children":6737},{},[6738],{"type":20,"tag":101,"props":6739,"children":6742},{"href":6740,"rel":6741},"https:\u002F\u002Fgithub.com\u002Fopenai\u002Fskills",[105],[6743],{"type":25,"value":6744},"openai\u002Fskills",{"type":20,"tag":48,"props":6746,"children":6747},{},[6748],{"type":20,"tag":101,"props":6749,"children":6752},{"href":6750,"rel":6751},"https:\u002F\u002Fgithub.com\u002Faffaan-m\u002Feverything-claude-code",[105],[6753],{"type":25,"value":5042},{"type":20,"tag":1785,"props":6755,"children":6756},{},[6757],{"type":25,"value":1789},{"title":8,"searchDepth":169,"depth":169,"links":6759},[6760,6761,6762,6763,6764,6765,6766,6767,6768,6769,6770],{"id":5061,"depth":169,"text":5064},{"id":5256,"depth":169,"text":5256},{"id":5363,"depth":169,"text":5363},{"id":5596,"depth":169,"text":5596},{"id":5754,"depth":169,"text":5757},{"id":6262,"depth":169,"text":6262},{"id":6471,"depth":169,"text":6474},{"id":6532,"depth":169,"text":6535},{"id":6636,"depth":169,"text":6636},{"id":6678,"depth":169,"text":6678},{"id":4926,"depth":169,"text":4926},"content:articles:ai:everything-claude-code在codex的应用.md","articles\u002Fai\u002Feverything-claude-code在codex的应用.md","articles\u002Fai\u002Feverything-claude-code在codex的应用",{"_path":6775,"_dir":6776,"_draft":7,"_partial":7,"_locale":8,"title":6777,"description":6778,"date":6779,"tags":6780,"body":6782,"_type":1800,"_id":10259,"_source":1802,"_file":10260,"_stem":10261,"_extension":1805},"\u002Farticles\u002Fdevops\u002Fdocker","devops","Docker 入门：从镜像、容器到项目部署","面向第一次接触 Docker 的学习笔记，梳理镜像、容器、Dockerfile、常用命令、Nginx 反向代理和服务器部署流程。","2026-05-15",[14,6781],"DevOps",{"type":17,"children":6783,"toc":10236},[6784,6789,6795,6800,6823,6833,6838,6843,6851,6857,6862,6867,6898,6903,6908,6919,6956,6961,6972,7008,7021,7026,7031,7051,7062,7073,7078,7106,7127,7133,7144,7149,7249,7254,7358,7363,7388,7408,7473,7478,7483,7595,7600,7612,7647,7667,7672,7741,7746,7818,7844,7849,7895,7900,7964,7969,7974,8002,8087,8099,8104,8385,8405,8411,8416,8421,8478,8497,8502,8555,8574,8579,8696,8701,8784,8832,8837,8927,8932,8937,9030,9050,9055,9157,9182,9187,9225,9250,9256,9277,9282,9305,9310,9351,9364,9437,9442,9448,9453,9466,9497,9502,9521,9552,9562,9582,9587,9592,9597,9611,9616,9855,9860,9865,9879,9884,9889,10042,10047,10052,10160,10165,10169,10232],{"type":20,"tag":21,"props":6785,"children":6786},{},[6787],{"type":25,"value":6788},"最近想弄个人博客，记录自己学习的过程，这就需要掌握一些部署的技能，如Docker、Nginx，这篇文章就是",{"type":20,"tag":1863,"props":6790,"children":6792},{"id":6791},"为什么需要-docker",[6793],{"type":25,"value":6794},"为什么需要 Docker",{"type":20,"tag":21,"props":6796,"children":6797},{},[6798],{"type":25,"value":6799},"很多人第一次部署项目时，遇到的不是代码问题，而是环境问题：",{"type":20,"tag":44,"props":6801,"children":6802},{},[6803,6808,6813,6818],{"type":20,"tag":48,"props":6804,"children":6805},{},[6806],{"type":25,"value":6807},"本地能跑，服务器跑不起来。",{"type":20,"tag":48,"props":6809,"children":6810},{},[6811],{"type":25,"value":6812},"A 项目需要 Node.js 18，B 项目需要 Node.js 20。",{"type":20,"tag":48,"props":6814,"children":6815},{},[6816],{"type":25,"value":6817},"换一台机器，又要重新安装运行环境、配置环境变量、调整启动脚本。",{"type":20,"tag":48,"props":6819,"children":6820},{},[6821],{"type":25,"value":6822},"项目越多，服务器上的依赖、日志、进程和端口越难管理。",{"type":20,"tag":21,"props":6824,"children":6825},{},[6826,6828],{"type":25,"value":6827},"Docker 要解决的核心问题是：",{"type":20,"tag":33,"props":6829,"children":6830},{},[6831],{"type":25,"value":6832},"把应用和它依赖的运行环境一起打包，让它在不同机器上用尽量一致的方式运行。",{"type":20,"tag":21,"props":6834,"children":6835},{},[6836],{"type":25,"value":6837},"可以把 Docker 理解成一种轻量的应用交付方式。它不会像传统虚拟机那样完整模拟一台操作系统，而是基于宿主机内核，把不同应用隔离在不同容器中。这样既能减少环境差异，又不会像虚拟机那样笨重。",{"type":20,"tag":21,"props":6839,"children":6840},{},[6841],{"type":25,"value":6842},"先记住一句话：",{"type":20,"tag":6665,"props":6844,"children":6845},{},[6846],{"type":20,"tag":21,"props":6847,"children":6848},{},[6849],{"type":25,"value":6850},"Docker 不是为了让代码变得神奇，而是为了让代码运行环境变得可复制、可迁移、可管理。",{"type":20,"tag":1863,"props":6852,"children":6854},{"id":6853},"没有-docker-和有-docker-的部署区别",[6855],{"type":25,"value":6856},"没有 Docker 和有 Docker 的部署区别",{"type":20,"tag":21,"props":6858,"children":6859},{},[6860],{"type":25,"value":6861},"假设服务器上要部署一个 Nuxt 应用，并且使用 Nginx 接收外部请求。",{"type":20,"tag":21,"props":6863,"children":6864},{},[6865],{"type":25,"value":6866},"没有 Docker 时，常见结构是：",{"type":20,"tag":126,"props":6868,"children":6870},{"className":324,"code":6869,"language":25,"meta":8,"style":8},"Linux 宿主机\n├─ Nginx（直接安装在宿主机）\n└─ Nuxt 应用（直接运行在宿主机）\n",[6871],{"type":20,"tag":84,"props":6872,"children":6873},{"__ignoreMap":8},[6874,6882,6890],{"type":20,"tag":157,"props":6875,"children":6876},{"class":159,"line":160},[6877],{"type":20,"tag":157,"props":6878,"children":6879},{},[6880],{"type":25,"value":6881},"Linux 宿主机\n",{"type":20,"tag":157,"props":6883,"children":6884},{"class":159,"line":169},[6885],{"type":20,"tag":157,"props":6886,"children":6887},{},[6888],{"type":25,"value":6889},"├─ Nginx（直接安装在宿主机）\n",{"type":20,"tag":157,"props":6891,"children":6892},{"class":159,"line":179},[6893],{"type":20,"tag":157,"props":6894,"children":6895},{},[6896],{"type":25,"value":6897},"└─ Nuxt 应用（直接运行在宿主机）\n",{"type":20,"tag":21,"props":6899,"children":6900},{},[6901],{"type":25,"value":6902},"这种方式当然能用，但服务器会逐渐变成一个\"手工配置现场\"：Node 版本、项目依赖、进程管理、日志目录、环境变量都散落在宿主机上。后续迁移、排查问题或部署新项目时，很容易出现环境不一致。",{"type":20,"tag":21,"props":6904,"children":6905},{},[6906],{"type":25,"value":6907},"使用 Docker 后，可以有两种常见做法。",{"type":20,"tag":21,"props":6909,"children":6910},{},[6911,6913,6918],{"type":25,"value":6912},"第一种是 ",{"type":20,"tag":33,"props":6914,"children":6915},{},[6916],{"type":25,"value":6917},"Nginx 不容器化，只把应用容器化",{"type":25,"value":146},{"type":20,"tag":126,"props":6920,"children":6922},{"className":324,"code":6921,"language":25,"meta":8,"style":8},"Linux 宿主机\n├─ Nginx（直接安装在宿主机）\n└─ Docker\n   └─ Nuxt 容器\n",[6923],{"type":20,"tag":84,"props":6924,"children":6925},{"__ignoreMap":8},[6926,6933,6940,6948],{"type":20,"tag":157,"props":6927,"children":6928},{"class":159,"line":160},[6929],{"type":20,"tag":157,"props":6930,"children":6931},{},[6932],{"type":25,"value":6881},{"type":20,"tag":157,"props":6934,"children":6935},{"class":159,"line":169},[6936],{"type":20,"tag":157,"props":6937,"children":6938},{},[6939],{"type":25,"value":6889},{"type":20,"tag":157,"props":6941,"children":6942},{"class":159,"line":179},[6943],{"type":20,"tag":157,"props":6944,"children":6945},{},[6946],{"type":25,"value":6947},"└─ Docker\n",{"type":20,"tag":157,"props":6949,"children":6950},{"class":159,"line":188},[6951],{"type":20,"tag":157,"props":6952,"children":6953},{},[6954],{"type":25,"value":6955},"   └─ Nuxt 容器\n",{"type":20,"tag":21,"props":6957,"children":6958},{},[6959],{"type":25,"value":6960},"这种方式适合刚开始使用 Docker 的阶段。Nginx 仍然由服务器直接管理，Docker 只负责应用本身，理解成本比较低。",{"type":20,"tag":21,"props":6962,"children":6963},{},[6964,6966,6971],{"type":25,"value":6965},"第二种是 ",{"type":20,"tag":33,"props":6967,"children":6968},{},[6969],{"type":25,"value":6970},"Nginx 和应用都容器化",{"type":25,"value":146},{"type":20,"tag":126,"props":6973,"children":6975},{"className":324,"code":6974,"language":25,"meta":8,"style":8},"Linux 宿主机\n└─ Docker\n   ├─ Nginx 容器\n   └─ Nuxt 容器\n",[6976],{"type":20,"tag":84,"props":6977,"children":6978},{"__ignoreMap":8},[6979,6986,6993,7001],{"type":20,"tag":157,"props":6980,"children":6981},{"class":159,"line":160},[6982],{"type":20,"tag":157,"props":6983,"children":6984},{},[6985],{"type":25,"value":6881},{"type":20,"tag":157,"props":6987,"children":6988},{"class":159,"line":169},[6989],{"type":20,"tag":157,"props":6990,"children":6991},{},[6992],{"type":25,"value":6947},{"type":20,"tag":157,"props":6994,"children":6995},{"class":159,"line":179},[6996],{"type":20,"tag":157,"props":6997,"children":6998},{},[6999],{"type":25,"value":7000},"   ├─ Nginx 容器\n",{"type":20,"tag":157,"props":7002,"children":7003},{"class":159,"line":188},[7004],{"type":20,"tag":157,"props":7005,"children":7006},{},[7007],{"type":25,"value":6955},{"type":20,"tag":21,"props":7009,"children":7010},{},[7011,7013,7019],{"type":25,"value":7012},"这种方式更统一，所有服务都由 Docker 管理，通常会配合 ",{"type":20,"tag":84,"props":7014,"children":7016},{"className":7015},[],[7017],{"type":25,"value":7018},"docker compose",{"type":25,"value":7020}," 使用。正式项目中，如果后面还要加入数据库、Redis、后端服务、AI 服务等，多容器编排会更方便。",{"type":20,"tag":21,"props":7022,"children":7023},{},[7024],{"type":25,"value":7025},"这两种方式没有绝对优劣。刚入门时，可以先让应用容器化；当项目服务变多、部署流程变复杂时，再把 Nginx、数据库、缓存等服务逐步纳入 Compose 管理。",{"type":20,"tag":1863,"props":7027,"children":7029},{"id":7028},"镜像和容器",[7030],{"type":25,"value":7028},{"type":20,"tag":21,"props":7032,"children":7033},{},[7034,7036,7042,7044,7050],{"type":25,"value":7035},"Docker 里最重要的两个概念是：",{"type":20,"tag":84,"props":7037,"children":7039},{"className":7038},[],[7040],{"type":25,"value":7041},"镜像",{"type":25,"value":7043}," 和 ",{"type":20,"tag":84,"props":7045,"children":7047},{"className":7046},[],[7048],{"type":25,"value":7049},"容器",{"type":25,"value":110},{"type":20,"tag":21,"props":7052,"children":7053},{},[7054,7060],{"type":20,"tag":84,"props":7055,"children":7057},{"className":7056},[],[7058],{"type":25,"value":7059},"镜像 Image",{"type":25,"value":7061}," 是静态的构建产物。它描述了应用运行需要的文件、依赖、环境和默认启动方式。可以把镜像类比成\"软件安装包\"，也可以类比成面向对象里的\"类\"。",{"type":20,"tag":21,"props":7063,"children":7064},{},[7065,7071],{"type":20,"tag":84,"props":7066,"children":7068},{"className":7067},[],[7069],{"type":25,"value":7070},"容器 Container",{"type":25,"value":7072}," 是镜像运行后的实例。它是动态的，是一个被隔离起来的运行环境。可以把容器类比成\"安装并启动后的软件\"，也可以类比成面向对象里的\"对象实例\"。",{"type":20,"tag":21,"props":7074,"children":7075},{},[7076],{"type":25,"value":7077},"简单说：",{"type":20,"tag":44,"props":7079,"children":7080},{},[7081,7086,7091,7096,7101],{"type":20,"tag":48,"props":7082,"children":7083},{},[7084],{"type":25,"value":7085},"镜像是构建结果。",{"type":20,"tag":48,"props":7087,"children":7088},{},[7089],{"type":25,"value":7090},"容器是镜像运行后的实例。",{"type":20,"tag":48,"props":7092,"children":7093},{},[7094],{"type":25,"value":7095},"一个镜像可以启动多个容器。",{"type":20,"tag":48,"props":7097,"children":7098},{},[7099],{"type":25,"value":7100},"删除容器不会自动删除镜像。",{"type":20,"tag":48,"props":7102,"children":7103},{},[7104],{"type":25,"value":7105},"删除镜像前，一般要先停止并删除依赖它的容器。",{"type":20,"tag":21,"props":7107,"children":7108},{},[7109,7111,7117,7119,7125],{"type":25,"value":7110},"比如你构建出一个 ",{"type":20,"tag":84,"props":7112,"children":7114},{"className":7113},[],[7115],{"type":25,"value":7116},"my-blog:v1",{"type":25,"value":7118}," 镜像，就可以用它启动一个博客容器。将来升级版本时，可以重新构建 ",{"type":20,"tag":84,"props":7120,"children":7122},{"className":7121},[],[7123],{"type":25,"value":7124},"my-blog:v2",{"type":25,"value":7126},"，再用新镜像启动新容器。",{"type":20,"tag":1863,"props":7128,"children":7130},{"id":7129},"dockerfile-是什么",[7131],{"type":25,"value":7132},"Dockerfile 是什么",{"type":20,"tag":21,"props":7134,"children":7135},{},[7136,7142],{"type":20,"tag":84,"props":7137,"children":7139},{"className":7138},[],[7140],{"type":25,"value":7141},"Dockerfile",{"type":25,"value":7143}," 是写给 Docker 的构建说明书。它告诉 Docker 如何一步一步生成镜像：使用哪个基础镜像、进入哪个工作目录、复制哪些文件、安装哪些依赖，以及容器启动时执行什么命令。",{"type":20,"tag":21,"props":7145,"children":7146},{},[7147],{"type":25,"value":7148},"下面是一个入门版 Node.js 项目 Dockerfile：",{"type":20,"tag":126,"props":7150,"children":7154},{"className":7151,"code":7152,"language":7153,"meta":8,"style":8},"language-dockerfile shiki shiki-themes github-dark","FROM node:20-alpine\n\nWORKDIR \u002Fapp\n\nCOPY package*.json .\u002F\nRUN npm ci --omit=dev\n\nCOPY . .\n\nEXPOSE 3000\n\nCMD [\"npm\", \"start\"]\n","dockerfile",[7155],{"type":20,"tag":84,"props":7156,"children":7157},{"__ignoreMap":8},[7158,7166,7173,7181,7188,7196,7204,7211,7219,7226,7234,7241],{"type":20,"tag":157,"props":7159,"children":7160},{"class":159,"line":160},[7161],{"type":20,"tag":157,"props":7162,"children":7163},{},[7164],{"type":25,"value":7165},"FROM node:20-alpine\n",{"type":20,"tag":157,"props":7167,"children":7168},{"class":159,"line":169},[7169],{"type":20,"tag":157,"props":7170,"children":7171},{"emptyLinePlaceholder":173},[7172],{"type":25,"value":176},{"type":20,"tag":157,"props":7174,"children":7175},{"class":159,"line":179},[7176],{"type":20,"tag":157,"props":7177,"children":7178},{},[7179],{"type":25,"value":7180},"WORKDIR \u002Fapp\n",{"type":20,"tag":157,"props":7182,"children":7183},{"class":159,"line":188},[7184],{"type":20,"tag":157,"props":7185,"children":7186},{"emptyLinePlaceholder":173},[7187],{"type":25,"value":176},{"type":20,"tag":157,"props":7189,"children":7190},{"class":159,"line":196},[7191],{"type":20,"tag":157,"props":7192,"children":7193},{},[7194],{"type":25,"value":7195},"COPY package*.json .\u002F\n",{"type":20,"tag":157,"props":7197,"children":7198},{"class":159,"line":204},[7199],{"type":20,"tag":157,"props":7200,"children":7201},{},[7202],{"type":25,"value":7203},"RUN npm ci --omit=dev\n",{"type":20,"tag":157,"props":7205,"children":7206},{"class":159,"line":213},[7207],{"type":20,"tag":157,"props":7208,"children":7209},{"emptyLinePlaceholder":173},[7210],{"type":25,"value":176},{"type":20,"tag":157,"props":7212,"children":7213},{"class":159,"line":222},[7214],{"type":20,"tag":157,"props":7215,"children":7216},{},[7217],{"type":25,"value":7218},"COPY . .\n",{"type":20,"tag":157,"props":7220,"children":7221},{"class":159,"line":440},[7222],{"type":20,"tag":157,"props":7223,"children":7224},{"emptyLinePlaceholder":173},[7225],{"type":25,"value":176},{"type":20,"tag":157,"props":7227,"children":7228},{"class":159,"line":448},[7229],{"type":20,"tag":157,"props":7230,"children":7231},{},[7232],{"type":25,"value":7233},"EXPOSE 3000\n",{"type":20,"tag":157,"props":7235,"children":7236},{"class":159,"line":456},[7237],{"type":20,"tag":157,"props":7238,"children":7239},{"emptyLinePlaceholder":173},[7240],{"type":25,"value":176},{"type":20,"tag":157,"props":7242,"children":7243},{"class":159,"line":465},[7244],{"type":20,"tag":157,"props":7245,"children":7246},{},[7247],{"type":25,"value":7248},"CMD [\"npm\", \"start\"]\n",{"type":20,"tag":21,"props":7250,"children":7251},{},[7252],{"type":25,"value":7253},"这里每一行都对应一个构建或运行阶段的动作：",{"type":20,"tag":44,"props":7255,"children":7256},{},[7257,7268,7279,7290,7317,7328,7347],{"type":20,"tag":48,"props":7258,"children":7259},{},[7260,7266],{"type":20,"tag":84,"props":7261,"children":7263},{"className":7262},[],[7264],{"type":25,"value":7265},"FROM node:20-alpine",{"type":25,"value":7267},"：选择一个已有的基础镜像，里面已经包含 Node.js 运行环境。",{"type":20,"tag":48,"props":7269,"children":7270},{},[7271,7277],{"type":20,"tag":84,"props":7272,"children":7274},{"className":7273},[],[7275],{"type":25,"value":7276},"WORKDIR \u002Fapp",{"type":25,"value":7278},"：指定容器内部的工作目录，后续命令默认都在这里执行。",{"type":20,"tag":48,"props":7280,"children":7281},{},[7282,7288],{"type":20,"tag":84,"props":7283,"children":7285},{"className":7284},[],[7286],{"type":25,"value":7287},"COPY package*.json .\u002F",{"type":25,"value":7289},"：先复制依赖描述文件，方便 Docker 利用构建缓存。",{"type":20,"tag":48,"props":7291,"children":7292},{},[7293,7299,7301,7307,7309,7315],{"type":20,"tag":84,"props":7294,"children":7296},{"className":7295},[],[7297],{"type":25,"value":7298},"RUN npm ci --omit=dev",{"type":25,"value":7300},"：在构建镜像时安装生产依赖。相比 ",{"type":20,"tag":84,"props":7302,"children":7304},{"className":7303},[],[7305],{"type":25,"value":7306},"npm install",{"type":25,"value":7308},"，",{"type":20,"tag":84,"props":7310,"children":7312},{"className":7311},[],[7313],{"type":25,"value":7314},"npm ci",{"type":25,"value":7316}," 更适合根据锁文件做可重复安装。",{"type":20,"tag":48,"props":7318,"children":7319},{},[7320,7326],{"type":20,"tag":84,"props":7321,"children":7323},{"className":7322},[],[7324],{"type":25,"value":7325},"COPY . .",{"type":25,"value":7327},"：把项目其他文件复制到镜像中。",{"type":20,"tag":48,"props":7329,"children":7330},{},[7331,7337,7339,7345],{"type":20,"tag":84,"props":7332,"children":7334},{"className":7333},[],[7335],{"type":25,"value":7336},"EXPOSE 3000",{"type":25,"value":7338},"：声明应用在容器内监听 ",{"type":20,"tag":84,"props":7340,"children":7342},{"className":7341},[],[7343],{"type":25,"value":7344},"3000",{"type":25,"value":7346}," 端口。注意它只是元信息，不会自动把端口暴露到宿主机。",{"type":20,"tag":48,"props":7348,"children":7349},{},[7350,7356],{"type":20,"tag":84,"props":7351,"children":7353},{"className":7352},[],[7354],{"type":25,"value":7355},"CMD [\"npm\", \"start\"]",{"type":25,"value":7357},"：容器启动后默认执行的命令。",{"type":20,"tag":21,"props":7359,"children":7360},{},[7361],{"type":25,"value":7362},"这里有两个时间点很容易混淆：",{"type":20,"tag":44,"props":7364,"children":7365},{},[7366,7377],{"type":20,"tag":48,"props":7367,"children":7368},{},[7369,7375],{"type":20,"tag":84,"props":7370,"children":7372},{"className":7371},[],[7373],{"type":25,"value":7374},"RUN",{"type":25,"value":7376}," 发生在构建镜像时，比如安装依赖、编译项目。",{"type":20,"tag":48,"props":7378,"children":7379},{},[7380,7386],{"type":20,"tag":84,"props":7381,"children":7383},{"className":7382},[],[7384],{"type":25,"value":7385},"CMD",{"type":25,"value":7387}," 发生在容器启动时，比如启动 Web 服务。",{"type":20,"tag":21,"props":7389,"children":7390},{},[7391,7393,7399,7401,7406],{"type":25,"value":7392},"真实项目中，还应该配合 ",{"type":20,"tag":84,"props":7394,"children":7396},{"className":7395},[],[7397],{"type":25,"value":7398},".dockerignore",{"type":25,"value":7400}," 使用，避免把 ",{"type":20,"tag":84,"props":7402,"children":7404},{"className":7403},[],[7405],{"type":25,"value":2013},{"type":25,"value":7407},"、构建产物、日志、环境变量文件等内容复制进镜像：",{"type":20,"tag":126,"props":7409,"children":7413},{"className":7410,"code":7411,"language":7412,"meta":8,"style":8},"language-gitignore shiki shiki-themes github-dark","node_modules\ndist\n.nuxt\n.output\n.env\n*.log\ncoverage\n","gitignore",[7414],{"type":20,"tag":84,"props":7415,"children":7416},{"__ignoreMap":8},[7417,7425,7433,7441,7449,7457,7465],{"type":20,"tag":157,"props":7418,"children":7419},{"class":159,"line":160},[7420],{"type":20,"tag":157,"props":7421,"children":7422},{},[7423],{"type":25,"value":7424},"node_modules\n",{"type":20,"tag":157,"props":7426,"children":7427},{"class":159,"line":169},[7428],{"type":20,"tag":157,"props":7429,"children":7430},{},[7431],{"type":25,"value":7432},"dist\n",{"type":20,"tag":157,"props":7434,"children":7435},{"class":159,"line":179},[7436],{"type":20,"tag":157,"props":7437,"children":7438},{},[7439],{"type":25,"value":7440},".nuxt\n",{"type":20,"tag":157,"props":7442,"children":7443},{"class":159,"line":188},[7444],{"type":20,"tag":157,"props":7445,"children":7446},{},[7447],{"type":25,"value":7448},".output\n",{"type":20,"tag":157,"props":7450,"children":7451},{"class":159,"line":196},[7452],{"type":20,"tag":157,"props":7453,"children":7454},{},[7455],{"type":25,"value":7456},".env\n",{"type":20,"tag":157,"props":7458,"children":7459},{"class":159,"line":204},[7460],{"type":20,"tag":157,"props":7461,"children":7462},{},[7463],{"type":25,"value":7464},"*.log\n",{"type":20,"tag":157,"props":7466,"children":7467},{"class":159,"line":213},[7468],{"type":20,"tag":157,"props":7469,"children":7470},{},[7471],{"type":25,"value":7472},"coverage\n",{"type":20,"tag":21,"props":7474,"children":7475},{},[7476],{"type":25,"value":7477},"如果项目需要先构建再运行，例如 Nuxt、Next.js、Vue、React 等前端项目，通常还会使用多阶段构建：第一阶段负责安装依赖和打包，第二阶段只保留运行所需文件。这样可以减少最终镜像体积，也能让镜像更干净。",{"type":20,"tag":21,"props":7479,"children":7480},{},[7481],{"type":25,"value":7482},"下面是一个多阶段构建的例子：",{"type":20,"tag":126,"props":7484,"children":7486},{"className":7151,"code":7485,"language":7153,"meta":8,"style":8},"# 阶段一：构建\nFROM node:20-alpine AS builder\nWORKDIR \u002Fapp\nCOPY package*.json .\u002F\nRUN npm ci\nCOPY . .\nRUN npm run build\n\n# 阶段二：运行\nFROM node:20-alpine\nWORKDIR \u002Fapp\nCOPY --from=builder \u002Fapp\u002F.output .\u002F\nEXPOSE 3000\nCMD [\"node\", \".output\u002Fserver\u002Findex.mjs\"]\n",[7487],{"type":20,"tag":84,"props":7488,"children":7489},{"__ignoreMap":8},[7490,7498,7506,7513,7520,7528,7535,7543,7550,7558,7565,7572,7580,7587],{"type":20,"tag":157,"props":7491,"children":7492},{"class":159,"line":160},[7493],{"type":20,"tag":157,"props":7494,"children":7495},{},[7496],{"type":25,"value":7497},"# 阶段一：构建\n",{"type":20,"tag":157,"props":7499,"children":7500},{"class":159,"line":169},[7501],{"type":20,"tag":157,"props":7502,"children":7503},{},[7504],{"type":25,"value":7505},"FROM node:20-alpine AS builder\n",{"type":20,"tag":157,"props":7507,"children":7508},{"class":159,"line":179},[7509],{"type":20,"tag":157,"props":7510,"children":7511},{},[7512],{"type":25,"value":7180},{"type":20,"tag":157,"props":7514,"children":7515},{"class":159,"line":188},[7516],{"type":20,"tag":157,"props":7517,"children":7518},{},[7519],{"type":25,"value":7195},{"type":20,"tag":157,"props":7521,"children":7522},{"class":159,"line":196},[7523],{"type":20,"tag":157,"props":7524,"children":7525},{},[7526],{"type":25,"value":7527},"RUN npm ci\n",{"type":20,"tag":157,"props":7529,"children":7530},{"class":159,"line":204},[7531],{"type":20,"tag":157,"props":7532,"children":7533},{},[7534],{"type":25,"value":7218},{"type":20,"tag":157,"props":7536,"children":7537},{"class":159,"line":213},[7538],{"type":20,"tag":157,"props":7539,"children":7540},{},[7541],{"type":25,"value":7542},"RUN npm run build\n",{"type":20,"tag":157,"props":7544,"children":7545},{"class":159,"line":222},[7546],{"type":20,"tag":157,"props":7547,"children":7548},{"emptyLinePlaceholder":173},[7549],{"type":25,"value":176},{"type":20,"tag":157,"props":7551,"children":7552},{"class":159,"line":440},[7553],{"type":20,"tag":157,"props":7554,"children":7555},{},[7556],{"type":25,"value":7557},"# 阶段二：运行\n",{"type":20,"tag":157,"props":7559,"children":7560},{"class":159,"line":448},[7561],{"type":20,"tag":157,"props":7562,"children":7563},{},[7564],{"type":25,"value":7165},{"type":20,"tag":157,"props":7566,"children":7567},{"class":159,"line":456},[7568],{"type":20,"tag":157,"props":7569,"children":7570},{},[7571],{"type":25,"value":7180},{"type":20,"tag":157,"props":7573,"children":7574},{"class":159,"line":465},[7575],{"type":20,"tag":157,"props":7576,"children":7577},{},[7578],{"type":25,"value":7579},"COPY --from=builder \u002Fapp\u002F.output .\u002F\n",{"type":20,"tag":157,"props":7581,"children":7582},{"class":159,"line":474},[7583],{"type":20,"tag":157,"props":7584,"children":7585},{},[7586],{"type":25,"value":7233},{"type":20,"tag":157,"props":7588,"children":7589},{"class":159,"line":483},[7590],{"type":20,"tag":157,"props":7591,"children":7592},{},[7593],{"type":25,"value":7594},"CMD [\"node\", \".output\u002Fserver\u002Findex.mjs\"]\n",{"type":20,"tag":1863,"props":7596,"children":7598},{"id":7597},"构建和运行一个镜像",[7599],{"type":25,"value":7597},{"type":20,"tag":21,"props":7601,"children":7602},{},[7603,7605,7610],{"type":25,"value":7604},"假设当前目录下已经有 ",{"type":20,"tag":84,"props":7606,"children":7608},{"className":7607},[],[7609],{"type":25,"value":7141},{"type":25,"value":7611},"，可以执行：",{"type":20,"tag":126,"props":7613,"children":7615},{"className":238,"code":7614,"language":237,"meta":8,"style":8},"docker build -t test-docker .\n",[7616],{"type":20,"tag":84,"props":7617,"children":7618},{"__ignoreMap":8},[7619],{"type":20,"tag":157,"props":7620,"children":7621},{"class":159,"line":160},[7622,7627,7632,7637,7642],{"type":20,"tag":157,"props":7623,"children":7624},{"style":248},[7625],{"type":25,"value":7626},"docker",{"type":20,"tag":157,"props":7628,"children":7629},{"style":254},[7630],{"type":25,"value":7631}," build",{"type":20,"tag":157,"props":7633,"children":7634},{"style":260},[7635],{"type":25,"value":7636}," -t",{"type":20,"tag":157,"props":7638,"children":7639},{"style":254},[7640],{"type":25,"value":7641}," test-docker",{"type":20,"tag":157,"props":7643,"children":7644},{"style":254},[7645],{"type":25,"value":7646}," .\n",{"type":20,"tag":21,"props":7648,"children":7649},{},[7650,7652,7657,7659,7665],{"type":25,"value":7651},"这条命令表示：使用当前目录的 ",{"type":20,"tag":84,"props":7653,"children":7655},{"className":7654},[],[7656],{"type":25,"value":7141},{"type":25,"value":7658}," 和项目文件构建一个名为 ",{"type":20,"tag":84,"props":7660,"children":7662},{"className":7661},[],[7663],{"type":25,"value":7664},"test-docker",{"type":25,"value":7666}," 的镜像。",{"type":20,"tag":21,"props":7668,"children":7669},{},[7670],{"type":25,"value":7671},"构建完成后，可以启动容器：",{"type":20,"tag":126,"props":7673,"children":7675},{"className":238,"code":7674,"language":237,"meta":8,"style":8},"docker run -d \\\n  --name test-docker \\\n  -p 80:3000 \\\n  test-docker\n",[7676],{"type":20,"tag":84,"props":7677,"children":7678},{"__ignoreMap":8},[7679,7700,7716,7733],{"type":20,"tag":157,"props":7680,"children":7681},{"class":159,"line":160},[7682,7686,7690,7695],{"type":20,"tag":157,"props":7683,"children":7684},{"style":248},[7685],{"type":25,"value":7626},{"type":20,"tag":157,"props":7687,"children":7688},{"style":254},[7689],{"type":25,"value":2119},{"type":20,"tag":157,"props":7691,"children":7692},{"style":260},[7693],{"type":25,"value":7694}," -d",{"type":20,"tag":157,"props":7696,"children":7697},{"style":260},[7698],{"type":25,"value":7699}," \\\n",{"type":20,"tag":157,"props":7701,"children":7702},{"class":159,"line":169},[7703,7708,7712],{"type":20,"tag":157,"props":7704,"children":7705},{"style":260},[7706],{"type":25,"value":7707},"  --name",{"type":20,"tag":157,"props":7709,"children":7710},{"style":254},[7711],{"type":25,"value":7641},{"type":20,"tag":157,"props":7713,"children":7714},{"style":260},[7715],{"type":25,"value":7699},{"type":20,"tag":157,"props":7717,"children":7718},{"class":159,"line":179},[7719,7724,7729],{"type":20,"tag":157,"props":7720,"children":7721},{"style":260},[7722],{"type":25,"value":7723},"  -p",{"type":20,"tag":157,"props":7725,"children":7726},{"style":254},[7727],{"type":25,"value":7728}," 80:3000",{"type":20,"tag":157,"props":7730,"children":7731},{"style":260},[7732],{"type":25,"value":7699},{"type":20,"tag":157,"props":7734,"children":7735},{"class":159,"line":188},[7736],{"type":20,"tag":157,"props":7737,"children":7738},{"style":254},[7739],{"type":25,"value":7740},"  test-docker\n",{"type":20,"tag":21,"props":7742,"children":7743},{},[7744],{"type":25,"value":7745},"这条命令的含义是：",{"type":20,"tag":44,"props":7747,"children":7748},{},[7749,7760,7771,7782,7808],{"type":20,"tag":48,"props":7750,"children":7751},{},[7752,7758],{"type":20,"tag":84,"props":7753,"children":7755},{"className":7754},[],[7756],{"type":25,"value":7757},"docker run",{"type":25,"value":7759},"：根据镜像创建并启动一个容器。",{"type":20,"tag":48,"props":7761,"children":7762},{},[7763,7769],{"type":20,"tag":84,"props":7764,"children":7766},{"className":7765},[],[7767],{"type":25,"value":7768},"-d",{"type":25,"value":7770},"：让容器在后台运行。",{"type":20,"tag":48,"props":7772,"children":7773},{},[7774,7780],{"type":20,"tag":84,"props":7775,"children":7777},{"className":7776},[],[7778],{"type":25,"value":7779},"--name test-docker",{"type":25,"value":7781},"：给容器起一个明确的名字，后续可以直接用名字操作。",{"type":20,"tag":48,"props":7783,"children":7784},{},[7785,7791,7793,7799,7801,7806],{"type":20,"tag":84,"props":7786,"children":7788},{"className":7787},[],[7789],{"type":25,"value":7790},"-p 80:3000",{"type":25,"value":7792},"：把宿主机的 ",{"type":20,"tag":84,"props":7794,"children":7796},{"className":7795},[],[7797],{"type":25,"value":7798},"80",{"type":25,"value":7800}," 端口映射到容器内部的 ",{"type":20,"tag":84,"props":7802,"children":7804},{"className":7803},[],[7805],{"type":25,"value":7344},{"type":25,"value":7807}," 端口。",{"type":20,"tag":48,"props":7809,"children":7810},{},[7811,7816],{"type":20,"tag":84,"props":7812,"children":7814},{"className":7813},[],[7815],{"type":25,"value":7664},{"type":25,"value":7817},"：要运行的镜像名。",{"type":20,"tag":21,"props":7819,"children":7820},{},[7821,7823,7829,7831,7836,7838,7843],{"type":25,"value":7822},"端口映射是初学 Docker 时最容易混淆的地方。",{"type":20,"tag":84,"props":7824,"children":7826},{"className":7825},[],[7827],{"type":25,"value":7828},"80:3000",{"type":25,"value":7830}," 的左边是宿主机端口，右边是容器端口。访问服务器的 ",{"type":20,"tag":84,"props":7832,"children":7834},{"className":7833},[],[7835],{"type":25,"value":7798},{"type":25,"value":7837}," 端口时，流量会被转发到容器内部的 ",{"type":20,"tag":84,"props":7839,"children":7841},{"className":7840},[],[7842],{"type":25,"value":7344},{"type":25,"value":7807},{"type":20,"tag":21,"props":7845,"children":7846},{},[7847],{"type":25,"value":7848},"也可以这样理解：",{"type":20,"tag":126,"props":7850,"children":7852},{"className":324,"code":7851,"language":25,"meta":8,"style":8},"浏览器访问服务器 80 端口\n        ↓\nDocker 转发到容器 3000 端口\n        ↓\n容器里的应用处理请求\n",[7853],{"type":20,"tag":84,"props":7854,"children":7855},{"__ignoreMap":8},[7856,7864,7872,7880,7887],{"type":20,"tag":157,"props":7857,"children":7858},{"class":159,"line":160},[7859],{"type":20,"tag":157,"props":7860,"children":7861},{},[7862],{"type":25,"value":7863},"浏览器访问服务器 80 端口\n",{"type":20,"tag":157,"props":7865,"children":7866},{"class":159,"line":169},[7867],{"type":20,"tag":157,"props":7868,"children":7869},{},[7870],{"type":25,"value":7871},"        ↓\n",{"type":20,"tag":157,"props":7873,"children":7874},{"class":159,"line":179},[7875],{"type":20,"tag":157,"props":7876,"children":7877},{},[7878],{"type":25,"value":7879},"Docker 转发到容器 3000 端口\n",{"type":20,"tag":157,"props":7881,"children":7882},{"class":159,"line":188},[7883],{"type":20,"tag":157,"props":7884,"children":7885},{},[7886],{"type":25,"value":7871},{"type":20,"tag":157,"props":7888,"children":7889},{"class":159,"line":196},[7890],{"type":20,"tag":157,"props":7891,"children":7892},{},[7893],{"type":25,"value":7894},"容器里的应用处理请求\n",{"type":20,"tag":21,"props":7896,"children":7897},{},[7898],{"type":25,"value":7899},"需要注意：默认情况下，发布端口可能会让外部网络访问到该服务。生产环境里，不要随手把数据库、Redis、内部管理服务直接映射到公网端口。如果只是想让本机 Nginx 反向代理访问应用，可以绑定到本地地址：",{"type":20,"tag":126,"props":7901,"children":7903},{"className":238,"code":7902,"language":237,"meta":8,"style":8},"docker run -d \\\n  --name test-docker \\\n  -p 127.0.0.1:3000:3000 \\\n  test-docker\n",[7904],{"type":20,"tag":84,"props":7905,"children":7906},{"__ignoreMap":8},[7907,7926,7941,7957],{"type":20,"tag":157,"props":7908,"children":7909},{"class":159,"line":160},[7910,7914,7918,7922],{"type":20,"tag":157,"props":7911,"children":7912},{"style":248},[7913],{"type":25,"value":7626},{"type":20,"tag":157,"props":7915,"children":7916},{"style":254},[7917],{"type":25,"value":2119},{"type":20,"tag":157,"props":7919,"children":7920},{"style":260},[7921],{"type":25,"value":7694},{"type":20,"tag":157,"props":7923,"children":7924},{"style":260},[7925],{"type":25,"value":7699},{"type":20,"tag":157,"props":7927,"children":7928},{"class":159,"line":169},[7929,7933,7937],{"type":20,"tag":157,"props":7930,"children":7931},{"style":260},[7932],{"type":25,"value":7707},{"type":20,"tag":157,"props":7934,"children":7935},{"style":254},[7936],{"type":25,"value":7641},{"type":20,"tag":157,"props":7938,"children":7939},{"style":260},[7940],{"type":25,"value":7699},{"type":20,"tag":157,"props":7942,"children":7943},{"class":159,"line":179},[7944,7948,7953],{"type":20,"tag":157,"props":7945,"children":7946},{"style":260},[7947],{"type":25,"value":7723},{"type":20,"tag":157,"props":7949,"children":7950},{"style":254},[7951],{"type":25,"value":7952}," 127.0.0.1:3000:3000",{"type":20,"tag":157,"props":7954,"children":7955},{"style":260},[7956],{"type":25,"value":7699},{"type":20,"tag":157,"props":7958,"children":7959},{"class":159,"line":188},[7960],{"type":20,"tag":157,"props":7961,"children":7962},{"style":254},[7963],{"type":25,"value":7740},{"type":20,"tag":21,"props":7965,"children":7966},{},[7967],{"type":25,"value":7968},"这样外部用户不能直接访问容器端口，而是由宿主机上的 Nginx 统一接收请求后再转发。",{"type":20,"tag":1863,"props":7970,"children":7972},{"id":7971},"一个更完整的项目结构",[7973],{"type":25,"value":7971},{"type":20,"tag":21,"props":7975,"children":7976},{},[7977,7979,7984,7986,7992,7994,8000],{"type":25,"value":7978},"如果项目里有前端、后端、AI 服务，就可以给每个服务写自己的 ",{"type":20,"tag":84,"props":7980,"children":7982},{"className":7981},[],[7983],{"type":25,"value":7141},{"type":25,"value":7985},"，再用 ",{"type":20,"tag":84,"props":7987,"children":7989},{"className":7988},[],[7990],{"type":25,"value":7991},"compose.yaml",{"type":25,"value":7993}," 或 ",{"type":20,"tag":84,"props":7995,"children":7997},{"className":7996},[],[7998],{"type":25,"value":7999},"docker-compose.yml",{"type":25,"value":8001}," 统一管理。",{"type":20,"tag":126,"props":8003,"children":8005},{"className":324,"code":8004,"language":25,"meta":8,"style":8},"ai-drug-discovery-workbench\u002F\n├─ compose.yaml\n├─ backend\u002F\n│  └─ Dockerfile\n├─ ai-service\u002F\n│  └─ Dockerfile\n├─ frontend\u002F\n│  └─ Dockerfile\n└─ nginx\u002F\n   └─ default.conf\n",[8006],{"type":20,"tag":84,"props":8007,"children":8008},{"__ignoreMap":8},[8009,8017,8025,8033,8041,8049,8056,8064,8071,8079],{"type":20,"tag":157,"props":8010,"children":8011},{"class":159,"line":160},[8012],{"type":20,"tag":157,"props":8013,"children":8014},{},[8015],{"type":25,"value":8016},"ai-drug-discovery-workbench\u002F\n",{"type":20,"tag":157,"props":8018,"children":8019},{"class":159,"line":169},[8020],{"type":20,"tag":157,"props":8021,"children":8022},{},[8023],{"type":25,"value":8024},"├─ compose.yaml\n",{"type":20,"tag":157,"props":8026,"children":8027},{"class":159,"line":179},[8028],{"type":20,"tag":157,"props":8029,"children":8030},{},[8031],{"type":25,"value":8032},"├─ backend\u002F\n",{"type":20,"tag":157,"props":8034,"children":8035},{"class":159,"line":188},[8036],{"type":20,"tag":157,"props":8037,"children":8038},{},[8039],{"type":25,"value":8040},"│  └─ Dockerfile\n",{"type":20,"tag":157,"props":8042,"children":8043},{"class":159,"line":196},[8044],{"type":20,"tag":157,"props":8045,"children":8046},{},[8047],{"type":25,"value":8048},"├─ ai-service\u002F\n",{"type":20,"tag":157,"props":8050,"children":8051},{"class":159,"line":204},[8052],{"type":20,"tag":157,"props":8053,"children":8054},{},[8055],{"type":25,"value":8040},{"type":20,"tag":157,"props":8057,"children":8058},{"class":159,"line":213},[8059],{"type":20,"tag":157,"props":8060,"children":8061},{},[8062],{"type":25,"value":8063},"├─ frontend\u002F\n",{"type":20,"tag":157,"props":8065,"children":8066},{"class":159,"line":222},[8067],{"type":20,"tag":157,"props":8068,"children":8069},{},[8070],{"type":25,"value":8040},{"type":20,"tag":157,"props":8072,"children":8073},{"class":159,"line":440},[8074],{"type":20,"tag":157,"props":8075,"children":8076},{},[8077],{"type":25,"value":8078},"└─ nginx\u002F\n",{"type":20,"tag":157,"props":8080,"children":8081},{"class":159,"line":448},[8082],{"type":20,"tag":157,"props":8083,"children":8084},{},[8085],{"type":25,"value":8086},"   └─ default.conf\n",{"type":20,"tag":21,"props":8088,"children":8089},{},[8090,8092,8097],{"type":25,"value":8091},"这种结构的好处是：每个服务只关心自己的运行环境，整体启动则交给 ",{"type":20,"tag":84,"props":8093,"children":8095},{"className":8094},[],[8096],{"type":25,"value":7018},{"type":25,"value":8098},"。例如后端可以使用 Java 或 Node.js，AI 服务可以使用 Python，前端可以使用 Nuxt，它们不用把所有运行环境都堆在宿主机上。",{"type":20,"tag":21,"props":8100,"children":8101},{},[8102],{"type":25,"value":8103},"一个简化的 Compose 文件可能长这样：",{"type":20,"tag":126,"props":8105,"children":8107},{"className":5655,"code":8106,"language":5657,"meta":8,"style":8},"services:\n  frontend:\n    build: .\u002Ffrontend\n    expose:\n      - \"3000\"\n\n  backend:\n    build: .\u002Fbackend\n    expose:\n      - \"8080\"\n    environment:\n      NODE_ENV: production\n\n  nginx:\n    image: nginx:alpine\n    ports:\n      - \"80:80\"\n    volumes:\n      - .\u002Fnginx\u002Fdefault.conf:\u002Fetc\u002Fnginx\u002Fconf.d\u002Fdefault.conf:ro\n    depends_on:\n      - frontend\n      - backend\n",[8108],{"type":20,"tag":84,"props":8109,"children":8110},{"__ignoreMap":8},[8111,8124,8136,8153,8165,8178,8185,8197,8213,8224,8236,8248,8265,8272,8284,8301,8313,8325,8337,8349,8361,8373],{"type":20,"tag":157,"props":8112,"children":8113},{"class":159,"line":160},[8114,8119],{"type":20,"tag":157,"props":8115,"children":8116},{"style":5667},[8117],{"type":25,"value":8118},"services",{"type":20,"tag":157,"props":8120,"children":8121},{"style":892},[8122],{"type":25,"value":8123},":\n",{"type":20,"tag":157,"props":8125,"children":8126},{"class":159,"line":169},[8127,8132],{"type":20,"tag":157,"props":8128,"children":8129},{"style":5667},[8130],{"type":25,"value":8131},"  frontend",{"type":20,"tag":157,"props":8133,"children":8134},{"style":892},[8135],{"type":25,"value":8123},{"type":20,"tag":157,"props":8137,"children":8138},{"class":159,"line":179},[8139,8144,8148],{"type":20,"tag":157,"props":8140,"children":8141},{"style":5667},[8142],{"type":25,"value":8143},"    build",{"type":20,"tag":157,"props":8145,"children":8146},{"style":892},[8147],{"type":25,"value":908},{"type":20,"tag":157,"props":8149,"children":8150},{"style":254},[8151],{"type":25,"value":8152},".\u002Ffrontend\n",{"type":20,"tag":157,"props":8154,"children":8155},{"class":159,"line":188},[8156,8161],{"type":20,"tag":157,"props":8157,"children":8158},{"style":5667},[8159],{"type":25,"value":8160},"    expose",{"type":20,"tag":157,"props":8162,"children":8163},{"style":892},[8164],{"type":25,"value":8123},{"type":20,"tag":157,"props":8166,"children":8167},{"class":159,"line":196},[8168,8173],{"type":20,"tag":157,"props":8169,"children":8170},{"style":892},[8171],{"type":25,"value":8172},"      - ",{"type":20,"tag":157,"props":8174,"children":8175},{"style":254},[8176],{"type":25,"value":8177},"\"3000\"\n",{"type":20,"tag":157,"props":8179,"children":8180},{"class":159,"line":204},[8181],{"type":20,"tag":157,"props":8182,"children":8183},{"emptyLinePlaceholder":173},[8184],{"type":25,"value":176},{"type":20,"tag":157,"props":8186,"children":8187},{"class":159,"line":213},[8188,8193],{"type":20,"tag":157,"props":8189,"children":8190},{"style":5667},[8191],{"type":25,"value":8192},"  backend",{"type":20,"tag":157,"props":8194,"children":8195},{"style":892},[8196],{"type":25,"value":8123},{"type":20,"tag":157,"props":8198,"children":8199},{"class":159,"line":222},[8200,8204,8208],{"type":20,"tag":157,"props":8201,"children":8202},{"style":5667},[8203],{"type":25,"value":8143},{"type":20,"tag":157,"props":8205,"children":8206},{"style":892},[8207],{"type":25,"value":908},{"type":20,"tag":157,"props":8209,"children":8210},{"style":254},[8211],{"type":25,"value":8212},".\u002Fbackend\n",{"type":20,"tag":157,"props":8214,"children":8215},{"class":159,"line":440},[8216,8220],{"type":20,"tag":157,"props":8217,"children":8218},{"style":5667},[8219],{"type":25,"value":8160},{"type":20,"tag":157,"props":8221,"children":8222},{"style":892},[8223],{"type":25,"value":8123},{"type":20,"tag":157,"props":8225,"children":8226},{"class":159,"line":448},[8227,8231],{"type":20,"tag":157,"props":8228,"children":8229},{"style":892},[8230],{"type":25,"value":8172},{"type":20,"tag":157,"props":8232,"children":8233},{"style":254},[8234],{"type":25,"value":8235},"\"8080\"\n",{"type":20,"tag":157,"props":8237,"children":8238},{"class":159,"line":456},[8239,8244],{"type":20,"tag":157,"props":8240,"children":8241},{"style":5667},[8242],{"type":25,"value":8243},"    environment",{"type":20,"tag":157,"props":8245,"children":8246},{"style":892},[8247],{"type":25,"value":8123},{"type":20,"tag":157,"props":8249,"children":8250},{"class":159,"line":465},[8251,8256,8260],{"type":20,"tag":157,"props":8252,"children":8253},{"style":5667},[8254],{"type":25,"value":8255},"      NODE_ENV",{"type":20,"tag":157,"props":8257,"children":8258},{"style":892},[8259],{"type":25,"value":908},{"type":20,"tag":157,"props":8261,"children":8262},{"style":254},[8263],{"type":25,"value":8264},"production\n",{"type":20,"tag":157,"props":8266,"children":8267},{"class":159,"line":474},[8268],{"type":20,"tag":157,"props":8269,"children":8270},{"emptyLinePlaceholder":173},[8271],{"type":25,"value":176},{"type":20,"tag":157,"props":8273,"children":8274},{"class":159,"line":483},[8275,8280],{"type":20,"tag":157,"props":8276,"children":8277},{"style":5667},[8278],{"type":25,"value":8279},"  nginx",{"type":20,"tag":157,"props":8281,"children":8282},{"style":892},[8283],{"type":25,"value":8123},{"type":20,"tag":157,"props":8285,"children":8286},{"class":159,"line":491},[8287,8292,8296],{"type":20,"tag":157,"props":8288,"children":8289},{"style":5667},[8290],{"type":25,"value":8291},"    image",{"type":20,"tag":157,"props":8293,"children":8294},{"style":892},[8295],{"type":25,"value":908},{"type":20,"tag":157,"props":8297,"children":8298},{"style":254},[8299],{"type":25,"value":8300},"nginx:alpine\n",{"type":20,"tag":157,"props":8302,"children":8303},{"class":159,"line":499},[8304,8309],{"type":20,"tag":157,"props":8305,"children":8306},{"style":5667},[8307],{"type":25,"value":8308},"    ports",{"type":20,"tag":157,"props":8310,"children":8311},{"style":892},[8312],{"type":25,"value":8123},{"type":20,"tag":157,"props":8314,"children":8315},{"class":159,"line":508},[8316,8320],{"type":20,"tag":157,"props":8317,"children":8318},{"style":892},[8319],{"type":25,"value":8172},{"type":20,"tag":157,"props":8321,"children":8322},{"style":254},[8323],{"type":25,"value":8324},"\"80:80\"\n",{"type":20,"tag":157,"props":8326,"children":8327},{"class":159,"line":517},[8328,8333],{"type":20,"tag":157,"props":8329,"children":8330},{"style":5667},[8331],{"type":25,"value":8332},"    volumes",{"type":20,"tag":157,"props":8334,"children":8335},{"style":892},[8336],{"type":25,"value":8123},{"type":20,"tag":157,"props":8338,"children":8339},{"class":159,"line":1499},[8340,8344],{"type":20,"tag":157,"props":8341,"children":8342},{"style":892},[8343],{"type":25,"value":8172},{"type":20,"tag":157,"props":8345,"children":8346},{"style":254},[8347],{"type":25,"value":8348},".\u002Fnginx\u002Fdefault.conf:\u002Fetc\u002Fnginx\u002Fconf.d\u002Fdefault.conf:ro\n",{"type":20,"tag":157,"props":8350,"children":8351},{"class":159,"line":1507},[8352,8357],{"type":20,"tag":157,"props":8353,"children":8354},{"style":5667},[8355],{"type":25,"value":8356},"    depends_on",{"type":20,"tag":157,"props":8358,"children":8359},{"style":892},[8360],{"type":25,"value":8123},{"type":20,"tag":157,"props":8362,"children":8363},{"class":159,"line":1515},[8364,8368],{"type":20,"tag":157,"props":8365,"children":8366},{"style":892},[8367],{"type":25,"value":8172},{"type":20,"tag":157,"props":8369,"children":8370},{"style":254},[8371],{"type":25,"value":8372},"frontend\n",{"type":20,"tag":157,"props":8374,"children":8375},{"class":159,"line":1523},[8376,8380],{"type":20,"tag":157,"props":8377,"children":8378},{"style":892},[8379],{"type":25,"value":8172},{"type":20,"tag":157,"props":8381,"children":8382},{"style":254},[8383],{"type":25,"value":8384},"backend\n",{"type":20,"tag":21,"props":8386,"children":8387},{},[8388,8390,8396,8397,8403],{"type":25,"value":8389},"在同一个 Compose 项目里，服务会加入默认网络，并且可以通过服务名互相访问。也就是说，Nginx 容器里可以把请求转发到 ",{"type":20,"tag":84,"props":8391,"children":8393},{"className":8392},[],[8394],{"type":25,"value":8395},"http:\u002F\u002Ffrontend:3000",{"type":25,"value":7993},{"type":20,"tag":84,"props":8398,"children":8400},{"className":8399},[],[8401],{"type":25,"value":8402},"http:\u002F\u002Fbackend:8080",{"type":25,"value":8404},"，而不是写死某个容器 IP。容器 IP 可能会变化，服务名才是更稳定的连接方式。",{"type":20,"tag":1863,"props":8406,"children":8408},{"id":8407},"常用-docker-命令",[8409],{"type":25,"value":8410},"常用 Docker 命令",{"type":20,"tag":21,"props":8412,"children":8413},{},[8414],{"type":25,"value":8415},"初学阶段不需要背完所有命令，先掌握\"看状态、看日志、进容器、停服务、清资源\"这几类就够了。",{"type":20,"tag":28,"props":8417,"children":8419},{"id":8418},"查看容器",[8420],{"type":25,"value":8418},{"type":20,"tag":126,"props":8422,"children":8424},{"className":238,"code":8423,"language":237,"meta":8,"style":8},"docker ps\ndocker ps -a\ndocker ps --format \"table {{.Names}}\\t{{.Status}}\\t{{.Ports}}\"\n",[8425],{"type":20,"tag":84,"props":8426,"children":8427},{"__ignoreMap":8},[8428,8440,8457],{"type":20,"tag":157,"props":8429,"children":8430},{"class":159,"line":160},[8431,8435],{"type":20,"tag":157,"props":8432,"children":8433},{"style":248},[8434],{"type":25,"value":7626},{"type":20,"tag":157,"props":8436,"children":8437},{"style":254},[8438],{"type":25,"value":8439}," ps\n",{"type":20,"tag":157,"props":8441,"children":8442},{"class":159,"line":169},[8443,8447,8452],{"type":20,"tag":157,"props":8444,"children":8445},{"style":248},[8446],{"type":25,"value":7626},{"type":20,"tag":157,"props":8448,"children":8449},{"style":254},[8450],{"type":25,"value":8451}," ps",{"type":20,"tag":157,"props":8453,"children":8454},{"style":260},[8455],{"type":25,"value":8456}," -a\n",{"type":20,"tag":157,"props":8458,"children":8459},{"class":159,"line":179},[8460,8464,8468,8473],{"type":20,"tag":157,"props":8461,"children":8462},{"style":248},[8463],{"type":25,"value":7626},{"type":20,"tag":157,"props":8465,"children":8466},{"style":254},[8467],{"type":25,"value":8451},{"type":20,"tag":157,"props":8469,"children":8470},{"style":260},[8471],{"type":25,"value":8472}," --format",{"type":20,"tag":157,"props":8474,"children":8475},{"style":254},[8476],{"type":25,"value":8477}," \"table {{.Names}}\\t{{.Status}}\\t{{.Ports}}\"\n",{"type":20,"tag":21,"props":8479,"children":8480},{},[8481,8487,8489,8495],{"type":20,"tag":84,"props":8482,"children":8484},{"className":8483},[],[8485],{"type":25,"value":8486},"docker ps",{"type":25,"value":8488}," 只查看运行中的容器，",{"type":20,"tag":84,"props":8490,"children":8492},{"className":8491},[],[8493],{"type":25,"value":8494},"docker ps -a",{"type":25,"value":8496}," 会包含已经停止的容器。第三条命令可以用表格形式查看容器名称、状态和端口映射，更适合日常排查。",{"type":20,"tag":28,"props":8498,"children":8500},{"id":8499},"查看和删除镜像",[8501],{"type":25,"value":8499},{"type":20,"tag":126,"props":8503,"children":8505},{"className":238,"code":8504,"language":237,"meta":8,"style":8},"docker images\ndocker images your-app\ndocker rmi your-app:v1\n",[8506],{"type":20,"tag":84,"props":8507,"children":8508},{"__ignoreMap":8},[8509,8521,8538],{"type":20,"tag":157,"props":8510,"children":8511},{"class":159,"line":160},[8512,8516],{"type":20,"tag":157,"props":8513,"children":8514},{"style":248},[8515],{"type":25,"value":7626},{"type":20,"tag":157,"props":8517,"children":8518},{"style":254},[8519],{"type":25,"value":8520}," images\n",{"type":20,"tag":157,"props":8522,"children":8523},{"class":159,"line":169},[8524,8528,8533],{"type":20,"tag":157,"props":8525,"children":8526},{"style":248},[8527],{"type":25,"value":7626},{"type":20,"tag":157,"props":8529,"children":8530},{"style":254},[8531],{"type":25,"value":8532}," images",{"type":20,"tag":157,"props":8534,"children":8535},{"style":254},[8536],{"type":25,"value":8537}," your-app\n",{"type":20,"tag":157,"props":8539,"children":8540},{"class":159,"line":179},[8541,8545,8550],{"type":20,"tag":157,"props":8542,"children":8543},{"style":248},[8544],{"type":25,"value":7626},{"type":20,"tag":157,"props":8546,"children":8547},{"style":254},[8548],{"type":25,"value":8549}," rmi",{"type":20,"tag":157,"props":8551,"children":8552},{"style":254},[8553],{"type":25,"value":8554}," your-app:v1\n",{"type":20,"tag":21,"props":8556,"children":8557},{},[8558,8564,8566,8572],{"type":20,"tag":84,"props":8559,"children":8561},{"className":8560},[],[8562],{"type":25,"value":8563},"docker images",{"type":25,"value":8565}," 用来查看本机已有镜像。",{"type":20,"tag":84,"props":8567,"children":8569},{"className":8568},[],[8570],{"type":25,"value":8571},"docker rmi",{"type":25,"value":8573}," 用来删除镜像。如果某个镜像正在被容器使用，需要先停止并删除对应容器。",{"type":20,"tag":28,"props":8575,"children":8577},{"id":8576},"启动容器",[8578],{"type":25,"value":8576},{"type":20,"tag":126,"props":8580,"children":8582},{"className":238,"code":8581,"language":237,"meta":8,"style":8},"docker run -d \\\n  --name my-app \\\n  -p 80:8080 \\\n  -v \u002Fdata:\u002Fapp\u002Fdata \\\n  -e DB_HOST=db.example.com \\\n  --restart unless-stopped \\\n  your-app:v1.0\n",[8583],{"type":20,"tag":84,"props":8584,"children":8585},{"__ignoreMap":8},[8586,8605,8621,8637,8654,8671,8688],{"type":20,"tag":157,"props":8587,"children":8588},{"class":159,"line":160},[8589,8593,8597,8601],{"type":20,"tag":157,"props":8590,"children":8591},{"style":248},[8592],{"type":25,"value":7626},{"type":20,"tag":157,"props":8594,"children":8595},{"style":254},[8596],{"type":25,"value":2119},{"type":20,"tag":157,"props":8598,"children":8599},{"style":260},[8600],{"type":25,"value":7694},{"type":20,"tag":157,"props":8602,"children":8603},{"style":260},[8604],{"type":25,"value":7699},{"type":20,"tag":157,"props":8606,"children":8607},{"class":159,"line":169},[8608,8612,8617],{"type":20,"tag":157,"props":8609,"children":8610},{"style":260},[8611],{"type":25,"value":7707},{"type":20,"tag":157,"props":8613,"children":8614},{"style":254},[8615],{"type":25,"value":8616}," my-app",{"type":20,"tag":157,"props":8618,"children":8619},{"style":260},[8620],{"type":25,"value":7699},{"type":20,"tag":157,"props":8622,"children":8623},{"class":159,"line":179},[8624,8628,8633],{"type":20,"tag":157,"props":8625,"children":8626},{"style":260},[8627],{"type":25,"value":7723},{"type":20,"tag":157,"props":8629,"children":8630},{"style":254},[8631],{"type":25,"value":8632}," 80:8080",{"type":20,"tag":157,"props":8634,"children":8635},{"style":260},[8636],{"type":25,"value":7699},{"type":20,"tag":157,"props":8638,"children":8639},{"class":159,"line":188},[8640,8645,8650],{"type":20,"tag":157,"props":8641,"children":8642},{"style":260},[8643],{"type":25,"value":8644},"  -v",{"type":20,"tag":157,"props":8646,"children":8647},{"style":254},[8648],{"type":25,"value":8649}," \u002Fdata:\u002Fapp\u002Fdata",{"type":20,"tag":157,"props":8651,"children":8652},{"style":260},[8653],{"type":25,"value":7699},{"type":20,"tag":157,"props":8655,"children":8656},{"class":159,"line":196},[8657,8662,8667],{"type":20,"tag":157,"props":8658,"children":8659},{"style":260},[8660],{"type":25,"value":8661},"  -e",{"type":20,"tag":157,"props":8663,"children":8664},{"style":254},[8665],{"type":25,"value":8666}," DB_HOST=db.example.com",{"type":20,"tag":157,"props":8668,"children":8669},{"style":260},[8670],{"type":25,"value":7699},{"type":20,"tag":157,"props":8672,"children":8673},{"class":159,"line":204},[8674,8679,8684],{"type":20,"tag":157,"props":8675,"children":8676},{"style":260},[8677],{"type":25,"value":8678},"  --restart",{"type":20,"tag":157,"props":8680,"children":8681},{"style":254},[8682],{"type":25,"value":8683}," unless-stopped",{"type":20,"tag":157,"props":8685,"children":8686},{"style":260},[8687],{"type":25,"value":7699},{"type":20,"tag":157,"props":8689,"children":8690},{"class":159,"line":213},[8691],{"type":20,"tag":157,"props":8692,"children":8693},{"style":254},[8694],{"type":25,"value":8695},"  your-app:v1.0\n",{"type":20,"tag":21,"props":8697,"children":8698},{},[8699],{"type":25,"value":8700},"这是一条比较完整的启动命令：",{"type":20,"tag":44,"props":8702,"children":8703},{},[8704,8715,8740,8751,8762,8773],{"type":20,"tag":48,"props":8705,"children":8706},{},[8707,8713],{"type":20,"tag":84,"props":8708,"children":8710},{"className":8709},[],[8711],{"type":25,"value":8712},"--name my-app",{"type":25,"value":8714},"：给容器起一个名字，后续可以直接用名字操作它。",{"type":20,"tag":48,"props":8716,"children":8717},{},[8718,8724,8726,8731,8733,8739],{"type":20,"tag":84,"props":8719,"children":8721},{"className":8720},[],[8722],{"type":25,"value":8723},"-p 80:8080",{"type":25,"value":8725},"：把宿主机 ",{"type":20,"tag":84,"props":8727,"children":8729},{"className":8728},[],[8730],{"type":25,"value":7798},{"type":25,"value":8732}," 端口映射到容器 ",{"type":20,"tag":84,"props":8734,"children":8736},{"className":8735},[],[8737],{"type":25,"value":8738},"8080",{"type":25,"value":7807},{"type":20,"tag":48,"props":8741,"children":8742},{},[8743,8749],{"type":20,"tag":84,"props":8744,"children":8746},{"className":8745},[],[8747],{"type":25,"value":8748},"-v \u002Fdata:\u002Fapp\u002Fdata",{"type":25,"value":8750},"：挂载目录，用来持久化数据或提供配置文件。",{"type":20,"tag":48,"props":8752,"children":8753},{},[8754,8760],{"type":20,"tag":84,"props":8755,"children":8757},{"className":8756},[],[8758],{"type":25,"value":8759},"-e DB_HOST=db.example.com",{"type":25,"value":8761},"：注入环境变量。",{"type":20,"tag":48,"props":8763,"children":8764},{},[8765,8771],{"type":20,"tag":84,"props":8766,"children":8768},{"className":8767},[],[8769],{"type":25,"value":8770},"--restart unless-stopped",{"type":25,"value":8772},"：容器异常退出后自动重启，除非手动停止。",{"type":20,"tag":48,"props":8774,"children":8775},{},[8776,8782],{"type":20,"tag":84,"props":8777,"children":8779},{"className":8778},[],[8780],{"type":25,"value":8781},"your-app:v1.0",{"type":25,"value":8783},"：指定镜像和版本标签。",{"type":20,"tag":21,"props":8785,"children":8786},{},[8787,8789,8795,8797,8803,8805,8810,8812,8818,8819,8825,8826,8831],{"type":25,"value":8788},"这里不建议把 ",{"type":20,"tag":84,"props":8790,"children":8792},{"className":8791},[],[8793],{"type":25,"value":8794},"DB_HOST",{"type":25,"value":8796}," 写成 ",{"type":20,"tag":84,"props":8798,"children":8800},{"className":8799},[],[8801],{"type":25,"value":8802},"localhost",{"type":25,"value":8804},"，除非数据库就在同一个容器里。对容器来说，",{"type":20,"tag":84,"props":8806,"children":8808},{"className":8807},[],[8809],{"type":25,"value":8802},{"type":25,"value":8811}," 通常指容器自己，不是宿主机，也不是其他容器。多容器项目中，应该优先使用 Compose 服务名，例如 ",{"type":20,"tag":84,"props":8813,"children":8815},{"className":8814},[],[8816],{"type":25,"value":8817},"db",{"type":25,"value":965},{"type":20,"tag":84,"props":8820,"children":8822},{"className":8821},[],[8823],{"type":25,"value":8824},"redis",{"type":25,"value":965},{"type":20,"tag":84,"props":8827,"children":8829},{"className":8828},[],[8830],{"type":25,"value":6},{"type":25,"value":110},{"type":20,"tag":28,"props":8833,"children":8835},{"id":8834},"停止和重启容器",[8836],{"type":25,"value":8834},{"type":20,"tag":126,"props":8838,"children":8840},{"className":238,"code":8839,"language":237,"meta":8,"style":8},"docker stop my-app\ndocker start my-app\ndocker restart my-app\ndocker stop $(docker ps -q)\n",[8841],{"type":20,"tag":84,"props":8842,"children":8843},{"__ignoreMap":8},[8844,8861,8877,8893],{"type":20,"tag":157,"props":8845,"children":8846},{"class":159,"line":160},[8847,8851,8856],{"type":20,"tag":157,"props":8848,"children":8849},{"style":248},[8850],{"type":25,"value":7626},{"type":20,"tag":157,"props":8852,"children":8853},{"style":254},[8854],{"type":25,"value":8855}," stop",{"type":20,"tag":157,"props":8857,"children":8858},{"style":254},[8859],{"type":25,"value":8860}," my-app\n",{"type":20,"tag":157,"props":8862,"children":8863},{"class":159,"line":169},[8864,8868,8873],{"type":20,"tag":157,"props":8865,"children":8866},{"style":248},[8867],{"type":25,"value":7626},{"type":20,"tag":157,"props":8869,"children":8870},{"style":254},[8871],{"type":25,"value":8872}," start",{"type":20,"tag":157,"props":8874,"children":8875},{"style":254},[8876],{"type":25,"value":8860},{"type":20,"tag":157,"props":8878,"children":8879},{"class":159,"line":179},[8880,8884,8889],{"type":20,"tag":157,"props":8881,"children":8882},{"style":248},[8883],{"type":25,"value":7626},{"type":20,"tag":157,"props":8885,"children":8886},{"style":254},[8887],{"type":25,"value":8888}," restart",{"type":20,"tag":157,"props":8890,"children":8891},{"style":254},[8892],{"type":25,"value":8860},{"type":20,"tag":157,"props":8894,"children":8895},{"class":159,"line":188},[8896,8900,8904,8909,8913,8917,8922],{"type":20,"tag":157,"props":8897,"children":8898},{"style":248},[8899],{"type":25,"value":7626},{"type":20,"tag":157,"props":8901,"children":8902},{"style":254},[8903],{"type":25,"value":8855},{"type":20,"tag":157,"props":8905,"children":8906},{"style":892},[8907],{"type":25,"value":8908}," $(",{"type":20,"tag":157,"props":8910,"children":8911},{"style":248},[8912],{"type":25,"value":7626},{"type":20,"tag":157,"props":8914,"children":8915},{"style":254},[8916],{"type":25,"value":8451},{"type":20,"tag":157,"props":8918,"children":8919},{"style":260},[8920],{"type":25,"value":8921}," -q",{"type":20,"tag":157,"props":8923,"children":8924},{"style":892},[8925],{"type":25,"value":8926},")\n",{"type":20,"tag":21,"props":8928,"children":8929},{},[8930],{"type":25,"value":8931},"容器既可以通过名称操作，也可以通过容器 ID 操作。日常使用时，建议给重要容器都设置明确的名字。",{"type":20,"tag":28,"props":8933,"children":8935},{"id":8934},"查看日志",[8936],{"type":25,"value":8934},{"type":20,"tag":126,"props":8938,"children":8940},{"className":238,"code":8939,"language":237,"meta":8,"style":8},"docker logs my-app\ndocker logs -f my-app\ndocker logs --tail 100 my-app\ndocker logs --since 1h my-app\n",[8941],{"type":20,"tag":84,"props":8942,"children":8943},{"__ignoreMap":8},[8944,8960,8980,9005],{"type":20,"tag":157,"props":8945,"children":8946},{"class":159,"line":160},[8947,8951,8956],{"type":20,"tag":157,"props":8948,"children":8949},{"style":248},[8950],{"type":25,"value":7626},{"type":20,"tag":157,"props":8952,"children":8953},{"style":254},[8954],{"type":25,"value":8955}," logs",{"type":20,"tag":157,"props":8957,"children":8958},{"style":254},[8959],{"type":25,"value":8860},{"type":20,"tag":157,"props":8961,"children":8962},{"class":159,"line":169},[8963,8967,8971,8976],{"type":20,"tag":157,"props":8964,"children":8965},{"style":248},[8966],{"type":25,"value":7626},{"type":20,"tag":157,"props":8968,"children":8969},{"style":254},[8970],{"type":25,"value":8955},{"type":20,"tag":157,"props":8972,"children":8973},{"style":260},[8974],{"type":25,"value":8975}," -f",{"type":20,"tag":157,"props":8977,"children":8978},{"style":254},[8979],{"type":25,"value":8860},{"type":20,"tag":157,"props":8981,"children":8982},{"class":159,"line":179},[8983,8987,8991,8996,9001],{"type":20,"tag":157,"props":8984,"children":8985},{"style":248},[8986],{"type":25,"value":7626},{"type":20,"tag":157,"props":8988,"children":8989},{"style":254},[8990],{"type":25,"value":8955},{"type":20,"tag":157,"props":8992,"children":8993},{"style":260},[8994],{"type":25,"value":8995}," --tail",{"type":20,"tag":157,"props":8997,"children":8998},{"style":260},[8999],{"type":25,"value":9000}," 100",{"type":20,"tag":157,"props":9002,"children":9003},{"style":254},[9004],{"type":25,"value":8860},{"type":20,"tag":157,"props":9006,"children":9007},{"class":159,"line":188},[9008,9012,9016,9021,9026],{"type":20,"tag":157,"props":9009,"children":9010},{"style":248},[9011],{"type":25,"value":7626},{"type":20,"tag":157,"props":9013,"children":9014},{"style":254},[9015],{"type":25,"value":8955},{"type":20,"tag":157,"props":9017,"children":9018},{"style":260},[9019],{"type":25,"value":9020}," --since",{"type":20,"tag":157,"props":9022,"children":9023},{"style":254},[9024],{"type":25,"value":9025}," 1h",{"type":20,"tag":157,"props":9027,"children":9028},{"style":254},[9029],{"type":25,"value":8860},{"type":20,"tag":21,"props":9031,"children":9032},{},[9033,9035,9041,9043,9049],{"type":25,"value":9034},"排查线上问题时，",{"type":20,"tag":84,"props":9036,"children":9038},{"className":9037},[],[9039],{"type":25,"value":9040},"docker logs -f",{"type":25,"value":9042}," 很常用，它会持续输出日志，效果类似 ",{"type":20,"tag":84,"props":9044,"children":9046},{"className":9045},[],[9047],{"type":25,"value":9048},"tail -f",{"type":25,"value":110},{"type":20,"tag":28,"props":9051,"children":9053},{"id":9052},"进入容器",[9054],{"type":25,"value":9052},{"type":20,"tag":126,"props":9056,"children":9058},{"className":238,"code":9057,"language":237,"meta":8,"style":8},"docker exec -it my-app sh\ndocker exec -it my-app bash\ndocker exec my-app ls \u002Fapp\ndocker exec my-app env\n",[9059],{"type":20,"tag":84,"props":9060,"children":9061},{"__ignoreMap":8},[9062,9088,9112,9137],{"type":20,"tag":157,"props":9063,"children":9064},{"class":159,"line":160},[9065,9069,9074,9079,9083],{"type":20,"tag":157,"props":9066,"children":9067},{"style":248},[9068],{"type":25,"value":7626},{"type":20,"tag":157,"props":9070,"children":9071},{"style":254},[9072],{"type":25,"value":9073}," exec",{"type":20,"tag":157,"props":9075,"children":9076},{"style":260},[9077],{"type":25,"value":9078}," -it",{"type":20,"tag":157,"props":9080,"children":9081},{"style":254},[9082],{"type":25,"value":8616},{"type":20,"tag":157,"props":9084,"children":9085},{"style":254},[9086],{"type":25,"value":9087}," sh\n",{"type":20,"tag":157,"props":9089,"children":9090},{"class":159,"line":169},[9091,9095,9099,9103,9107],{"type":20,"tag":157,"props":9092,"children":9093},{"style":248},[9094],{"type":25,"value":7626},{"type":20,"tag":157,"props":9096,"children":9097},{"style":254},[9098],{"type":25,"value":9073},{"type":20,"tag":157,"props":9100,"children":9101},{"style":260},[9102],{"type":25,"value":9078},{"type":20,"tag":157,"props":9104,"children":9105},{"style":254},[9106],{"type":25,"value":8616},{"type":20,"tag":157,"props":9108,"children":9109},{"style":254},[9110],{"type":25,"value":9111}," bash\n",{"type":20,"tag":157,"props":9113,"children":9114},{"class":159,"line":179},[9115,9119,9123,9127,9132],{"type":20,"tag":157,"props":9116,"children":9117},{"style":248},[9118],{"type":25,"value":7626},{"type":20,"tag":157,"props":9120,"children":9121},{"style":254},[9122],{"type":25,"value":9073},{"type":20,"tag":157,"props":9124,"children":9125},{"style":254},[9126],{"type":25,"value":8616},{"type":20,"tag":157,"props":9128,"children":9129},{"style":254},[9130],{"type":25,"value":9131}," ls",{"type":20,"tag":157,"props":9133,"children":9134},{"style":254},[9135],{"type":25,"value":9136}," \u002Fapp\n",{"type":20,"tag":157,"props":9138,"children":9139},{"class":159,"line":188},[9140,9144,9148,9152],{"type":20,"tag":157,"props":9141,"children":9142},{"style":248},[9143],{"type":25,"value":7626},{"type":20,"tag":157,"props":9145,"children":9146},{"style":254},[9147],{"type":25,"value":9073},{"type":20,"tag":157,"props":9149,"children":9150},{"style":254},[9151],{"type":25,"value":8616},{"type":20,"tag":157,"props":9153,"children":9154},{"style":254},[9155],{"type":25,"value":9156}," env\n",{"type":20,"tag":21,"props":9158,"children":9159},{},[9160,9166,9168,9174,9176,9181],{"type":20,"tag":84,"props":9161,"children":9163},{"className":9162},[],[9164],{"type":25,"value":9165},"docker exec",{"type":25,"value":9167}," 可以在已经运行的容器里执行命令。基于 Alpine 的镜像通常只有 ",{"type":20,"tag":84,"props":9169,"children":9171},{"className":9170},[],[9172],{"type":25,"value":9173},"sh",{"type":25,"value":9175},"，Ubuntu、Debian 这类镜像通常可以使用 ",{"type":20,"tag":84,"props":9177,"children":9179},{"className":9178},[],[9180],{"type":25,"value":237},{"type":25,"value":110},{"type":20,"tag":28,"props":9183,"children":9185},{"id":9184},"删除容器和镜像",[9186],{"type":25,"value":9184},{"type":20,"tag":126,"props":9188,"children":9190},{"className":238,"code":9189,"language":237,"meta":8,"style":8},"docker rm my-app\ndocker rmi your-app:v1\n",[9191],{"type":20,"tag":84,"props":9192,"children":9193},{"__ignoreMap":8},[9194,9210],{"type":20,"tag":157,"props":9195,"children":9196},{"class":159,"line":160},[9197,9201,9206],{"type":20,"tag":157,"props":9198,"children":9199},{"style":248},[9200],{"type":25,"value":7626},{"type":20,"tag":157,"props":9202,"children":9203},{"style":254},[9204],{"type":25,"value":9205}," rm",{"type":20,"tag":157,"props":9207,"children":9208},{"style":254},[9209],{"type":25,"value":8860},{"type":20,"tag":157,"props":9211,"children":9212},{"class":159,"line":169},[9213,9217,9221],{"type":20,"tag":157,"props":9214,"children":9215},{"style":248},[9216],{"type":25,"value":7626},{"type":20,"tag":157,"props":9218,"children":9219},{"style":254},[9220],{"type":25,"value":8549},{"type":20,"tag":157,"props":9222,"children":9223},{"style":254},[9224],{"type":25,"value":8554},{"type":20,"tag":21,"props":9226,"children":9227},{},[9228,9234,9236,9241,9243,9248],{"type":20,"tag":84,"props":9229,"children":9231},{"className":9230},[],[9232],{"type":25,"value":9233},"docker rm",{"type":25,"value":9235}," 删除容器，",{"type":20,"tag":84,"props":9237,"children":9239},{"className":9238},[],[9240],{"type":25,"value":8571},{"type":25,"value":9242}," 删除镜像。清理资源时一般先用 ",{"type":20,"tag":84,"props":9244,"children":9246},{"className":9245},[],[9247],{"type":25,"value":8494},{"type":25,"value":9249}," 找到停止的容器，再决定是否删除。",{"type":20,"tag":1863,"props":9251,"children":9253},{"id":9252},"构建镜像时的-cpu-架构问题",[9254],{"type":25,"value":9255},"构建镜像时的 CPU 架构问题",{"type":20,"tag":21,"props":9257,"children":9258},{},[9259,9261,9267,9269,9275],{"type":25,"value":9260},"构建镜像时还要注意 CPU 架构。很多开发者使用 Apple Silicon Mac，比如 M1、M2、M3、M4，这类机器通常是 ",{"type":20,"tag":84,"props":9262,"children":9264},{"className":9263},[],[9265],{"type":25,"value":9266},"linux\u002Farm64",{"type":25,"value":9268}," 架构。而大多数云服务器、Intel\u002FAMD 服务器通常是 ",{"type":20,"tag":84,"props":9270,"children":9272},{"className":9271},[],[9273],{"type":25,"value":9274},"linux\u002Famd64",{"type":25,"value":9276}," 架构。",{"type":20,"tag":21,"props":9278,"children":9279},{},[9280],{"type":25,"value":9281},"常见架构可以这样理解：",{"type":20,"tag":44,"props":9283,"children":9284},{},[9285,9295],{"type":20,"tag":48,"props":9286,"children":9287},{},[9288,9293],{"type":20,"tag":84,"props":9289,"children":9291},{"className":9290},[],[9292],{"type":25,"value":9266},{"type":25,"value":9294},"：ARM 64 位架构，常见于 Apple Silicon Mac、部分 ARM 服务器、树莓派等。",{"type":20,"tag":48,"props":9296,"children":9297},{},[9298,9303],{"type":20,"tag":84,"props":9299,"children":9301},{"className":9300},[],[9302],{"type":25,"value":9274},{"type":25,"value":9304},"：x86_64 架构，常见于大多数云服务器和普通 Intel\u002FAMD 服务器。",{"type":20,"tag":21,"props":9306,"children":9307},{},[9308],{"type":25,"value":9309},"如果在 M 系列 Mac 上构建镜像，然后拿到 x86_64 云服务器上运行，就可能遇到架构不匹配的问题。部署到这类服务器时，可以显式指定目标平台：",{"type":20,"tag":126,"props":9311,"children":9313},{"className":238,"code":9312,"language":237,"meta":8,"style":8},"docker build --platform linux\u002Famd64 -t your-app:v1 .\n",[9314],{"type":20,"tag":84,"props":9315,"children":9316},{"__ignoreMap":8},[9317],{"type":20,"tag":157,"props":9318,"children":9319},{"class":159,"line":160},[9320,9324,9328,9333,9338,9342,9347],{"type":20,"tag":157,"props":9321,"children":9322},{"style":248},[9323],{"type":25,"value":7626},{"type":20,"tag":157,"props":9325,"children":9326},{"style":254},[9327],{"type":25,"value":7631},{"type":20,"tag":157,"props":9329,"children":9330},{"style":260},[9331],{"type":25,"value":9332}," --platform",{"type":20,"tag":157,"props":9334,"children":9335},{"style":254},[9336],{"type":25,"value":9337}," linux\u002Famd64",{"type":20,"tag":157,"props":9339,"children":9340},{"style":260},[9341],{"type":25,"value":7636},{"type":20,"tag":157,"props":9343,"children":9344},{"style":254},[9345],{"type":25,"value":9346}," your-app:v1",{"type":20,"tag":157,"props":9348,"children":9349},{"style":254},[9350],{"type":25,"value":7646},{"type":20,"tag":21,"props":9352,"children":9353},{},[9354,9356,9362],{"type":25,"value":9355},"如果需要同时支持多种架构，可以使用 ",{"type":20,"tag":84,"props":9357,"children":9359},{"className":9358},[],[9360],{"type":25,"value":9361},"docker buildx",{"type":25,"value":9363}," 构建多平台镜像，并推送到镜像仓库：",{"type":20,"tag":126,"props":9365,"children":9367},{"className":238,"code":9366,"language":237,"meta":8,"style":8},"docker buildx build \\\n  --platform linux\u002Famd64,linux\u002Farm64 \\\n  -t yourname\u002Fyour-app:v1 \\\n  --push .\n",[9368],{"type":20,"tag":84,"props":9369,"children":9370},{"__ignoreMap":8},[9371,9391,9408,9425],{"type":20,"tag":157,"props":9372,"children":9373},{"class":159,"line":160},[9374,9378,9383,9387],{"type":20,"tag":157,"props":9375,"children":9376},{"style":248},[9377],{"type":25,"value":7626},{"type":20,"tag":157,"props":9379,"children":9380},{"style":254},[9381],{"type":25,"value":9382}," buildx",{"type":20,"tag":157,"props":9384,"children":9385},{"style":254},[9386],{"type":25,"value":7631},{"type":20,"tag":157,"props":9388,"children":9389},{"style":260},[9390],{"type":25,"value":7699},{"type":20,"tag":157,"props":9392,"children":9393},{"class":159,"line":169},[9394,9399,9404],{"type":20,"tag":157,"props":9395,"children":9396},{"style":260},[9397],{"type":25,"value":9398},"  --platform",{"type":20,"tag":157,"props":9400,"children":9401},{"style":254},[9402],{"type":25,"value":9403}," linux\u002Famd64,linux\u002Farm64",{"type":20,"tag":157,"props":9405,"children":9406},{"style":260},[9407],{"type":25,"value":7699},{"type":20,"tag":157,"props":9409,"children":9410},{"class":159,"line":179},[9411,9416,9421],{"type":20,"tag":157,"props":9412,"children":9413},{"style":260},[9414],{"type":25,"value":9415},"  -t",{"type":20,"tag":157,"props":9417,"children":9418},{"style":254},[9419],{"type":25,"value":9420}," yourname\u002Fyour-app:v1",{"type":20,"tag":157,"props":9422,"children":9423},{"style":260},[9424],{"type":25,"value":7699},{"type":20,"tag":157,"props":9426,"children":9427},{"class":159,"line":188},[9428,9433],{"type":20,"tag":157,"props":9429,"children":9430},{"style":260},[9431],{"type":25,"value":9432},"  --push",{"type":20,"tag":157,"props":9434,"children":9435},{"style":254},[9436],{"type":25,"value":7646},{"type":20,"tag":21,"props":9438,"children":9439},{},[9440],{"type":25,"value":9441},"单平台构建适合个人项目或固定服务器架构；多平台构建适合要同时支持 x86_64 服务器、ARM 服务器或不同开发机器的项目。",{"type":20,"tag":1863,"props":9443,"children":9445},{"id":9444},"docker-和-nginx-的关系",[9446],{"type":25,"value":9447},"Docker 和 Nginx 的关系",{"type":20,"tag":21,"props":9449,"children":9450},{},[9451],{"type":25,"value":9452},"Docker 负责打包和运行应用，Nginx 更常见的角色是接收外部请求、处理域名和路径转发、统一配置 HTTPS。",{"type":20,"tag":21,"props":9454,"children":9455},{},[9456,9458,9464],{"type":25,"value":9457},"没有 Nginx，服务器也可以部署多个项目，只要每个项目监听不同端口，外部就能通过 ",{"type":20,"tag":84,"props":9459,"children":9461},{"className":9460},[],[9462],{"type":25,"value":9463},"IP + 端口",{"type":25,"value":9465}," 访问它们。例如：",{"type":20,"tag":126,"props":9467,"children":9469},{"className":324,"code":9468,"language":25,"meta":8,"style":8},"http:\u002F\u002F服务器IP:3000\nhttp:\u002F\u002F服务器IP:8080\nhttp:\u002F\u002F服务器IP:9000\n",[9470],{"type":20,"tag":84,"props":9471,"children":9472},{"__ignoreMap":8},[9473,9481,9489],{"type":20,"tag":157,"props":9474,"children":9475},{"class":159,"line":160},[9476],{"type":20,"tag":157,"props":9477,"children":9478},{},[9479],{"type":25,"value":9480},"http:\u002F\u002F服务器IP:3000\n",{"type":20,"tag":157,"props":9482,"children":9483},{"class":159,"line":169},[9484],{"type":20,"tag":157,"props":9485,"children":9486},{},[9487],{"type":25,"value":9488},"http:\u002F\u002F服务器IP:8080\n",{"type":20,"tag":157,"props":9490,"children":9491},{"class":159,"line":179},[9492],{"type":20,"tag":157,"props":9493,"children":9494},{},[9495],{"type":25,"value":9496},"http:\u002F\u002F服务器IP:9000\n",{"type":20,"tag":21,"props":9498,"children":9499},{},[9500],{"type":25,"value":9501},"但是这种方式不适合正式网站。用户不会希望记住端口号，HTTPS 配置也会变得分散。",{"type":20,"tag":21,"props":9503,"children":9504},{},[9505,9507,9512,9513,9519],{"type":25,"value":9506},"加入 Nginx 后，可以让外部统一访问标准的 ",{"type":20,"tag":84,"props":9508,"children":9510},{"className":9509},[],[9511],{"type":25,"value":7798},{"type":25,"value":7993},{"type":20,"tag":84,"props":9514,"children":9516},{"className":9515},[],[9517],{"type":25,"value":9518},"443",{"type":25,"value":9520}," 端口，再由 Nginx 根据域名、子域名或路径转发到不同项目：",{"type":20,"tag":126,"props":9522,"children":9524},{"className":324,"code":9523,"language":25,"meta":8,"style":8},"blog.example.com  ->  frontend:3000\napi.example.com   ->  backend:8080\nai.example.com    ->  ai-service:9000\n",[9525],{"type":20,"tag":84,"props":9526,"children":9527},{"__ignoreMap":8},[9528,9536,9544],{"type":20,"tag":157,"props":9529,"children":9530},{"class":159,"line":160},[9531],{"type":20,"tag":157,"props":9532,"children":9533},{},[9534],{"type":25,"value":9535},"blog.example.com  ->  frontend:3000\n",{"type":20,"tag":157,"props":9537,"children":9538},{"class":159,"line":169},[9539],{"type":20,"tag":157,"props":9540,"children":9541},{},[9542],{"type":25,"value":9543},"api.example.com   ->  backend:8080\n",{"type":20,"tag":157,"props":9545,"children":9546},{"class":159,"line":179},[9547],{"type":20,"tag":157,"props":9548,"children":9549},{},[9550],{"type":25,"value":9551},"ai.example.com    ->  ai-service:9000\n",{"type":20,"tag":21,"props":9553,"children":9554},{},[9555,9557],{"type":25,"value":9556},"所以更准确的理解是：",{"type":20,"tag":33,"props":9558,"children":9559},{},[9560],{"type":25,"value":9561},"Nginx 不是 Docker 的替代品。Docker 负责运行应用，Nginx 负责把外部请求转发到正确的应用。",{"type":20,"tag":21,"props":9563,"children":9564},{},[9565,9567,9573,9575,9581],{"type":25,"value":9566},"如果 Nginx 直接安装在宿主机上，可以把容器端口绑定到 ",{"type":20,"tag":84,"props":9568,"children":9570},{"className":9569},[],[9571],{"type":25,"value":9572},"127.0.0.1",{"type":25,"value":9574},"，再让 Nginx 转发到本机端口。如果 Nginx 也在 Compose 里，则可以直接用服务名转发，例如 ",{"type":20,"tag":84,"props":9576,"children":9578},{"className":9577},[],[9579],{"type":25,"value":9580},"proxy_pass http:\u002F\u002Ffrontend:3000;",{"type":25,"value":110},{"type":20,"tag":1863,"props":9583,"children":9585},{"id":9584},"上传服务器的一种流程",[9586],{"type":25,"value":9584},{"type":20,"tag":21,"props":9588,"children":9589},{},[9590],{"type":25,"value":9591},"如果不使用镜像仓库，也可以先在本地构建镜像，再把镜像文件上传到服务器。",{"type":20,"tag":21,"props":9593,"children":9594},{},[9595],{"type":25,"value":9596},"整体流程是：",{"type":20,"tag":126,"props":9598,"children":9600},{"className":324,"code":9599,"language":25,"meta":8,"style":8},"构建镜像 -> 保存镜像到文件 -> scp 上传到 Linux 服务器 -> 加载镜像 -> 运行容器\n",[9601],{"type":20,"tag":84,"props":9602,"children":9603},{"__ignoreMap":8},[9604],{"type":20,"tag":157,"props":9605,"children":9606},{"class":159,"line":160},[9607],{"type":20,"tag":157,"props":9608,"children":9609},{},[9610],{"type":25,"value":9599},{"type":20,"tag":21,"props":9612,"children":9613},{},[9614],{"type":25,"value":9615},"对应命令大致如下：",{"type":20,"tag":126,"props":9617,"children":9619},{"className":238,"code":9618,"language":237,"meta":8,"style":8},"# 本地构建镜像（注意指定目标平台）\ndocker build --platform linux\u002Famd64 -t your-app:v1 .\n\n# 保存镜像为文件\ndocker save your-app:v1 -o your-app-v1.tar\n\n# 上传到服务器\nscp your-app-v1.tar user@server:\u002Ftmp\u002F\n\n# 登录服务器\nssh user@server\n\n# 加载镜像\ndocker load -i \u002Ftmp\u002Fyour-app-v1.tar\n\n# 运行容器\ndocker run -d --name your-app -p 80:3000 your-app:v1\n",[9620],{"type":20,"tag":84,"props":9621,"children":9622},{"__ignoreMap":8},[9623,9632,9663,9670,9678,9704,9711,9719,9737,9744,9752,9765,9772,9780,9802,9809,9817],{"type":20,"tag":157,"props":9624,"children":9625},{"class":159,"line":160},[9626],{"type":20,"tag":157,"props":9627,"children":9629},{"style":9628},"--shiki-default:#6A737D",[9630],{"type":25,"value":9631},"# 本地构建镜像（注意指定目标平台）\n",{"type":20,"tag":157,"props":9633,"children":9634},{"class":159,"line":169},[9635,9639,9643,9647,9651,9655,9659],{"type":20,"tag":157,"props":9636,"children":9637},{"style":248},[9638],{"type":25,"value":7626},{"type":20,"tag":157,"props":9640,"children":9641},{"style":254},[9642],{"type":25,"value":7631},{"type":20,"tag":157,"props":9644,"children":9645},{"style":260},[9646],{"type":25,"value":9332},{"type":20,"tag":157,"props":9648,"children":9649},{"style":254},[9650],{"type":25,"value":9337},{"type":20,"tag":157,"props":9652,"children":9653},{"style":260},[9654],{"type":25,"value":7636},{"type":20,"tag":157,"props":9656,"children":9657},{"style":254},[9658],{"type":25,"value":9346},{"type":20,"tag":157,"props":9660,"children":9661},{"style":254},[9662],{"type":25,"value":7646},{"type":20,"tag":157,"props":9664,"children":9665},{"class":159,"line":179},[9666],{"type":20,"tag":157,"props":9667,"children":9668},{"emptyLinePlaceholder":173},[9669],{"type":25,"value":176},{"type":20,"tag":157,"props":9671,"children":9672},{"class":159,"line":188},[9673],{"type":20,"tag":157,"props":9674,"children":9675},{"style":9628},[9676],{"type":25,"value":9677},"# 保存镜像为文件\n",{"type":20,"tag":157,"props":9679,"children":9680},{"class":159,"line":196},[9681,9685,9690,9694,9699],{"type":20,"tag":157,"props":9682,"children":9683},{"style":248},[9684],{"type":25,"value":7626},{"type":20,"tag":157,"props":9686,"children":9687},{"style":254},[9688],{"type":25,"value":9689}," save",{"type":20,"tag":157,"props":9691,"children":9692},{"style":254},[9693],{"type":25,"value":9346},{"type":20,"tag":157,"props":9695,"children":9696},{"style":260},[9697],{"type":25,"value":9698}," -o",{"type":20,"tag":157,"props":9700,"children":9701},{"style":254},[9702],{"type":25,"value":9703}," your-app-v1.tar\n",{"type":20,"tag":157,"props":9705,"children":9706},{"class":159,"line":204},[9707],{"type":20,"tag":157,"props":9708,"children":9709},{"emptyLinePlaceholder":173},[9710],{"type":25,"value":176},{"type":20,"tag":157,"props":9712,"children":9713},{"class":159,"line":213},[9714],{"type":20,"tag":157,"props":9715,"children":9716},{"style":9628},[9717],{"type":25,"value":9718},"# 上传到服务器\n",{"type":20,"tag":157,"props":9720,"children":9721},{"class":159,"line":222},[9722,9727,9732],{"type":20,"tag":157,"props":9723,"children":9724},{"style":248},[9725],{"type":25,"value":9726},"scp",{"type":20,"tag":157,"props":9728,"children":9729},{"style":254},[9730],{"type":25,"value":9731}," your-app-v1.tar",{"type":20,"tag":157,"props":9733,"children":9734},{"style":254},[9735],{"type":25,"value":9736}," user@server:\u002Ftmp\u002F\n",{"type":20,"tag":157,"props":9738,"children":9739},{"class":159,"line":440},[9740],{"type":20,"tag":157,"props":9741,"children":9742},{"emptyLinePlaceholder":173},[9743],{"type":25,"value":176},{"type":20,"tag":157,"props":9745,"children":9746},{"class":159,"line":448},[9747],{"type":20,"tag":157,"props":9748,"children":9749},{"style":9628},[9750],{"type":25,"value":9751},"# 登录服务器\n",{"type":20,"tag":157,"props":9753,"children":9754},{"class":159,"line":456},[9755,9760],{"type":20,"tag":157,"props":9756,"children":9757},{"style":248},[9758],{"type":25,"value":9759},"ssh",{"type":20,"tag":157,"props":9761,"children":9762},{"style":254},[9763],{"type":25,"value":9764}," user@server\n",{"type":20,"tag":157,"props":9766,"children":9767},{"class":159,"line":465},[9768],{"type":20,"tag":157,"props":9769,"children":9770},{"emptyLinePlaceholder":173},[9771],{"type":25,"value":176},{"type":20,"tag":157,"props":9773,"children":9774},{"class":159,"line":474},[9775],{"type":20,"tag":157,"props":9776,"children":9777},{"style":9628},[9778],{"type":25,"value":9779},"# 加载镜像\n",{"type":20,"tag":157,"props":9781,"children":9782},{"class":159,"line":483},[9783,9787,9792,9797],{"type":20,"tag":157,"props":9784,"children":9785},{"style":248},[9786],{"type":25,"value":7626},{"type":20,"tag":157,"props":9788,"children":9789},{"style":254},[9790],{"type":25,"value":9791}," load",{"type":20,"tag":157,"props":9793,"children":9794},{"style":260},[9795],{"type":25,"value":9796}," -i",{"type":20,"tag":157,"props":9798,"children":9799},{"style":254},[9800],{"type":25,"value":9801}," \u002Ftmp\u002Fyour-app-v1.tar\n",{"type":20,"tag":157,"props":9803,"children":9804},{"class":159,"line":491},[9805],{"type":20,"tag":157,"props":9806,"children":9807},{"emptyLinePlaceholder":173},[9808],{"type":25,"value":176},{"type":20,"tag":157,"props":9810,"children":9811},{"class":159,"line":499},[9812],{"type":20,"tag":157,"props":9813,"children":9814},{"style":9628},[9815],{"type":25,"value":9816},"# 运行容器\n",{"type":20,"tag":157,"props":9818,"children":9819},{"class":159,"line":508},[9820,9824,9828,9832,9837,9842,9847,9851],{"type":20,"tag":157,"props":9821,"children":9822},{"style":248},[9823],{"type":25,"value":7626},{"type":20,"tag":157,"props":9825,"children":9826},{"style":254},[9827],{"type":25,"value":2119},{"type":20,"tag":157,"props":9829,"children":9830},{"style":260},[9831],{"type":25,"value":7694},{"type":20,"tag":157,"props":9833,"children":9834},{"style":260},[9835],{"type":25,"value":9836}," --name",{"type":20,"tag":157,"props":9838,"children":9839},{"style":254},[9840],{"type":25,"value":9841}," your-app",{"type":20,"tag":157,"props":9843,"children":9844},{"style":260},[9845],{"type":25,"value":9846}," -p",{"type":20,"tag":157,"props":9848,"children":9849},{"style":254},[9850],{"type":25,"value":7728},{"type":20,"tag":157,"props":9852,"children":9853},{"style":254},[9854],{"type":25,"value":8554},{"type":20,"tag":21,"props":9856,"children":9857},{},[9858],{"type":25,"value":9859},"这种方式适合个人项目、小项目或刚开始学习部署的时候。它的优点是直观，不需要先配置镜像仓库；缺点是手工步骤多，版本管理和回滚不够方便。",{"type":20,"tag":21,"props":9861,"children":9862},{},[9863],{"type":25,"value":9864},"更正式的生产环境里，通常会把镜像推送到镜像仓库，例如 Docker Hub、GitHub Container Registry 或私有镜像仓库，然后让服务器直接拉取指定版本的镜像：",{"type":20,"tag":126,"props":9866,"children":9868},{"className":324,"code":9867,"language":25,"meta":8,"style":8},"本地或 CI 构建镜像 -> 推送到镜像仓库 -> 服务器拉取镜像 -> 重启容器或 Compose 项目\n",[9869],{"type":20,"tag":84,"props":9870,"children":9871},{"__ignoreMap":8},[9872],{"type":20,"tag":157,"props":9873,"children":9874},{"class":159,"line":160},[9875],{"type":20,"tag":157,"props":9876,"children":9877},{},[9878],{"type":25,"value":9867},{"type":20,"tag":21,"props":9880,"children":9881},{},[9882],{"type":25,"value":9883},"这样做更利于版本追踪、自动化部署和回滚。",{"type":20,"tag":1863,"props":9885,"children":9887},{"id":9886},"入门时最容易踩的坑",[9888],{"type":25,"value":9886},{"type":20,"tag":5719,"props":9890,"children":9891},{},[9892,9920,9959,9980,10008],{"type":20,"tag":48,"props":9893,"children":9894},{},[9895,9907,9911,9913,9918],{"type":20,"tag":33,"props":9896,"children":9897},{},[9898,9900,9905],{"type":25,"value":9899},"把 ",{"type":20,"tag":84,"props":9901,"children":9903},{"className":9902},[],[9904],{"type":25,"value":8802},{"type":25,"value":9906}," 理解错",{"type":20,"tag":9908,"props":9909,"children":9910},"br",{},[],{"type":25,"value":9912},"在容器内部，",{"type":20,"tag":84,"props":9914,"children":9916},{"className":9915},[],[9917],{"type":25,"value":8802},{"type":25,"value":9919}," 通常指容器自己。容器访问宿主机、其他容器或外部数据库时，要根据网络环境使用正确地址。",{"type":20,"tag":48,"props":9921,"children":9922},{},[9923,9928,9931,9936,9938,9944,9946,9951,9953,9958],{"type":20,"tag":33,"props":9924,"children":9925},{},[9926],{"type":25,"value":9927},"端口方向写反",{"type":20,"tag":9908,"props":9929,"children":9930},{},[],{"type":20,"tag":84,"props":9932,"children":9934},{"className":9933},[],[9935],{"type":25,"value":7790},{"type":25,"value":9937}," 是 ",{"type":20,"tag":84,"props":9939,"children":9941},{"className":9940},[],[9942],{"type":25,"value":9943},"宿主机端口:容器端口",{"type":25,"value":9945},"。如果应用在容器里监听 ",{"type":20,"tag":84,"props":9947,"children":9949},{"className":9948},[],[9950],{"type":25,"value":7344},{"type":25,"value":9952},"，右边就应该是 ",{"type":20,"tag":84,"props":9954,"children":9956},{"className":9955},[],[9957],{"type":25,"value":7344},{"type":25,"value":110},{"type":20,"tag":48,"props":9960,"children":9961},{},[9962,9967,9970,9972,9978],{"type":20,"tag":33,"props":9963,"children":9964},{},[9965],{"type":25,"value":9966},"把敏感服务暴露到公网",{"type":20,"tag":9908,"props":9968,"children":9969},{},[],{"type":25,"value":9971},"数据库、Redis、内部管理后台不要随便 ",{"type":20,"tag":84,"props":9973,"children":9975},{"className":9974},[],[9976],{"type":25,"value":9977},"-p",{"type":25,"value":9979}," 到公网。能走内网或 Compose 网络的，就不要暴露到外部。",{"type":20,"tag":48,"props":9981,"children":9982},{},[9983,9988,9991,9993,9998,10000,10006],{"type":20,"tag":33,"props":9984,"children":9985},{},[9986],{"type":25,"value":9987},"镜像里复制了太多无关文件",{"type":20,"tag":9908,"props":9989,"children":9990},{},[],{"type":25,"value":9992},"忘记写 ",{"type":20,"tag":84,"props":9994,"children":9996},{"className":9995},[],[9997],{"type":25,"value":7398},{"type":25,"value":9999}," 会让镜像变大，也可能把 ",{"type":20,"tag":84,"props":10001,"children":10003},{"className":10002},[],[10004],{"type":25,"value":10005},".env",{"type":25,"value":10007}," 等敏感文件打进镜像。",{"type":20,"tag":48,"props":10009,"children":10010},{},[10011,10023,10026,10028,10033,10034,10040],{"type":20,"tag":33,"props":10012,"children":10013},{},[10014,10016,10021],{"type":25,"value":10015},"只会 ",{"type":20,"tag":84,"props":10017,"children":10019},{"className":10018},[],[10020],{"type":25,"value":7757},{"type":25,"value":10022},"，不会看日志",{"type":20,"tag":9908,"props":10024,"children":10025},{},[],{"type":25,"value":10027},"容器启动失败时，先看 ",{"type":20,"tag":84,"props":10029,"children":10031},{"className":10030},[],[10032],{"type":25,"value":8494},{"type":25,"value":7043},{"type":20,"tag":84,"props":10035,"children":10037},{"className":10036},[],[10038],{"type":25,"value":10039},"docker logs 容器名",{"type":25,"value":10041},"，通常比反复重启更有效。",{"type":20,"tag":1863,"props":10043,"children":10045},{"id":10044},"总结",[10046],{"type":25,"value":10044},{"type":20,"tag":21,"props":10048,"children":10049},{},[10050],{"type":25,"value":10051},"Docker 入门可以先抓住这几件事：",{"type":20,"tag":5719,"props":10053,"children":10054},{},[10055,10060,10104,10122,10140,10150,10155],{"type":20,"tag":48,"props":10056,"children":10057},{},[10058],{"type":25,"value":10059},"镜像是静态的构建产物，容器是镜像运行起来后的实例。",{"type":20,"tag":48,"props":10061,"children":10062},{},[10063,10068,10070,10076,10077,10083,10084,10090,10091,10096,10097,10102],{"type":20,"tag":84,"props":10064,"children":10066},{"className":10065},[],[10067],{"type":25,"value":7141},{"type":25,"value":10069}," 是构建镜像的说明书，",{"type":20,"tag":84,"props":10071,"children":10073},{"className":10072},[],[10074],{"type":25,"value":10075},"FROM",{"type":25,"value":965},{"type":20,"tag":84,"props":10078,"children":10080},{"className":10079},[],[10081],{"type":25,"value":10082},"WORKDIR",{"type":25,"value":965},{"type":20,"tag":84,"props":10085,"children":10087},{"className":10086},[],[10088],{"type":25,"value":10089},"COPY",{"type":25,"value":965},{"type":20,"tag":84,"props":10092,"children":10094},{"className":10093},[],[10095],{"type":25,"value":7374},{"type":25,"value":965},{"type":20,"tag":84,"props":10098,"children":10100},{"className":10099},[],[10101],{"type":25,"value":7385},{"type":25,"value":10103}," 是最常见的指令。",{"type":20,"tag":48,"props":10105,"children":10106},{},[10107,10113,10115,10120],{"type":20,"tag":84,"props":10108,"children":10110},{"className":10109},[],[10111],{"type":25,"value":10112},"docker build",{"type":25,"value":10114}," 用来构建镜像，",{"type":20,"tag":84,"props":10116,"children":10118},{"className":10117},[],[10119],{"type":25,"value":7757},{"type":25,"value":10121}," 用来启动容器。",{"type":20,"tag":48,"props":10123,"children":10124},{},[10125,10127,10132,10134,10139],{"type":25,"value":10126},"端口映射里，",{"type":20,"tag":84,"props":10128,"children":10130},{"className":10129},[],[10131],{"type":25,"value":9943},{"type":25,"value":10133},"，例如 ",{"type":20,"tag":84,"props":10135,"children":10137},{"className":10136},[],[10138],{"type":25,"value":7828},{"type":25,"value":110},{"type":20,"tag":48,"props":10141,"children":10142},{},[10143,10148],{"type":20,"tag":84,"props":10144,"children":10146},{"className":10145},[],[10147],{"type":25,"value":7018},{"type":25,"value":10149}," 适合管理多容器项目，服务之间可以通过服务名访问。",{"type":20,"tag":48,"props":10151,"children":10152},{},[10153],{"type":25,"value":10154},"Nginx 不是 Docker 的替代品，它通常负责接收外部请求并转发到不同容器或服务。",{"type":20,"tag":48,"props":10156,"children":10157},{},[10158],{"type":25,"value":10159},"正式部署时，把镜像构建、推送、拉取和重启流程标准化，比直接在服务器上手动安装依赖更可靠。",{"type":20,"tag":21,"props":10161,"children":10162},{},[10163],{"type":25,"value":10164},"学 Docker 不需要一开始就把所有命令都背下来。先理解镜像、容器、Dockerfile、端口映射和日志查看，再拿一个真实项目从构建到部署跑通一遍，很多概念就会自然连起来。",{"type":20,"tag":1863,"props":10166,"children":10167},{"id":4926},[10168],{"type":25,"value":4926},{"type":20,"tag":44,"props":10170,"children":10171},{},[10172,10182,10192,10202,10212,10222],{"type":20,"tag":48,"props":10173,"children":10174},{},[10175],{"type":20,"tag":101,"props":10176,"children":10179},{"href":10177,"rel":10178},"https:\u002F\u002Fdocs.docker.com\u002Fbuild\u002Fbuilding\u002Fbest-practices\u002F",[105],[10180],{"type":25,"value":10181},"Docker Docs: Dockerfile best practices",{"type":20,"tag":48,"props":10183,"children":10184},{},[10185],{"type":20,"tag":101,"props":10186,"children":10189},{"href":10187,"rel":10188},"https:\u002F\u002Fdocs.docker.com\u002Fget-started\u002Fdocker-concepts\u002Frunning-containers\u002Fpublishing-ports\u002F",[105],[10190],{"type":25,"value":10191},"Docker Docs: Publishing and exposing ports",{"type":20,"tag":48,"props":10193,"children":10194},{},[10195],{"type":20,"tag":101,"props":10196,"children":10199},{"href":10197,"rel":10198},"https:\u002F\u002Fdocs.docker.com\u002Fcompose\u002Fhow-tos\u002Fnetworking\u002F",[105],[10200],{"type":25,"value":10201},"Docker Docs: Networking in Compose",{"type":20,"tag":48,"props":10203,"children":10204},{},[10205],{"type":20,"tag":101,"props":10206,"children":10209},{"href":10207,"rel":10208},"https:\u002F\u002Fdocs.docker.com\u002Fbuild\u002Fbuilding\u002Fmulti-platform\u002F",[105],[10210],{"type":25,"value":10211},"Docker Docs: Multi-platform builds",{"type":20,"tag":48,"props":10213,"children":10214},{},[10215],{"type":20,"tag":101,"props":10216,"children":10219},{"href":10217,"rel":10218},"https:\u002F\u002Fdocs.docker.com\u002Freference\u002Fcli\u002Fdocker\u002Fimage\u002Fsave\u002F",[105],[10220],{"type":25,"value":10221},"Docker Docs: docker image save",{"type":20,"tag":48,"props":10223,"children":10224},{},[10225],{"type":20,"tag":101,"props":10226,"children":10229},{"href":10227,"rel":10228},"https:\u002F\u002Fdocs.docker.com\u002Freference\u002Fcli\u002Fdocker\u002Fimage\u002Fload\u002F",[105],[10230],{"type":25,"value":10231},"Docker Docs: docker image load",{"type":20,"tag":1785,"props":10233,"children":10234},{},[10235],{"type":25,"value":1789},{"title":8,"searchDepth":169,"depth":169,"links":10237},[10238,10239,10240,10241,10242,10243,10244,10253,10254,10255,10256,10257,10258],{"id":6791,"depth":169,"text":6794},{"id":6853,"depth":169,"text":6856},{"id":7028,"depth":169,"text":7028},{"id":7129,"depth":169,"text":7132},{"id":7597,"depth":169,"text":7597},{"id":7971,"depth":169,"text":7971},{"id":8407,"depth":169,"text":8410,"children":10245},[10246,10247,10248,10249,10250,10251,10252],{"id":8418,"depth":179,"text":8418},{"id":8499,"depth":179,"text":8499},{"id":8576,"depth":179,"text":8576},{"id":8834,"depth":179,"text":8834},{"id":8934,"depth":179,"text":8934},{"id":9052,"depth":179,"text":9052},{"id":9184,"depth":179,"text":9184},{"id":9252,"depth":169,"text":9255},{"id":9444,"depth":169,"text":9447},{"id":9584,"depth":169,"text":9584},{"id":9886,"depth":169,"text":9886},{"id":10044,"depth":169,"text":10044},{"id":4926,"depth":169,"text":4926},"content:articles:devops:Docker.md","articles\u002Fdevops\u002FDocker.md","articles\u002Fdevops\u002FDocker",{"_path":10263,"_dir":1808,"_draft":7,"_partial":7,"_locale":8,"title":10264,"description":10265,"date":10266,"tags":10267,"body":10268,"_type":1800,"_id":11114,"_source":1802,"_file":11115,"_stem":11116,"_extension":1805},"\u002Farticles\u002Fai\u002Fmpl-hydrophobicity","用MLP预测氨基酸亲疏水性：一个完整的机器学习小实验-PyTorch版","使用PyTorch构建MLP模型，结合RDKit Morgan指纹特征工程，通过留一交叉验证预测20种标准氨基酸的亲疏水性，覆盖数据处理、模型训练、正则化与评估的完整流程。","2026-05-11",[13,1813],{"type":17,"children":10269,"toc":11097},[10270,10275,10280,10308,10313,10321,10326,10331,10336,10344,10349,10390,10395,10401,10406,10414,10438,10443,10448,10456,10461,10466,10473,10478,10486,10491,10496,10501,10509,10514,10522,10527,10532,10537,10569,10574,10582,10593,10603,10608,10613,10618,10623,10628,10652,10657,10665,10670,10675,10687,10695,10700,10708,10712,10758,10763,10768,10776,10781,10789,10794,10799,10804,10809,10815,10820,10825,10832,10837,10845,10850,10856,10861,10866,10872,10877,10896,10901,10907,10912,10917,10925,10930,10948,10953,10958,10963,10968,10996,11001,11006,11011,11029,11033,11038,11046,11051,11079,11084],{"type":20,"tag":21,"props":10271,"children":10272},{},[10273],{"type":25,"value":10274},"最近我做了一个很小的机器学习项目：用 MLP 预测 20 种标准氨基酸的亲疏水性。",{"type":20,"tag":21,"props":10276,"children":10277},{},[10278],{"type":25,"value":10279},"这个项目的数据量很小，模型也不复杂，但它刚好覆盖了一个机器学习实验最重要的几个环节：",{"type":20,"tag":44,"props":10281,"children":10282},{},[10283,10288,10293,10298,10303],{"type":20,"tag":48,"props":10284,"children":10285},{},[10286],{"type":25,"value":10287},"原始数据如何变成模型能理解的数值特征",{"type":20,"tag":48,"props":10289,"children":10290},{},[10291],{"type":25,"value":10292},"神经网络如何通过损失函数和优化器学习",{"type":20,"tag":48,"props":10294,"children":10295},{},[10296],{"type":25,"value":10297},"小数据集为什么容易过拟合",{"type":20,"tag":48,"props":10299,"children":10300},{},[10301],{"type":25,"value":10302},"为什么评估方式比单次准确率更重要",{"type":20,"tag":48,"props":10304,"children":10305},{},[10306],{"type":25,"value":10307},"为什么要做 baseline 和 正则化",{"type":20,"tag":21,"props":10309,"children":10310},{},[10311],{"type":25,"value":10312},"项目地址中的核心流程是：",{"type":20,"tag":126,"props":10314,"children":10316},{"code":10315},"SMILES 字符串 -> RDKit 解析 -> Morgan 指纹 -> MLP 分类 -> 留一交叉验证\n",[10317],{"type":20,"tag":84,"props":10318,"children":10319},{"__ignoreMap":8},[10320],{"type":25,"value":10315},{"type":20,"tag":1863,"props":10322,"children":10324},{"id":10323},"项目目标",[10325],{"type":25,"value":10323},{"type":20,"tag":21,"props":10327,"children":10328},{},[10329],{"type":25,"value":10330},"给定一个氨基酸的分子结构，用机器学习模型预测它是疏水还是亲水。",{"type":20,"tag":21,"props":10332,"children":10333},{},[10334],{"type":25,"value":10335},"数据文件中每一行是一种氨基酸，例如：",{"type":20,"tag":126,"props":10337,"children":10339},{"code":10338},"name,abbreviation,three_letter,smiles,hydrophobic\n丙氨酸,Ala,Ala,CC(N)C(=O)O,1\n丝氨酸,Ser,Ser,NC(CO)C(=O)O,0\n",[10340],{"type":20,"tag":84,"props":10341,"children":10342},{"__ignoreMap":8},[10343],{"type":25,"value":10338},{"type":20,"tag":21,"props":10345,"children":10346},{},[10347],{"type":25,"value":10348},"其中：",{"type":20,"tag":44,"props":10350,"children":10351},{},[10352,10363],{"type":20,"tag":48,"props":10353,"children":10354},{},[10355,10361],{"type":20,"tag":84,"props":10356,"children":10358},{"className":10357},[],[10359],{"type":25,"value":10360},"smiles",{"type":25,"value":10362}," 是氨基酸的分子结构表示",{"type":20,"tag":48,"props":10364,"children":10365},{},[10366,10372,10374,10380,10382,10388],{"type":20,"tag":84,"props":10367,"children":10369},{"className":10368},[],[10370],{"type":25,"value":10371},"hydrophobic",{"type":25,"value":10373}," 是标签，",{"type":20,"tag":84,"props":10375,"children":10377},{"className":10376},[],[10378],{"type":25,"value":10379},"1",{"type":25,"value":10381}," 表示疏水，",{"type":20,"tag":84,"props":10383,"children":10385},{"className":10384},[],[10386],{"type":25,"value":10387},"0",{"type":25,"value":10389}," 表示亲水",{"type":20,"tag":21,"props":10391,"children":10392},{},[10393],{"type":25,"value":10394},"这是一个二分类问题。",{"type":20,"tag":1863,"props":10396,"children":10398},{"id":10397},"为什么不能直接把-smiles-喂给神经网络",[10399],{"type":25,"value":10400},"为什么不能直接把 SMILES 喂给神经网络",{"type":20,"tag":21,"props":10402,"children":10403},{},[10404],{"type":25,"value":10405},"SMILES 是一种文本形式的分子表示，例如丙氨酸的 SMILES 是：",{"type":20,"tag":126,"props":10407,"children":10409},{"code":10408},"CC(N)C(=O)O\n",[10410],{"type":20,"tag":84,"props":10411,"children":10412},{"__ignoreMap":8},[10413],{"type":25,"value":10408},{"type":20,"tag":21,"props":10415,"children":10416},{},[10417,10419,10424,10425,10430,10431,10436],{"type":25,"value":10418},"但是神经网络本质上处理的是数值张量。它并不直接理解 ",{"type":20,"tag":84,"props":10420,"children":10422},{"className":10421},[],[10423],{"type":25,"value":2210},{"type":25,"value":965},{"type":20,"tag":84,"props":10426,"children":10428},{"className":10427},[],[10429],{"type":25,"value":2244},{"type":25,"value":965},{"type":20,"tag":84,"props":10432,"children":10434},{"className":10433},[],[10435],{"type":25,"value":2227},{"type":25,"value":10437}," 这些字符代表什么化学意义。",{"type":20,"tag":21,"props":10439,"children":10440},{},[10441],{"type":25,"value":10442},"所以第一步要做特征工程：把 SMILES 转换成模型可以学习的数值向量。",{"type":20,"tag":21,"props":10444,"children":10445},{},[10446],{"type":25,"value":10447},"在这个项目中，我使用 RDKit 生成 Morgan 指纹：",{"type":20,"tag":126,"props":10449,"children":10451},{"code":10450},"def smiles_to_morgan_bits(\n    smiles: str,\n    radius: int = 2,\n    fp_size: int = 2048,\n) -> torch.Tensor:\n    mol = Chem.MolFromSmiles(smiles)\n    if mol is None:\n        raise ValueError(f\"Invalid SMILES: {smiles}\")\n​\n    generator = AllChem.GetMorganGenerator(radius=radius, fpSize=fp_size)\n    fingerprint = generator.GetFingerprint(mol)\n    return torch.tensor(\n        list(map(int, fingerprint.ToBitString())),\n        dtype=torch.float32,\n    )\n",[10452],{"type":20,"tag":84,"props":10453,"children":10454},{"__ignoreMap":8},[10455],{"type":25,"value":10450},{"type":20,"tag":21,"props":10457,"children":10458},{},[10459],{"type":25,"value":10460},"Morgan 指纹可以理解为：把分子中的局部结构模式编码成一个固定长度的 0\u002F1 向量。",{"type":20,"tag":21,"props":10462,"children":10463},{},[10464],{"type":25,"value":10465},"比如原始输入是：",{"type":20,"tag":126,"props":10467,"children":10468},{"code":10408},[10469],{"type":20,"tag":84,"props":10470,"children":10471},{"__ignoreMap":8},[10472],{"type":25,"value":10408},{"type":20,"tag":21,"props":10474,"children":10475},{},[10476],{"type":25,"value":10477},"经过特征工程后变成类似这样的向量：",{"type":20,"tag":126,"props":10479,"children":10481},{"code":10480},"[0, 1, 0, 0, 1, ..., 0]\n",[10482],{"type":20,"tag":84,"props":10483,"children":10484},{"__ignoreMap":8},[10485],{"type":25,"value":10480},{"type":20,"tag":21,"props":10487,"children":10488},{},[10489],{"type":25,"value":10490},"在这个项目中，每个氨基酸最终都会变成一个 2048 维的向量。",{"type":20,"tag":1863,"props":10492,"children":10494},{"id":10493},"模型结构",[10495],{"type":25,"value":10493},{"type":20,"tag":21,"props":10497,"children":10498},{},[10499],{"type":25,"value":10500},"模型使用的是一个很小的 MLP：",{"type":20,"tag":126,"props":10502,"children":10504},{"code":10503},"Input (2048) -> Linear -> ReLU -> Dropout -> Linear -> Output (1)\n",[10505],{"type":20,"tag":84,"props":10506,"children":10507},{"__ignoreMap":8},[10508],{"type":25,"value":10503},{"type":20,"tag":21,"props":10510,"children":10511},{},[10512],{"type":25,"value":10513},"对应代码：",{"type":20,"tag":126,"props":10515,"children":10517},{"code":10516},"model = HydroMLP(in_dim=2048, hidden_layer_sizes=(32,), dropout=0.1)\n",[10518],{"type":20,"tag":84,"props":10519,"children":10520},{"__ignoreMap":8},[10521],{"type":25,"value":10516},{"type":20,"tag":21,"props":10523,"children":10524},{},[10525],{"type":25,"value":10526},"这里有一个重要选择：模型没有设计得很大。",{"type":20,"tag":21,"props":10528,"children":10529},{},[10530],{"type":25,"value":10531},"原因是数据只有 20 条，而输入特征却有 2048 维。如果模型太大，它很容易把训练集记住，而不是真的学到亲疏水性的规律。这就是过拟合。",{"type":20,"tag":21,"props":10533,"children":10534},{},[10535],{"type":25,"value":10536},"所以我做了两个约束：",{"type":20,"tag":44,"props":10538,"children":10539},{},[10540,10551],{"type":20,"tag":48,"props":10541,"children":10542},{},[10543,10545],{"type":25,"value":10544},"使用较小的隐藏层：",{"type":20,"tag":84,"props":10546,"children":10548},{"className":10547},[],[10549],{"type":25,"value":10550},"2048 -> 32 -> 1",{"type":20,"tag":48,"props":10552,"children":10553},{},[10554,10556,10562,10563],{"type":25,"value":10555},"加入正则化：",{"type":20,"tag":84,"props":10557,"children":10559},{"className":10558},[],[10560],{"type":25,"value":10561},"Dropout",{"type":25,"value":7043},{"type":20,"tag":84,"props":10564,"children":10566},{"className":10565},[],[10567],{"type":25,"value":10568},"weight_decay",{"type":20,"tag":21,"props":10570,"children":10571},{},[10572],{"type":25,"value":10573},"训练时使用：",{"type":20,"tag":126,"props":10575,"children":10577},{"code":10576},"criterion = nn.BCEWithLogitsLoss()\noptimizer = torch.optim.Adam(model.parameters(), lr=0.003, weight_decay=1e-3)\n",[10578],{"type":20,"tag":84,"props":10579,"children":10580},{"__ignoreMap":8},[10581],{"type":25,"value":10576},{"type":20,"tag":21,"props":10583,"children":10584},{},[10585,10591],{"type":20,"tag":84,"props":10586,"children":10588},{"className":10587},[],[10589],{"type":25,"value":10590},"BCEWithLogitsLoss",{"type":25,"value":10592}," 适合二分类任务。它内部会把模型输出的 logit 转换成概率，再计算二分类交叉熵。",{"type":20,"tag":21,"props":10594,"children":10595},{},[10596,10601],{"type":20,"tag":84,"props":10597,"children":10599},{"className":10598},[],[10600],{"type":25,"value":10568},{"type":25,"value":10602}," 是 L2 正则化，可以限制模型参数不要变得过大，从而降低过拟合风险。",{"type":20,"tag":1863,"props":10604,"children":10606},{"id":10605},"为什么使用留一交叉验证",[10607],{"type":25,"value":10605},{"type":20,"tag":21,"props":10609,"children":10610},{},[10611],{"type":25,"value":10612},"一开始我用的是普通的训练集\u002F测试集划分，例如 15 条训练、5 条测试。",{"type":20,"tag":21,"props":10614,"children":10615},{},[10616],{"type":25,"value":10617},"但这个项目只有 20 条数据。测试集只有 5 条时，结果非常容易受随机划分影响。某一次准确率高，不一定说明模型真的好；某一次准确率低，也不一定说明模型完全没学到东西。",{"type":20,"tag":21,"props":10619,"children":10620},{},[10621],{"type":25,"value":10622},"因此我改成了留一交叉验证。",{"type":20,"tag":21,"props":10624,"children":10625},{},[10626],{"type":25,"value":10627},"留一交叉验证的做法是：",{"type":20,"tag":5719,"props":10629,"children":10631},{"start":10630},0,[10632,10637,10642,10647],{"type":20,"tag":48,"props":10633,"children":10634},{},[10635],{"type":25,"value":10636},"每次拿 1 个氨基酸作为测试样本",{"type":20,"tag":48,"props":10638,"children":10639},{},[10640],{"type":25,"value":10641},"剩下 19 个氨基酸作为训练样本",{"type":20,"tag":48,"props":10643,"children":10644},{},[10645],{"type":25,"value":10646},"重复 20 次，让每个氨基酸都当一次测试样本",{"type":20,"tag":48,"props":10648,"children":10649},{},[10650],{"type":25,"value":10651},"汇总 20 次预测结果，计算总体准确率",{"type":20,"tag":21,"props":10653,"children":10654},{},[10655],{"type":25,"value":10656},"代码中的核心逻辑是：",{"type":20,"tag":126,"props":10658,"children":10660},{"code":10659},"for test_idx in range(len(X)):\n    train_idx = [i for i in range(len(X)) if i != test_idx]\n​\n    model = train_model(X[train_idx], y[train_idx])\n    model.eval()\n​\n    with torch.no_grad():\n        logit = model(X[test_idx].unsqueeze(0))\n        prob = torch.sigmoid(logit).item()\n        pred = int(prob > 0.5)\n",[10661],{"type":20,"tag":84,"props":10662,"children":10663},{"__ignoreMap":8},[10664],{"type":25,"value":10659},{"type":20,"tag":21,"props":10666,"children":10667},{},[10668],{"type":25,"value":10669},"对于小数据集来说，留一交叉验证比单次随机划分更适合用来观察模型表现。",{"type":20,"tag":1863,"props":10671,"children":10673},{"id":10672},"如何复现",[10674],{"type":25,"value":10672},{"type":20,"tag":21,"props":10676,"children":10677},{},[10678,10680,10685],{"type":25,"value":10679},"项目使用 ",{"type":20,"tag":84,"props":10681,"children":10683},{"className":10682},[],[10684],{"type":25,"value":1884},{"type":25,"value":10686}," 管理依赖。安装依赖后，直接运行主脚本：",{"type":20,"tag":126,"props":10688,"children":10690},{"code":10689},"uv sync\nuv run python main.py\n",[10691],{"type":20,"tag":84,"props":10692,"children":10693},{"__ignoreMap":8},[10694],{"type":25,"value":10689},{"type":20,"tag":21,"props":10696,"children":10697},{},[10698],{"type":25,"value":10699},"项目结构如下：",{"type":20,"tag":126,"props":10701,"children":10703},{"code":10702},"├── data\u002F\n│   └── amino_acids.csv\n├── src\u002F\n│   ├── features.py\n│   └── model.py\n├── main.py\n└── pyproject.toml\n",[10704],{"type":20,"tag":84,"props":10705,"children":10706},{"__ignoreMap":8},[10707],{"type":25,"value":10702},{"type":20,"tag":21,"props":10709,"children":10710},{},[10711],{"type":25,"value":10348},{"type":20,"tag":44,"props":10713,"children":10714},{},[10715,10726,10737,10748],{"type":20,"tag":48,"props":10716,"children":10717},{},[10718,10724],{"type":20,"tag":84,"props":10719,"children":10721},{"className":10720},[],[10722],{"type":25,"value":10723},"data\u002Famino_acids.csv",{"type":25,"value":10725}," 保存 20 种氨基酸的数据和标签",{"type":20,"tag":48,"props":10727,"children":10728},{},[10729,10735],{"type":20,"tag":84,"props":10730,"children":10732},{"className":10731},[],[10733],{"type":25,"value":10734},"src\u002Ffeatures.py",{"type":25,"value":10736}," 负责把 SMILES 转换成 Morgan 指纹",{"type":20,"tag":48,"props":10738,"children":10739},{},[10740,10746],{"type":20,"tag":84,"props":10741,"children":10743},{"className":10742},[],[10744],{"type":25,"value":10745},"src\u002Fmodel.py",{"type":25,"value":10747}," 定义 MLP 模型",{"type":20,"tag":48,"props":10749,"children":10750},{},[10751,10756],{"type":20,"tag":84,"props":10752,"children":10754},{"className":10753},[],[10755],{"type":25,"value":144},{"type":25,"value":10757}," 负责训练、留一交叉验证和结果输出",{"type":20,"tag":1863,"props":10759,"children":10761},{"id":10760},"实验结果",[10762],{"type":25,"value":10760},{"type":20,"tag":21,"props":10764,"children":10765},{},[10766],{"type":25,"value":10767},"当前运行结果是：",{"type":20,"tag":126,"props":10769,"children":10771},{"code":10770},"留一交叉验证准确率: 65.0% (13\u002F20)\n",[10772],{"type":20,"tag":84,"props":10773,"children":10774},{"__ignoreMap":8},[10775],{"type":25,"value":10770},{"type":20,"tag":21,"props":10777,"children":10778},{},[10779],{"type":25,"value":10780},"部分预测结果如下：",{"type":20,"tag":126,"props":10782,"children":10784},{"code":10783},"✓ 丙氨酸: 预测=疏水 (疏水概率 0.70), 真实=疏水\n✓ 缬氨酸: 预测=疏水 (疏水概率 0.61), 真实=疏水\n✗ 亮氨酸: 预测=亲水 (疏水概率 0.31), 真实=疏水\n✓ 丝氨酸: 预测=亲水 (疏水概率 0.29), 真实=亲水\n✗ 酪氨酸: 预测=疏水 (疏水概率 0.97), 真实=亲水\n✓ 精氨酸: 预测=亲水 (疏水概率 0.02), 真实=亲水\n",[10785],{"type":20,"tag":84,"props":10786,"children":10787},{"__ignoreMap":8},[10788],{"type":25,"value":10783},{"type":20,"tag":21,"props":10790,"children":10791},{},[10792],{"type":25,"value":10793},"这个结果说明模型确实学到了一部分规律，但还不稳定。",{"type":20,"tag":21,"props":10795,"children":10796},{},[10797],{"type":25,"value":10798},"比如它能识别一些明显的亲水氨基酸，也能识别一部分疏水氨基酸。但对于边界比较模糊，或者结构上有特殊基团的氨基酸，仍然容易出错。",{"type":20,"tag":21,"props":10800,"children":10801},{},[10802],{"type":25,"value":10803},"这也提醒我：不能只看训练集损失。如果训练损失很低，但留一交叉验证表现一般，那模型很可能只是记住了训练样本。",{"type":20,"tag":1863,"props":10805,"children":10807},{"id":10806},"我从这个项目学到了什么",[10808],{"type":25,"value":10806},{"type":20,"tag":28,"props":10810,"children":10812},{"id":10811},"_1-特征工程是机器学习的入口",[10813],{"type":25,"value":10814},"1. 特征工程是机器学习的入口",{"type":20,"tag":21,"props":10816,"children":10817},{},[10818],{"type":25,"value":10819},"模型并不是直接学习 SMILES 字符串，而是学习 Morgan 指纹。",{"type":20,"tag":21,"props":10821,"children":10822},{},[10823],{"type":25,"value":10824},"所以这个项目真正的输入不是：",{"type":20,"tag":126,"props":10826,"children":10827},{"code":10408},[10828],{"type":20,"tag":84,"props":10829,"children":10830},{"__ignoreMap":8},[10831],{"type":25,"value":10408},{"type":20,"tag":21,"props":10833,"children":10834},{},[10835],{"type":25,"value":10836},"而是：",{"type":20,"tag":126,"props":10838,"children":10840},{"code":10839},"2048 维 Morgan 指纹向量\n",[10841],{"type":20,"tag":84,"props":10842,"children":10843},{"__ignoreMap":8},[10844],{"type":25,"value":10839},{"type":20,"tag":21,"props":10846,"children":10847},{},[10848],{"type":25,"value":10849},"特征工程决定了模型能看到什么信息。",{"type":20,"tag":28,"props":10851,"children":10853},{"id":10852},"_2-小数据项目里评估比训练更重要",[10854],{"type":25,"value":10855},"2. 小数据项目里，评估比训练更重要",{"type":20,"tag":21,"props":10857,"children":10858},{},[10859],{"type":25,"value":10860},"只有 20 条数据时，模型很容易把训练集背下来。",{"type":20,"tag":21,"props":10862,"children":10863},{},[10864],{"type":25,"value":10865},"如果只看训练损失，很容易得到错误信心。留一交叉验证虽然不能让数据变多，但能让评估更完整。",{"type":20,"tag":28,"props":10867,"children":10869},{"id":10868},"_3-正则化是在限制模型死记硬背",[10870],{"type":25,"value":10871},"3. 正则化是在限制模型死记硬背",{"type":20,"tag":21,"props":10873,"children":10874},{},[10875],{"type":25,"value":10876},"这个项目里使用了两种正则化方式：",{"type":20,"tag":44,"props":10878,"children":10879},{},[10880,10888],{"type":20,"tag":48,"props":10881,"children":10882},{},[10883],{"type":20,"tag":84,"props":10884,"children":10886},{"className":10885},[],[10887],{"type":25,"value":10561},{"type":20,"tag":48,"props":10889,"children":10890},{},[10891],{"type":20,"tag":84,"props":10892,"children":10894},{"className":10893},[],[10895],{"type":25,"value":10568},{"type":20,"tag":21,"props":10897,"children":10898},{},[10899],{"type":25,"value":10900},"它们的目的不是让模型更复杂，而是让模型更克制。",{"type":20,"tag":28,"props":10902,"children":10904},{"id":10903},"_4-baseline-很重要",[10905],{"type":25,"value":10906},"4. Baseline 很重要",{"type":20,"tag":21,"props":10908,"children":10909},{},[10910],{"type":25,"value":10911},"这个项目目前已经有了 MLP，但下一步应该做 baseline。",{"type":20,"tag":21,"props":10913,"children":10914},{},[10915],{"type":25,"value":10916},"Baseline 就是一个简单参照模型，用来回答一个问题：",{"type":20,"tag":6665,"props":10918,"children":10919},{},[10920],{"type":20,"tag":21,"props":10921,"children":10922},{},[10923],{"type":25,"value":10924},"我的复杂模型真的比简单方法更好吗？",{"type":20,"tag":21,"props":10926,"children":10927},{},[10928],{"type":25,"value":10929},"可以尝试的 baseline 包括：",{"type":20,"tag":44,"props":10931,"children":10932},{},[10933,10938,10943],{"type":20,"tag":48,"props":10934,"children":10935},{},[10936],{"type":25,"value":10937},"多数类 baseline：永远预测数据中数量更多的类别",{"type":20,"tag":48,"props":10939,"children":10940},{},[10941],{"type":25,"value":10942},"Logistic Regression：使用同样的 Morgan 指纹，但只训练线性分类器",{"type":20,"tag":48,"props":10944,"children":10945},{},[10946],{"type":25,"value":10947},"RDKit 描述符模型：使用 LogP、分子量、TPSA、氢键供体\u002F受体数量等少量特征",{"type":20,"tag":21,"props":10949,"children":10950},{},[10951],{"type":25,"value":10952},"如果一个简单 baseline 就能达到和 MLP 接近的准确率，那说明 MLP 可能并没有带来明显收益。",{"type":20,"tag":1863,"props":10954,"children":10956},{"id":10955},"项目局限",[10957],{"type":25,"value":10955},{"type":20,"tag":21,"props":10959,"children":10960},{},[10961],{"type":25,"value":10962},"这个项目是一个学习项目，不适合直接当作严肃的化学预测模型。",{"type":20,"tag":21,"props":10964,"children":10965},{},[10966],{"type":25,"value":10967},"主要局限有：",{"type":20,"tag":44,"props":10969,"children":10970},{},[10971,10976,10981,10986,10991],{"type":20,"tag":48,"props":10972,"children":10973},{},[10974],{"type":25,"value":10975},"数据只有 20 条，远远不够训练稳定模型",{"type":20,"tag":48,"props":10977,"children":10978},{},[10979],{"type":25,"value":10980},"亲疏水性本身不是绝对二分类，不同教材或标度可能会有不同划分",{"type":20,"tag":48,"props":10982,"children":10983},{},[10984],{"type":25,"value":10985},"Morgan 指纹只是一种特征表示，可能没有捕捉到所有与亲疏水性相关的信息",{"type":20,"tag":48,"props":10987,"children":10988},{},[10989],{"type":25,"value":10990},"没有和 baseline 模型做系统比较",{"type":20,"tag":48,"props":10992,"children":10993},{},[10994],{"type":25,"value":10995},"没有调参实验，也没有更多评价指标",{"type":20,"tag":21,"props":10997,"children":10998},{},[10999],{"type":25,"value":11000},"这些局限并不代表项目没有价值。相反，它们正好说明了机器学习实验中最重要的一点：模型结果必须结合数据、特征、评估方式一起解释。",{"type":20,"tag":1863,"props":11002,"children":11004},{"id":11003},"后续可以继续做什么",[11005],{"type":25,"value":11003},{"type":20,"tag":21,"props":11007,"children":11008},{},[11009],{"type":25,"value":11010},"后续我想从三个方向继续优化：",{"type":20,"tag":5719,"props":11012,"children":11013},{"start":10630},[11014,11019,11024],{"type":20,"tag":48,"props":11015,"children":11016},{},[11017],{"type":25,"value":11018},"增加 baseline 用 Logistic Regression、Random Forest 或多数类预测作为对照。",{"type":20,"tag":48,"props":11020,"children":11021},{},[11022],{"type":25,"value":11023},"尝试 RDKit 分子描述符 不只使用 Morgan 指纹，还可以加入 LogP、TPSA、分子量、氢键供体和受体数量等更直观的化学特征。",{"type":20,"tag":48,"props":11025,"children":11026},{},[11027],{"type":25,"value":11028},"输出更多评价指标 除了 accuracy，还可以看 confusion matrix、precision、recall，观察模型到底更容易把哪一类预测错。",{"type":20,"tag":1863,"props":11030,"children":11031},{"id":10044},[11032],{"type":25,"value":10044},{"type":20,"tag":21,"props":11034,"children":11035},{},[11036],{"type":25,"value":11037},"这个项目很小，但它让我完整走了一遍机器学习实验流程：",{"type":20,"tag":126,"props":11039,"children":11041},{"code":11040},"数据 -> 特征工程 -> 模型 -> 训练 -> 正则化 -> 交叉验证 -> 结果解释\n",[11042],{"type":20,"tag":84,"props":11043,"children":11044},{"__ignoreMap":8},[11045],{"type":25,"value":11040},{"type":20,"tag":21,"props":11047,"children":11048},{},[11049],{"type":25,"value":11050},"对我来说，这个项目最重要的收获不是准确率有多高，而是开始理解：",{"type":20,"tag":44,"props":11052,"children":11053},{},[11054,11059,11064,11069,11074],{"type":20,"tag":48,"props":11055,"children":11056},{},[11057],{"type":25,"value":11058},"模型只能学习它看到的特征",{"type":20,"tag":48,"props":11060,"children":11061},{},[11062],{"type":25,"value":11063},"训练集表现好不代表泛化能力强",{"type":20,"tag":48,"props":11065,"children":11066},{},[11067],{"type":25,"value":11068},"小数据集更需要谨慎评估",{"type":20,"tag":48,"props":11070,"children":11071},{},[11072],{"type":25,"value":11073},"baseline 是判断模型价值的参照物",{"type":20,"tag":48,"props":11075,"children":11076},{},[11077],{"type":25,"value":11078},"机器学习结果需要被解释，而不是只被展示",{"type":20,"tag":21,"props":11080,"children":11081},{},[11082],{"type":25,"value":11083},"这也是我觉得这个项目适合作为机器学习入门练习的原因：它不大，但关键概念都在里面。",{"type":20,"tag":11085,"props":11086,"children":11088},"h4",{"id":11087},"项目地址httpsgiteecomo_insistmpl-hydrophobicity",[11089,11091],{"type":25,"value":11090},"项目地址：",{"type":20,"tag":101,"props":11092,"children":11095},{"href":11093,"rel":11094},"https:\u002F\u002Fgitee.com\u002Fo_insist\u002Fmpl-hydrophobicity",[105],[11096],{"type":25,"value":11093},{"title":8,"searchDepth":169,"depth":169,"links":11098},[11099,11100,11101,11102,11103,11104,11105,11111,11112,11113],{"id":10323,"depth":169,"text":10323},{"id":10397,"depth":169,"text":10400},{"id":10493,"depth":169,"text":10493},{"id":10605,"depth":169,"text":10605},{"id":10672,"depth":169,"text":10672},{"id":10760,"depth":169,"text":10760},{"id":10806,"depth":169,"text":10806,"children":11106},[11107,11108,11109,11110],{"id":10811,"depth":179,"text":10814},{"id":10852,"depth":179,"text":10855},{"id":10868,"depth":179,"text":10871},{"id":10903,"depth":179,"text":10906},{"id":10955,"depth":169,"text":10955},{"id":11003,"depth":169,"text":11003},{"id":10044,"depth":169,"text":10044},"content:articles:ai:mpl-hydrophobicity.md","articles\u002Fai\u002Fmpl-hydrophobicity.md","articles\u002Fai\u002Fmpl-hydrophobicity",{"_path":11118,"_dir":6776,"_draft":7,"_partial":7,"_locale":8,"title":11119,"description":11120,"date":11121,"tags":11122,"body":11123,"_type":1800,"_id":12316,"_source":1802,"_file":12317,"_stem":12318,"_extension":1805},"\u002Farticles\u002Fdevops\u002Fssh","Mac在外网SSH绕过内网NAT的完整指北","无需公网 IP，通过 Tailscale 组建虚拟局域网，实现 Mac 远程 SSH 登录家里 Windows 主机的完整配置指南。","2026-05-07",[14,6781],{"type":17,"children":11124,"toc":12287},[11125,11130,11143,11155,11164,11169,11192,11197,11208,11213,11236,11241,11272,11275,11281,11286,11292,11306,11312,11317,11333,11339,11385,11390,11395,11409,11414,11477,11483,11497,11510,11513,11519,11524,11529,11554,11559,11594,11607,11613,11618,11632,11637,11651,11656,11676,11679,11685,11690,11709,11714,11728,11733,11738,11757,11770,11773,11785,11796,11837,11842,11861,11874,11877,11883,11888,11894,11918,11923,11948,11954,11967,11985,11997,12011,12016,12036,12039,12044,12183,12186,12191,12196,12219,12222,12226,12238,12243,12248,12278,12283],{"type":20,"tag":1863,"props":11126,"children":11128},{"id":11127},"导致问题的原因",[11129],{"type":25,"value":11127},{"type":20,"tag":21,"props":11131,"children":11132},{},[11133,11135,11141],{"type":25,"value":11134},"我的需求是Mac在外网SSH连接家里Windows主机做训练和微调大模型，但现在路由器都自带 ",{"type":20,"tag":84,"props":11136,"children":11138},{"className":11137},[],[11139],{"type":25,"value":11140},"NAT",{"type":25,"value":11142}," 功能，家里的电脑没有一个可以被外网直接访问的公网地址；学校或实验室网络也常常有额外限制，所以直接通过公网 IP 连接往往行不通。",{"type":20,"tag":21,"props":11144,"children":11145},{},[11146,11148,11153],{"type":25,"value":11147},"这篇文章记录一套我自己会长期使用的方案：",{"type":20,"tag":33,"props":11149,"children":11150},{},[11151],{"type":25,"value":11152},"用 Tailscale 把 Mac 和 Windows 组到同一个虚拟局域网里，再通过 SSH 远程登录 Windows 主机。",{"type":25,"value":11154}," 这样不用折腾公网 IP、端口映射和 DDNS，配置成本低，稳定性也更适合个人使用。",{"type":20,"tag":21,"props":11156,"children":11157},{},[11158],{"type":20,"tag":11159,"props":11160,"children":11163},"img",{"alt":11161,"src":11162},"Mac 通过 Tailscale SSH 连接 Windows 的网络结构示意","\u002Fimg\u002Fssh-tail.png",[],{"type":20,"tag":1863,"props":11165,"children":11167},{"id":11166},"这篇文章适合谁",[11168],{"type":25,"value":11166},{"type":20,"tag":44,"props":11170,"children":11171},{},[11172,11177,11182,11187],{"type":20,"tag":48,"props":11173,"children":11174},{},[11175],{"type":25,"value":11176},"家里有一台 Windows 台式机或工作站",{"type":20,"tag":48,"props":11178,"children":11179},{},[11180],{"type":25,"value":11181},"手头主要使用 MacBook",{"type":20,"tag":48,"props":11183,"children":11184},{},[11185],{"type":25,"value":11186},"需要在外面远程连接家里的机器",{"type":20,"tag":48,"props":11188,"children":11189},{},[11190],{"type":25,"value":11191},"不想折腾公网 IP、路由器端口转发和 DDNS",{"type":20,"tag":1863,"props":11193,"children":11195},{"id":11194},"方案原理",[11196],{"type":25,"value":11194},{"type":20,"tag":21,"props":11198,"children":11199},{},[11200,11206],{"type":20,"tag":84,"props":11201,"children":11203},{"className":11202},[],[11204],{"type":25,"value":11205},"Tailscale",{"type":25,"value":11207}," 可以把多台设备拉进同一个加密的虚拟网络中。只要 Mac 和 Windows 登录到同一个账号，它们就像处在同一个局域网里，可以直接通过 Tailscale 分配的地址互相访问。",{"type":20,"tag":21,"props":11209,"children":11210},{},[11211],{"type":25,"value":11212},"这套方案的核心优点是：",{"type":20,"tag":44,"props":11214,"children":11215},{},[11216,11221,11226,11231],{"type":20,"tag":48,"props":11217,"children":11218},{},[11219],{"type":25,"value":11220},"不需要公网 IP",{"type":20,"tag":48,"props":11222,"children":11223},{},[11224],{"type":25,"value":11225},"不需要在路由器上开放 22 端口",{"type":20,"tag":48,"props":11227,"children":11228},{},[11229],{"type":25,"value":11230},"自带加密传输，安全性比裸露公网端口更高",{"type":20,"tag":48,"props":11232,"children":11233},{},[11234],{"type":25,"value":11235},"对复杂网络环境更友好，适合家里和实验室这种跨网络场景",{"type":20,"tag":1863,"props":11237,"children":11239},{"id":11238},"整体流程",[11240],{"type":25,"value":11238},{"type":20,"tag":5719,"props":11242,"children":11243},{},[11244,11255,11267],{"type":20,"tag":48,"props":11245,"children":11246},{},[11247,11249],{"type":25,"value":11248},"在 Windows 上安装并启用 ",{"type":20,"tag":84,"props":11250,"children":11252},{"className":11251},[],[11253],{"type":25,"value":11254},"OpenSSH Server",{"type":20,"tag":48,"props":11256,"children":11257},{},[11258,11260,11265],{"type":25,"value":11259},"在 Mac 和 Windows 上安装 ",{"type":20,"tag":84,"props":11261,"children":11263},{"className":11262},[],[11264],{"type":25,"value":11205},{"type":25,"value":11266},"，并登录同一个账号",{"type":20,"tag":48,"props":11268,"children":11269},{},[11270],{"type":25,"value":11271},"在 Mac 上通过 Tailscale 地址发起 SSH 连接",{"type":20,"tag":5056,"props":11273,"children":11274},{},[],{"type":20,"tag":1863,"props":11276,"children":11278},{"id":11277},"第一步在-windows-上启用-ssh-服务",[11279],{"type":25,"value":11280},"第一步：在 Windows 上启用 SSH 服务",{"type":20,"tag":21,"props":11282,"children":11283},{},[11284],{"type":25,"value":11285},"Windows 11 已经内置了 OpenSSH 组件，但很多机器默认只装了客户端，没有启用服务端。",{"type":20,"tag":28,"props":11287,"children":11289},{"id":11288},"方法一图形界面安装",[11290],{"type":25,"value":11291},"方法一：图形界面安装",{"type":20,"tag":126,"props":11293,"children":11295},{"className":324,"code":11294,"language":25,"meta":8,"style":8},"设置 -> 系统 -> 可选功能 -> 添加功能 -> 搜索 \"OpenSSH Server\" -> 安装\n",[11296],{"type":20,"tag":84,"props":11297,"children":11298},{"__ignoreMap":8},[11299],{"type":20,"tag":157,"props":11300,"children":11301},{"class":159,"line":160},[11302],{"type":20,"tag":157,"props":11303,"children":11304},{},[11305],{"type":25,"value":11294},{"type":20,"tag":28,"props":11307,"children":11309},{"id":11308},"方法二powershell-安装",[11310],{"type":25,"value":11311},"方法二：PowerShell 安装",{"type":20,"tag":21,"props":11313,"children":11314},{},[11315],{"type":25,"value":11316},"以管理员身份打开 PowerShell，执行：",{"type":20,"tag":126,"props":11318,"children":11322},{"className":11319,"code":11320,"language":11321,"meta":8,"style":8},"language-powershell shiki shiki-themes github-dark","Add-WindowsCapability -Online -Name OpenSSH.Server~~~~0.0.1.0\n","powershell",[11323],{"type":20,"tag":84,"props":11324,"children":11325},{"__ignoreMap":8},[11326],{"type":20,"tag":157,"props":11327,"children":11328},{"class":159,"line":160},[11329],{"type":20,"tag":157,"props":11330,"children":11331},{},[11332],{"type":25,"value":11320},{"type":20,"tag":28,"props":11334,"children":11336},{"id":11335},"启动-sshd-并设置为开机自启",[11337],{"type":25,"value":11338},"启动 sshd 并设置为开机自启",{"type":20,"tag":126,"props":11340,"children":11342},{"className":11319,"code":11341,"language":11321,"meta":8,"style":8},"# 启动 SSH 服务\nStart-Service sshd\n\n# 设置为开机自动启动\nSet-Service -Name sshd -StartupType Automatic\n",[11343],{"type":20,"tag":84,"props":11344,"children":11345},{"__ignoreMap":8},[11346,11354,11362,11369,11377],{"type":20,"tag":157,"props":11347,"children":11348},{"class":159,"line":160},[11349],{"type":20,"tag":157,"props":11350,"children":11351},{},[11352],{"type":25,"value":11353},"# 启动 SSH 服务\n",{"type":20,"tag":157,"props":11355,"children":11356},{"class":159,"line":169},[11357],{"type":20,"tag":157,"props":11358,"children":11359},{},[11360],{"type":25,"value":11361},"Start-Service sshd\n",{"type":20,"tag":157,"props":11363,"children":11364},{"class":159,"line":179},[11365],{"type":20,"tag":157,"props":11366,"children":11367},{"emptyLinePlaceholder":173},[11368],{"type":25,"value":176},{"type":20,"tag":157,"props":11370,"children":11371},{"class":159,"line":188},[11372],{"type":20,"tag":157,"props":11373,"children":11374},{},[11375],{"type":25,"value":11376},"# 设置为开机自动启动\n",{"type":20,"tag":157,"props":11378,"children":11379},{"class":159,"line":196},[11380],{"type":20,"tag":157,"props":11381,"children":11382},{},[11383],{"type":25,"value":11384},"Set-Service -Name sshd -StartupType Automatic\n",{"type":20,"tag":28,"props":11386,"children":11388},{"id":11387},"检查防火墙规则",[11389],{"type":25,"value":11387},{"type":20,"tag":21,"props":11391,"children":11392},{},[11393],{"type":25,"value":11394},"通常安装 OpenSSH Server 后，Windows 会自动创建防火墙规则。可以先检查：",{"type":20,"tag":126,"props":11396,"children":11398},{"className":11319,"code":11397,"language":11321,"meta":8,"style":8},"Get-NetFirewallRule -Name \"OpenSSH-Server-In-TCP\"\n",[11399],{"type":20,"tag":84,"props":11400,"children":11401},{"__ignoreMap":8},[11402],{"type":20,"tag":157,"props":11403,"children":11404},{"class":159,"line":160},[11405],{"type":20,"tag":157,"props":11406,"children":11407},{},[11408],{"type":25,"value":11397},{"type":20,"tag":21,"props":11410,"children":11411},{},[11412],{"type":25,"value":11413},"如果没有这条规则，再手动创建：",{"type":20,"tag":126,"props":11415,"children":11417},{"className":11319,"code":11416,"language":11321,"meta":8,"style":8},"New-NetFirewallRule -Name \"OpenSSH-Server-In-TCP\" `\n  -DisplayName \"OpenSSH Server (sshd)\" `\n  -Enabled True `\n  -Direction Inbound `\n  -Protocol TCP `\n  -Action Allow `\n  -LocalPort 22\n",[11418],{"type":20,"tag":84,"props":11419,"children":11420},{"__ignoreMap":8},[11421,11429,11437,11445,11453,11461,11469],{"type":20,"tag":157,"props":11422,"children":11423},{"class":159,"line":160},[11424],{"type":20,"tag":157,"props":11425,"children":11426},{},[11427],{"type":25,"value":11428},"New-NetFirewallRule -Name \"OpenSSH-Server-In-TCP\" `\n",{"type":20,"tag":157,"props":11430,"children":11431},{"class":159,"line":169},[11432],{"type":20,"tag":157,"props":11433,"children":11434},{},[11435],{"type":25,"value":11436},"  -DisplayName \"OpenSSH Server (sshd)\" `\n",{"type":20,"tag":157,"props":11438,"children":11439},{"class":159,"line":179},[11440],{"type":20,"tag":157,"props":11441,"children":11442},{},[11443],{"type":25,"value":11444},"  -Enabled True `\n",{"type":20,"tag":157,"props":11446,"children":11447},{"class":159,"line":188},[11448],{"type":20,"tag":157,"props":11449,"children":11450},{},[11451],{"type":25,"value":11452},"  -Direction Inbound `\n",{"type":20,"tag":157,"props":11454,"children":11455},{"class":159,"line":196},[11456],{"type":20,"tag":157,"props":11457,"children":11458},{},[11459],{"type":25,"value":11460},"  -Protocol TCP `\n",{"type":20,"tag":157,"props":11462,"children":11463},{"class":159,"line":204},[11464],{"type":20,"tag":157,"props":11465,"children":11466},{},[11467],{"type":25,"value":11468},"  -Action Allow `\n",{"type":20,"tag":157,"props":11470,"children":11471},{"class":159,"line":213},[11472],{"type":20,"tag":157,"props":11473,"children":11474},{},[11475],{"type":25,"value":11476},"  -LocalPort 22\n",{"type":20,"tag":28,"props":11478,"children":11480},{"id":11479},"验证-ssh-服务是否正常",[11481],{"type":25,"value":11482},"验证 SSH 服务是否正常",{"type":20,"tag":126,"props":11484,"children":11486},{"className":11319,"code":11485,"language":11321,"meta":8,"style":8},"Get-Service sshd\n",[11487],{"type":20,"tag":84,"props":11488,"children":11489},{"__ignoreMap":8},[11490],{"type":20,"tag":157,"props":11491,"children":11492},{"class":159,"line":160},[11493],{"type":20,"tag":157,"props":11494,"children":11495},{},[11496],{"type":25,"value":11485},{"type":20,"tag":21,"props":11498,"children":11499},{},[11500,11502,11508],{"type":25,"value":11501},"如果状态显示为 ",{"type":20,"tag":84,"props":11503,"children":11505},{"className":11504},[],[11506],{"type":25,"value":11507},"Running",{"type":25,"value":11509},"，说明 SSH 服务已经起来了。",{"type":20,"tag":5056,"props":11511,"children":11512},{},[],{"type":20,"tag":1863,"props":11514,"children":11516},{"id":11515},"第二步在两台设备上安装并登录-tailscale",[11517],{"type":25,"value":11518},"第二步：在两台设备上安装并登录 Tailscale",{"type":20,"tag":21,"props":11520,"children":11521},{},[11522],{"type":25,"value":11523},"Mac 和 Windows 两端都需要安装 Tailscale，并登录同一个账号。",{"type":20,"tag":28,"props":11525,"children":11527},{"id":11526},"安装方式",[11528],{"type":25,"value":11526},{"type":20,"tag":5719,"props":11530,"children":11531},{},[11532,11544,11549],{"type":20,"tag":48,"props":11533,"children":11534},{},[11535,11537],{"type":25,"value":11536},"打开 ",{"type":20,"tag":101,"props":11538,"children":11541},{"href":11539,"rel":11540},"https:\u002F\u002Ftailscale.com\u002Fdownloads",[105],[11542],{"type":25,"value":11543},"Tailscale 下载页",{"type":20,"tag":48,"props":11545,"children":11546},{},[11547],{"type":25,"value":11548},"根据平台下载安装包",{"type":20,"tag":48,"props":11550,"children":11551},{},[11552],{"type":25,"value":11553},"安装完成后登录同一个账号",{"type":20,"tag":21,"props":11555,"children":11556},{},[11557],{"type":25,"value":11558},"常见平台选择：",{"type":20,"tag":44,"props":11560,"children":11561},{},[11562,11581],{"type":20,"tag":48,"props":11563,"children":11564},{},[11565,11567,11573,11575],{"type":25,"value":11566},"Mac：",{"type":20,"tag":84,"props":11568,"children":11570},{"className":11569},[],[11571],{"type":25,"value":11572},"App Store",{"type":25,"value":11574}," 或官方 ",{"type":20,"tag":84,"props":11576,"children":11578},{"className":11577},[],[11579],{"type":25,"value":11580},"dmg",{"type":20,"tag":48,"props":11582,"children":11583},{},[11584,11586,11592],{"type":25,"value":11585},"Windows：官方 ",{"type":20,"tag":84,"props":11587,"children":11589},{"className":11588},[],[11590],{"type":25,"value":11591},"exe",{"type":25,"value":11593}," 安装包",{"type":20,"tag":21,"props":11595,"children":11596},{},[11597,11599,11605],{"type":25,"value":11598},"登录完成后，两台设备就会出现在同一个 ",{"type":20,"tag":84,"props":11600,"children":11602},{"className":11601},[],[11603],{"type":25,"value":11604},"Tailnet",{"type":25,"value":11606}," 里。",{"type":20,"tag":28,"props":11608,"children":11610},{"id":11609},"查看-windows-的-tailscale-地址",[11611],{"type":25,"value":11612},"查看 Windows 的 Tailscale 地址",{"type":20,"tag":21,"props":11614,"children":11615},{},[11616],{"type":25,"value":11617},"在 Windows PowerShell 中执行：",{"type":20,"tag":126,"props":11619,"children":11621},{"className":11319,"code":11620,"language":11321,"meta":8,"style":8},"tailscale ip -4\n",[11622],{"type":20,"tag":84,"props":11623,"children":11624},{"__ignoreMap":8},[11625],{"type":20,"tag":157,"props":11626,"children":11627},{"class":159,"line":160},[11628],{"type":20,"tag":157,"props":11629,"children":11630},{},[11631],{"type":25,"value":11620},{"type":20,"tag":21,"props":11633,"children":11634},{},[11635],{"type":25,"value":11636},"你会看到一个类似下面的地址：",{"type":20,"tag":126,"props":11638,"children":11640},{"className":324,"code":11639,"language":25,"meta":8,"style":8},"100.xx.xx.xx\n",[11641],{"type":20,"tag":84,"props":11642,"children":11643},{"__ignoreMap":8},[11644],{"type":20,"tag":157,"props":11645,"children":11646},{"class":159,"line":160},[11647],{"type":20,"tag":157,"props":11648,"children":11649},{},[11650],{"type":25,"value":11639},{"type":20,"tag":21,"props":11652,"children":11653},{},[11654],{"type":25,"value":11655},"也可以在 Mac 上查看当前 Tailnet 里的设备状态：",{"type":20,"tag":126,"props":11657,"children":11659},{"className":238,"code":11658,"language":237,"meta":8,"style":8},"tailscale status\n",[11660],{"type":20,"tag":84,"props":11661,"children":11662},{"__ignoreMap":8},[11663],{"type":20,"tag":157,"props":11664,"children":11665},{"class":159,"line":160},[11666,11671],{"type":20,"tag":157,"props":11667,"children":11668},{"style":248},[11669],{"type":25,"value":11670},"tailscale",{"type":20,"tag":157,"props":11672,"children":11673},{"style":254},[11674],{"type":25,"value":11675}," status\n",{"type":20,"tag":5056,"props":11677,"children":11678},{},[],{"type":20,"tag":1863,"props":11680,"children":11682},{"id":11681},"第三步从-mac-发起-ssh-连接",[11683],{"type":25,"value":11684},"第三步：从 Mac 发起 SSH 连接",{"type":20,"tag":21,"props":11686,"children":11687},{},[11688],{"type":25,"value":11689},"拿到 Windows 主机的 Tailscale 地址之后，就可以直接在 Mac 终端连接：",{"type":20,"tag":126,"props":11691,"children":11693},{"className":238,"code":11692,"language":237,"meta":8,"style":8},"ssh 你的Windows用户名@100.xx.xx.xx\n",[11694],{"type":20,"tag":84,"props":11695,"children":11696},{"__ignoreMap":8},[11697],{"type":20,"tag":157,"props":11698,"children":11699},{"class":159,"line":160},[11700,11704],{"type":20,"tag":157,"props":11701,"children":11702},{"style":248},[11703],{"type":25,"value":9759},{"type":20,"tag":157,"props":11705,"children":11706},{"style":254},[11707],{"type":25,"value":11708}," 你的Windows用户名@100.xx.xx.xx\n",{"type":20,"tag":21,"props":11710,"children":11711},{},[11712],{"type":25,"value":11713},"如果你不确定 Windows 的用户名，可以先在 Windows 终端执行：",{"type":20,"tag":126,"props":11715,"children":11717},{"className":11319,"code":11716,"language":11321,"meta":8,"style":8},"whoami\n",[11718],{"type":20,"tag":84,"props":11719,"children":11720},{"__ignoreMap":8},[11721],{"type":20,"tag":157,"props":11722,"children":11723},{"class":159,"line":160},[11724],{"type":20,"tag":157,"props":11725,"children":11726},{},[11727],{"type":25,"value":11716},{"type":20,"tag":21,"props":11729,"children":11730},{},[11731],{"type":25,"value":11732},"然后使用返回结果中的用户名部分进行连接。",{"type":20,"tag":28,"props":11734,"children":11736},{"id":11735},"一个连接示例",[11737],{"type":25,"value":11735},{"type":20,"tag":126,"props":11739,"children":11741},{"className":238,"code":11740,"language":237,"meta":8,"style":8},"ssh wangkun@100.xx.xx.xx\n",[11742],{"type":20,"tag":84,"props":11743,"children":11744},{"__ignoreMap":8},[11745],{"type":20,"tag":157,"props":11746,"children":11747},{"class":159,"line":160},[11748,11752],{"type":20,"tag":157,"props":11749,"children":11750},{"style":248},[11751],{"type":25,"value":9759},{"type":20,"tag":157,"props":11753,"children":11754},{"style":254},[11755],{"type":25,"value":11756}," wangkun@100.xx.xx.xx\n",{"type":20,"tag":21,"props":11758,"children":11759},{},[11760,11762,11768],{"type":25,"value":11761},"第一次连接时，终端会提示你确认主机指纹，输入 ",{"type":20,"tag":84,"props":11763,"children":11765},{"className":11764},[],[11766],{"type":25,"value":11767},"yes",{"type":25,"value":11769}," 即可。",{"type":20,"tag":5056,"props":11771,"children":11772},{},[],{"type":20,"tag":1863,"props":11774,"children":11776},{"id":11775},"可选优化配置-sshconfig",[11777,11779],{"type":25,"value":11778},"可选优化：配置 ",{"type":20,"tag":84,"props":11780,"children":11782},{"className":11781},[],[11783],{"type":25,"value":11784},"~\u002F.ssh\u002Fconfig",{"type":20,"tag":21,"props":11786,"children":11787},{},[11788,11790,11795],{"type":25,"value":11789},"如果你不想每次都手动输入完整 IP，可以在 Mac 上编辑 ",{"type":20,"tag":84,"props":11791,"children":11793},{"className":11792},[],[11794],{"type":25,"value":11784},{"type":25,"value":146},{"type":20,"tag":126,"props":11797,"children":11801},{"className":11798,"code":11799,"language":11800,"meta":8,"style":8},"language-sshconfig shiki shiki-themes github-dark","Host home-gpu\n    HostName 100.xx.xx.xx\n    User 你的Windows用户名\n    Port 22\n","sshconfig",[11802],{"type":20,"tag":84,"props":11803,"children":11804},{"__ignoreMap":8},[11805,11813,11821,11829],{"type":20,"tag":157,"props":11806,"children":11807},{"class":159,"line":160},[11808],{"type":20,"tag":157,"props":11809,"children":11810},{},[11811],{"type":25,"value":11812},"Host home-gpu\n",{"type":20,"tag":157,"props":11814,"children":11815},{"class":159,"line":169},[11816],{"type":20,"tag":157,"props":11817,"children":11818},{},[11819],{"type":25,"value":11820},"    HostName 100.xx.xx.xx\n",{"type":20,"tag":157,"props":11822,"children":11823},{"class":159,"line":179},[11824],{"type":20,"tag":157,"props":11825,"children":11826},{},[11827],{"type":25,"value":11828},"    User 你的Windows用户名\n",{"type":20,"tag":157,"props":11830,"children":11831},{"class":159,"line":188},[11832],{"type":20,"tag":157,"props":11833,"children":11834},{},[11835],{"type":25,"value":11836},"    Port 22\n",{"type":20,"tag":21,"props":11838,"children":11839},{},[11840],{"type":25,"value":11841},"保存后，以后只需要输入：",{"type":20,"tag":126,"props":11843,"children":11845},{"className":238,"code":11844,"language":237,"meta":8,"style":8},"ssh home-gpu\n",[11846],{"type":20,"tag":84,"props":11847,"children":11848},{"__ignoreMap":8},[11849],{"type":20,"tag":157,"props":11850,"children":11851},{"class":159,"line":160},[11852,11856],{"type":20,"tag":157,"props":11853,"children":11854},{"style":248},[11855],{"type":25,"value":9759},{"type":20,"tag":157,"props":11857,"children":11858},{"style":254},[11859],{"type":25,"value":11860}," home-gpu\n",{"type":20,"tag":21,"props":11862,"children":11863},{},[11864,11866,11872],{"type":25,"value":11865},"如果你的 Tailscale 网络里设备名比较稳定，也可以把 ",{"type":20,"tag":84,"props":11867,"children":11869},{"className":11868},[],[11870],{"type":25,"value":11871},"HostName",{"type":25,"value":11873}," 换成设备名。",{"type":20,"tag":5056,"props":11875,"children":11876},{},[],{"type":20,"tag":1863,"props":11878,"children":11880},{"id":11879},"可选优化配置-ssh-密钥免密登录",[11881],{"type":25,"value":11882},"可选优化：配置 SSH 密钥免密登录",{"type":20,"tag":21,"props":11884,"children":11885},{},[11886],{"type":25,"value":11887},"如果你经常要连接这台机器，推荐配置 SSH 密钥登录，省去重复输入密码，也更安全。",{"type":20,"tag":28,"props":11889,"children":11891},{"id":11890},"在-mac-上生成密钥",[11892],{"type":25,"value":11893},"在 Mac 上生成密钥",{"type":20,"tag":126,"props":11895,"children":11897},{"className":238,"code":11896,"language":237,"meta":8,"style":8},"ssh-keygen -t ed25519\n",[11898],{"type":20,"tag":84,"props":11899,"children":11900},{"__ignoreMap":8},[11901],{"type":20,"tag":157,"props":11902,"children":11903},{"class":159,"line":160},[11904,11909,11913],{"type":20,"tag":157,"props":11905,"children":11906},{"style":248},[11907],{"type":25,"value":11908},"ssh-keygen",{"type":20,"tag":157,"props":11910,"children":11911},{"style":260},[11912],{"type":25,"value":7636},{"type":20,"tag":157,"props":11914,"children":11915},{"style":254},[11916],{"type":25,"value":11917}," ed25519\n",{"type":20,"tag":21,"props":11919,"children":11920},{},[11921],{"type":25,"value":11922},"一路回车即可，默认会生成：",{"type":20,"tag":44,"props":11924,"children":11925},{},[11926,11937],{"type":20,"tag":48,"props":11927,"children":11928},{},[11929,11931],{"type":25,"value":11930},"私钥：",{"type":20,"tag":84,"props":11932,"children":11934},{"className":11933},[],[11935],{"type":25,"value":11936},"~\u002F.ssh\u002Fid_ed25519",{"type":20,"tag":48,"props":11938,"children":11939},{},[11940,11942],{"type":25,"value":11941},"公钥：",{"type":20,"tag":84,"props":11943,"children":11945},{"className":11944},[],[11946],{"type":25,"value":11947},"~\u002F.ssh\u002Fid_ed25519.pub",{"type":20,"tag":28,"props":11949,"children":11951},{"id":11950},"将公钥写入-windows",[11952],{"type":25,"value":11953},"将公钥写入 Windows",{"type":20,"tag":21,"props":11955,"children":11956},{},[11957,11959,11965],{"type":25,"value":11958},"如果你的系统装了 ",{"type":20,"tag":84,"props":11960,"children":11962},{"className":11961},[],[11963],{"type":25,"value":11964},"ssh-copy-id",{"type":25,"value":11966},"，可以直接执行：",{"type":20,"tag":126,"props":11968,"children":11970},{"className":238,"code":11969,"language":237,"meta":8,"style":8},"ssh-copy-id 你的Windows用户名@100.xx.xx.xx\n",[11971],{"type":20,"tag":84,"props":11972,"children":11973},{"__ignoreMap":8},[11974],{"type":20,"tag":157,"props":11975,"children":11976},{"class":159,"line":160},[11977,11981],{"type":20,"tag":157,"props":11978,"children":11979},{"style":248},[11980],{"type":25,"value":11964},{"type":20,"tag":157,"props":11982,"children":11983},{"style":254},[11984],{"type":25,"value":11708},{"type":20,"tag":21,"props":11986,"children":11987},{},[11988,11990,11995],{"type":25,"value":11989},"如果没有 ",{"type":20,"tag":84,"props":11991,"children":11993},{"className":11992},[],[11994],{"type":25,"value":11964},{"type":25,"value":11996},"，就把公钥内容手动追加到 Windows 的这个文件中：",{"type":20,"tag":126,"props":11998,"children":12000},{"className":324,"code":11999,"language":25,"meta":8,"style":8},"C:\\Users\\你的用户名\\.ssh\\authorized_keys\n",[12001],{"type":20,"tag":84,"props":12002,"children":12003},{"__ignoreMap":8},[12004],{"type":20,"tag":157,"props":12005,"children":12006},{"class":159,"line":160},[12007],{"type":20,"tag":157,"props":12008,"children":12009},{},[12010],{"type":25,"value":11999},{"type":20,"tag":21,"props":12012,"children":12013},{},[12014],{"type":25,"value":12015},"公钥内容可以用下面这条命令查看：",{"type":20,"tag":126,"props":12017,"children":12019},{"className":238,"code":12018,"language":237,"meta":8,"style":8},"cat ~\u002F.ssh\u002Fid_ed25519.pub\n",[12020],{"type":20,"tag":84,"props":12021,"children":12022},{"__ignoreMap":8},[12023],{"type":20,"tag":157,"props":12024,"children":12025},{"class":159,"line":160},[12026,12031],{"type":20,"tag":157,"props":12027,"children":12028},{"style":248},[12029],{"type":25,"value":12030},"cat",{"type":20,"tag":157,"props":12032,"children":12033},{"style":254},[12034],{"type":25,"value":12035}," ~\u002F.ssh\u002Fid_ed25519.pub\n",{"type":20,"tag":5056,"props":12037,"children":12038},{},[],{"type":20,"tag":1863,"props":12040,"children":12042},{"id":12041},"常见问题排查",[12043],{"type":25,"value":12041},{"type":20,"tag":1902,"props":12045,"children":12046},{},[12047,12068],{"type":20,"tag":1906,"props":12048,"children":12049},{},[12050],{"type":20,"tag":1910,"props":12051,"children":12052},{},[12053,12058,12063],{"type":20,"tag":1914,"props":12054,"children":12055},{},[12056],{"type":25,"value":12057},"现象",{"type":20,"tag":1914,"props":12059,"children":12060},{},[12061],{"type":25,"value":12062},"常见原因",{"type":20,"tag":1914,"props":12064,"children":12065},{},[12066],{"type":25,"value":12067},"处理方式",{"type":20,"tag":1930,"props":12069,"children":12070},{},[12071,12107,12129,12165],{"type":20,"tag":1910,"props":12072,"children":12073},{},[12074,12083,12094],{"type":20,"tag":1937,"props":12075,"children":12076},{},[12077],{"type":20,"tag":84,"props":12078,"children":12080},{"className":12079},[],[12081],{"type":25,"value":12082},"Connection refused",{"type":20,"tag":1937,"props":12084,"children":12085},{},[12086,12092],{"type":20,"tag":84,"props":12087,"children":12089},{"className":12088},[],[12090],{"type":25,"value":12091},"sshd",{"type":25,"value":12093}," 没启动，或者防火墙规则不存在",{"type":20,"tag":1937,"props":12095,"children":12096},{},[12097,12099,12105],{"type":25,"value":12098},"检查 ",{"type":20,"tag":84,"props":12100,"children":12102},{"className":12101},[],[12103],{"type":25,"value":12104},"Get-Service sshd",{"type":25,"value":12106}," 和防火墙规则",{"type":20,"tag":1910,"props":12108,"children":12109},{},[12110,12119,12124],{"type":20,"tag":1937,"props":12111,"children":12112},{},[12113],{"type":20,"tag":84,"props":12114,"children":12116},{"className":12115},[],[12117],{"type":25,"value":12118},"Connection timed out",{"type":20,"tag":1937,"props":12120,"children":12121},{},[12122],{"type":25,"value":12123},"Tailscale 没连上，或 Windows 处于休眠状态",{"type":20,"tag":1937,"props":12125,"children":12126},{},[12127],{"type":25,"value":12128},"确认两台设备在线，并检查电源设置",{"type":20,"tag":1910,"props":12130,"children":12131},{},[12132,12141,12146],{"type":20,"tag":1937,"props":12133,"children":12134},{},[12135],{"type":20,"tag":84,"props":12136,"children":12138},{"className":12137},[],[12139],{"type":25,"value":12140},"Permission denied",{"type":20,"tag":1937,"props":12142,"children":12143},{},[12144],{"type":25,"value":12145},"用户名不对、密码不对，或密钥未正确写入",{"type":20,"tag":1937,"props":12147,"children":12148},{},[12149,12151,12157,12159],{"type":25,"value":12150},"先用 ",{"type":20,"tag":84,"props":12152,"children":12154},{"className":12153},[],[12155],{"type":25,"value":12156},"whoami",{"type":25,"value":12158}," 确认用户名，再检查 ",{"type":20,"tag":84,"props":12160,"children":12162},{"className":12161},[],[12163],{"type":25,"value":12164},"authorized_keys",{"type":20,"tag":1910,"props":12166,"children":12167},{},[12168,12173,12178],{"type":20,"tag":1937,"props":12169,"children":12170},{},[12171],{"type":25,"value":12172},"看得到 Tailscale 设备但连不上 SSH",{"type":20,"tag":1937,"props":12174,"children":12175},{},[12176],{"type":25,"value":12177},"Tailscale 正常，SSH 服务本身没配好",{"type":20,"tag":1937,"props":12179,"children":12180},{},[12181],{"type":25,"value":12182},"回到第一步检查 OpenSSH Server 是否安装完成",{"type":20,"tag":5056,"props":12184,"children":12185},{},[],{"type":20,"tag":1863,"props":12187,"children":12189},{"id":12188},"安全建议",[12190],{"type":25,"value":12188},{"type":20,"tag":21,"props":12192,"children":12193},{},[12194],{"type":25,"value":12195},"这套方案已经比“直接把家里 22 端口暴露到公网”安全很多，但如果要长期使用，还是建议再做几件事：",{"type":20,"tag":44,"props":12197,"children":12198},{},[12199,12204,12209,12214],{"type":20,"tag":48,"props":12200,"children":12201},{},[12202],{"type":25,"value":12203},"优先使用 SSH 密钥登录，而不是只靠密码",{"type":20,"tag":48,"props":12205,"children":12206},{},[12207],{"type":25,"value":12208},"不要在路由器上额外做 22 端口映射",{"type":20,"tag":48,"props":12210,"children":12211},{},[12212],{"type":25,"value":12213},"给 Windows 账户设置强密码",{"type":20,"tag":48,"props":12215,"children":12216},{},[12217],{"type":25,"value":12218},"如果机器需要长期远程访问，检查是否会自动休眠",{"type":20,"tag":5056,"props":12220,"children":12221},{},[],{"type":20,"tag":1863,"props":12223,"children":12224},{"id":10044},[12225],{"type":25,"value":10044},{"type":20,"tag":21,"props":12227,"children":12228},{},[12229,12231,12236],{"type":25,"value":12230},"如果你的目标只是“在外面稳定地连回家里的 Windows 主机”，那么 ",{"type":20,"tag":33,"props":12232,"children":12233},{},[12234],{"type":25,"value":12235},"OpenSSH Server + Tailscale",{"type":25,"value":12237}," 是一套非常省心的方案。",{"type":20,"tag":21,"props":12239,"children":12240},{},[12241],{"type":25,"value":12242},"它的优势不在于炫技，而在于简单、实用、可长期维护：不用研究公网 IP，不用折腾路由器，也不用担心一堆家庭网络环境带来的坑。对于需要远程跑模型、传文件、执行命令的人来说，这基本已经够用了。",{"type":20,"tag":21,"props":12244,"children":12245},{},[12246],{"type":25,"value":12247},"如果后面你还想继续完善这套远程方案，可以再往下加：",{"type":20,"tag":44,"props":12249,"children":12250},{},[12251,12268,12273],{"type":20,"tag":48,"props":12252,"children":12253},{},[12254,12259,12260,12266],{"type":20,"tag":84,"props":12255,"children":12257},{"className":12256},[],[12258],{"type":25,"value":9726},{"type":25,"value":7993},{"type":20,"tag":84,"props":12261,"children":12263},{"className":12262},[],[12264],{"type":25,"value":12265},"rsync",{"type":25,"value":12267}," 传文件",{"type":20,"tag":48,"props":12269,"children":12270},{},[12271],{"type":25,"value":12272},"VS Code Remote SSH 远程开发",{"type":20,"tag":48,"props":12274,"children":12275},{},[12276],{"type":25,"value":12277},"Jupyter \u002F WebUI 通过 Tailscale 内网访问",{"type":20,"tag":21,"props":12279,"children":12280},{},[12281],{"type":25,"value":12282},"这样你的家用 GPU 主机，基本就能变成一台随时可用的私人算力节点。",{"type":20,"tag":1785,"props":12284,"children":12285},{},[12286],{"type":25,"value":1789},{"title":8,"searchDepth":169,"depth":169,"links":12288},[12289,12290,12291,12292,12293,12300,12304,12307,12309,12313,12314,12315],{"id":11127,"depth":169,"text":11127},{"id":11166,"depth":169,"text":11166},{"id":11194,"depth":169,"text":11194},{"id":11238,"depth":169,"text":11238},{"id":11277,"depth":169,"text":11280,"children":12294},[12295,12296,12297,12298,12299],{"id":11288,"depth":179,"text":11291},{"id":11308,"depth":179,"text":11311},{"id":11335,"depth":179,"text":11338},{"id":11387,"depth":179,"text":11387},{"id":11479,"depth":179,"text":11482},{"id":11515,"depth":169,"text":11518,"children":12301},[12302,12303],{"id":11526,"depth":179,"text":11526},{"id":11609,"depth":179,"text":11612},{"id":11681,"depth":169,"text":11684,"children":12305},[12306],{"id":11735,"depth":179,"text":11735},{"id":11775,"depth":169,"text":12308},"可选优化：配置 ~\u002F.ssh\u002Fconfig",{"id":11879,"depth":169,"text":11882,"children":12310},[12311,12312],{"id":11890,"depth":179,"text":11893},{"id":11950,"depth":179,"text":11953},{"id":12041,"depth":169,"text":12041},{"id":12188,"depth":169,"text":12188},{"id":10044,"depth":169,"text":10044},"content:articles:devops:SSH连接.md","articles\u002Fdevops\u002FSSH连接.md","articles\u002Fdevops\u002FSSH连接",1779811684444]