<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
  <author>
    <name>洛墨-天染-依然</name>
  </author>
  <generator uri="https://hexo.io/">Hexo</generator>
  <id>https://blog.luomo.moe/</id>
  <link href="https://blog.luomo.moe/" rel="alternate"/>
  <link href="https://blog.luomo.moe/atom.xml" rel="self"/>
  <rights>All rights reserved 2026, 洛墨-天染-依然</rights>
  <subtitle>洛墨的个人博客，记录 AI 智能体、开发实践、服务器运维与云上项目。循此苦旅，直抵群星。</subtitle>
  <title>Luomoの云日常</title>
  <updated>2026-11-05T10:00:00.000Z</updated>
  <entry>
    <author>
      <name>洛墨-天染-依然</name>
    </author>
    <category term="游戏星海" scheme="https://blog.luomo.moe/categories/%E6%B8%B8%E6%88%8F%E6%98%9F%E6%B5%B7/"/>
    <category term="融化的风花与白兔" scheme="https://blog.luomo.moe/tags/%E8%9E%8D%E5%8C%96%E7%9A%84%E9%A3%8E%E8%8A%B1%E4%B8%8E%E7%99%BD%E5%85%94/"/>
    <category term="星空列车与白的旅行" scheme="https://blog.luomo.moe/tags/%E6%98%9F%E7%A9%BA%E5%88%97%E8%BD%A6%E4%B8%8E%E7%99%BD%E7%9A%84%E6%97%85%E8%A1%8C/"/>
    <category term="视觉小说" scheme="https://blog.luomo.moe/tags/%E8%A7%86%E8%A7%89%E5%B0%8F%E8%AF%B4/"/>
    <category term="白玉" scheme="https://blog.luomo.moe/tags/%E7%99%BD%E7%8E%89/"/>
    <content>
      <![CDATA[<link rel="stylesheet" class="aplayer-secondary-style-marker" href="/assets/css/APlayer.min.css"><script src="/assets/js/APlayer.min.js" class="aplayer-secondary-script-marker"></script><script class="meting-secondary-script-marker" src="/assets/js/Meting.min.js"></script><link rel="stylesheet" type="text&#x2F;css" href="/pluginsSrc/hexo-tag-hint/dist/hexo-tag-hint.min.css"><div class="note warning flat"><p>以下含《融化的风花与白兔》（とける風花とシロうさぎ）与前作《星空列车与白的旅行》的剧情剧透，尚未到站的旅人请谨慎下车。</p></div><p>通关之后，我的第一反应其实朴素得很：<strong>太好了，<span class="hint--info hint--rounded hint--top" data-hint="两作的编剧 さかき傘" ontouchstart>伞哥</span>这次终于做个人了！</strong></p><mark class="hl-label orange">剧情剧透</mark> <mark class="hl-label purple">前作关联</mark> <mark class="hl-label pink">白玉厨私心</mark> <mark class="hl-label green">HE·放心食用</mark><span id="more"></span><h2 id="从那趟列车上下来">从那趟列车上下来</h2><p>这话听上去有点好笑，可对一个刚从《星白》的后劲里爬出来的玩家而言，实在是肺腑之言。《星空列车与白的旅行》曾让我难受了很久很久。它最狠的地方，并不在于把悲剧藏得多深——恰恰相反，推到中途，我已隐约嗅到了刀子的气味。可即便早有心理准备，那个看似温柔圆满、细想却更叫人心口发紧的结局，还是杀得我措手不及。故事里的人们驶向了明天，我却好像被落在了那趟列车上，迟迟没能下车。</p><p><img src="/images/kazeshiro/hoshishiro-nowa.webp" alt="《星空列车与白的旅行》游戏画面：星空下的诺瓦" title="《星空列车与白的旅行》：星空下的诺瓦"></p><p><em>《星空列车与白的旅行》Steam 简中版游戏画面。图源：Steam 商店页。</em></p><p>所以打开《融化的风花与白兔》时，我是真的有点怕。中后期每飘来一丝不祥的气息，脑内警报便自动拉响：坏了坏了，那个熟悉的伞哥又回来了！普卡这么可爱，你不会真舍得下手吧？！</p><p>好在这一次，等着我的不是又一场无法挽回的告别，而是庆幸、释怀，以及一点点——终于可以卸下防备、放心去喜欢这些角色的幸福。</p><p>不过在聊剧情之前，请允许我暂时把文学修养寄存在门口，诚实地发一会儿癫。</p><h2 id="理智值，已暂存门口">理智值，已暂存门口</h2><p style="font-size:1.5em;font-weight:700;text-align:center;margin:1.2em 0;">普卡也太可爱了吧啊啊啊啊！！！</p><p>兔耳朵！兔尾巴！那副理直气壮又天真烂漫的模样！再配上<span class="hint--info hint--rounded hint--top" data-hint="普卡的 CV" ontouchstart>菱川花菜</span>灵动得恰到好处的配音——<ruby>白玉<rt>しらたま</rt></ruby>老师，您画的是角色吗？您这是在我的理智值上直接开洞啊！尤其是<ruby>普卡<rt>プーカ</rt></ruby>那股有点调皮、有点黏人，却又总在不经意间体贴别人的劲儿，简直让人毫无招架之力。<ruby>风花<rt>ふうか</rt></ruby>也一样，可爱得犯规。至此，我那套极不严谨的评价体系已经给出了最终结论：<strong>可爱，喜欢，还想再多看一会儿。完毕。</strong></p><div class="gallery-container" data-type="data" data-button="" data-limit="10" data-first="10">    <div class="gallery-items">[{"url":"/images/kazeshiro/pooka-manga.webp","alt":"趴在床上看漫画、笑得眯起眼的普卡","title":"看漫画看到笑出声的普卡"},{"url":"/images/kazeshiro/pooka-parfait.webp","alt":"捧着草莓芭菲、一脸幸福的普卡","title":"芭菲当前，幸福满溢"}]</div>  </div><p><em>官网公开的事件 CG。图源：《とける風花とシロうさぎ》官方网站。</em></p><p>咳。理智回归。虽然只回归了一丢丢。</p><h2 id="会呼吸的日常">会呼吸的日常</h2><p>真正让我喜欢上《风白》的，不只是白玉老师那种柔软、明亮、连笑容都带着体温的画风。秋穗身上那股令人安心的青梅竹马气息，乃良笨拙却真挚的认真，再加上一众性格鲜明、各有脾气的配角，共同撑起了这部作品鲜活得仿佛会呼吸的日常。大家聊天、拌嘴、互相添点小麻烦，又总在关键时刻伸出手来。很多时候，故事里什么大事都没发生，屏幕前的我却看得眉开眼笑。</p><p><img src="/images/kazeshiro/cast-lineup.webp" alt="《融化的风花与白兔》角色立绘一览" title="左起：普卡、乃良、秋穗、春香、绘利、美世"></p><p><em>左起：普卡、乃良、秋穗、春香、绘利、美世。官网角色立绘，由博主拼合。</em></p><p>仔细想来，我们平日里说的幸福，哪有那么多惊天动地的瞬间呢？它往往只是有人记得你的口味，有人愿意听你说些废话；又或是推开门时，屋子里不再安静得只剩下自己的回声。</p><p>而那些围坐在吧台边、笑着拌嘴的人，其实也都悄悄揣着各自放不下的东西。普卡有一句话，我一直记着：</p><blockquote><p><strong>普卡</strong>：“大家都带着只属于自己的‘挚爱’活着。只是没有表现出来而已。”</p></blockquote><p>也正因为每个人心里都藏着这样一份挚爱，那些吵吵闹闹、看似什么都没发生的日常，才显得格外珍贵。</p><div class="gallery-container" data-type="data" data-button="" data-limit="10" data-first="10">    <div class="gallery-items">[{"url":"/images/kazeshiro/bar.webp","alt":"稔打理的酒吧：暖黄灯光下的吧台与酒柜","title":"稔的酒吧"},{"url":"/images/kazeshiro/shopping-street.webp","alt":"岩手县小镇里冷清的拱廊商店街","title":"商店街"}]</div>  </div><p><em>大家聚在一起的酒吧，和故事开始的那条寂寥商店街。图源：官方网站。</em></p><h2 id="看不见的少女，看见了身边的人">看不见的少女，看见了身边的人</h2><p>也正因如此，我重新理解了普卡之于<ruby>稔<rt>みのる</rt></ruby>的意义。</p><p>起初的她像一场不讲道理的奇迹：明明就站在那里，却几乎没有人能看见。她究竟是幻象、灵魂，还是某种无法命名的存在？对此，普卡本人倒是看得很开：</p><blockquote><p><strong>普卡</strong>：“幽灵啊、灵魂啊、妖精啊、幻影啊。我觉得这些全都差不多。”</p></blockquote><p>是啊，比起追问她到底是什么，我更在意的是——她确确实实让稔那段停摆的生活重新流动了起来。一个几乎不被任何人看见的少女，却让他重新看见了身边的人。这份奇妙的反差，恰好呼应了作品想传达的内核：<strong>眼睛所见，从来都不是世界的全部。</strong></p><p><img src="/images/kazeshiro/pooka-piggyback.webp" alt="星空雪夜里，普卡趴在稔的背上" title="雪夜里的背影"></p><p><em>官网事件 CG。官网故事页的第一句，正是「見えてるものが全てじゃない」。</em></p><h2 id="记忆缺了一角，人还是那个人吗">记忆缺了一角，人还是那个人吗</h2><p>而在风花醒来、普卡的记忆却没能完整归来之后，故事留下了一个比“这到底算不算 Happy Ending”更让我在意的问题：<strong>如果记忆缺失了，一个人还是原来的那个人吗？</strong></p><p>我不愿轻巧地说一句“普卡已经彻底回来了”，因为那会把告别时的不舍一笔勾销；可我也不认为普卡就此消失了。人与人之间的羁绊，从来不只是一份能被准确复述的记忆。曾经的陪伴，因她而发生的改变，以及重逢时那些说不清来由的熟悉与亲近，都是真真切切发生过的事。风花或许没法记起那只兔子的每一次胡闹，但那些共同度过的时光，绝不会因此被撤回。</p><p>说到底，这个问题的答案，普卡早就借着另一段对话，轻轻放在了稔的手心里：</p><div class="note pink flat"><p><strong>普卡</strong>：“小稔，你之前说过，姐姐……爸爸妈妈都消失不见了，对吧？”</p><p><strong>普卡</strong>：“小稔，你心里的挚爱没有消失吧？”</p><p><strong>稔</strong>：“……”</p><p><strong>普卡</strong>：“所以，不用勉强自己抹掉它。”</p><p><strong>普卡</strong>：“自己心里的挚爱不用抹去。让它和你一起在心里活下去就好。这样一来——”</p><p><strong>普卡</strong>：“<strong>你肯定不会再寂寞了。</strong>”</p></div><p>失去的人不必被抹去，挚爱可以一直带在身上。放到风花与普卡身上，大概也是同样的道理：记忆或许会缺一角，可住进心里的那个人，不会因此离开。</p><p><img src="/images/kazeshiro/important-people.webp" alt="官方宣传画面：「大切な人は、消えない」" title="大切な人は、消えない"></p><p><em>官方 PV 封面上的那句「大切な人は、消えない」——重要的人，不会消失。</em></p><blockquote><p>风花落在掌心，终究会融化。可掌心曾触到的那一点凉意，是真的。</p></blockquote><h2 id="《星白》与《风白》：同一个问题的两种回答">《星白》与《风白》：同一个问题的两种回答</h2><p>在我看来，《星白》与《风白》其实在探讨相似的命题：<strong>失去之后，留下来的人要怎样继续生活？</strong> 不同的是，《星白》把遗憾酿进了幸福的最深处；《风白》则愿意让人在跨过遗憾之后，重新拥抱幸福。前者让我更为动容，后者却让我长长地松了一口气。这两种感动并不冲突，更不必拿“谁更会发刀”来分个高下。</p><h2 id="喜欢归喜欢，也说几句实话">喜欢归喜欢，也说几句实话</h2><p>当然，喜欢归喜欢，面对剧本我还是得说几句实话。</p><mark class="hl-label green">前中期：日常铺得扎实</mark> <mark class="hl-label orange">后期：危机与救援偏赶</mark><p>《风白》的前中期很舍得把笔墨交给日常，人物关系与细碎的伏笔也因此一点点积攒了起来。可到了后期，尤其是危机与救援这些关键段落，事件的展开与收束都显得过于匆忙。热闹的场面固然能把情绪推上去，几位配角也纷纷迎来了各自的高光，但核心矛盾本身却少了一点沉甸甸的分量。那些关于失去、记忆与自我认同的叩问，本值得人物再多挣扎片刻，也值得剧本留出更深远的回响。</p><p>我并不觉得它非得写成悲剧才算深刻。恰恰相反，我很喜欢这个结局。只是，<strong>圆满不该成为仓促的借口，治愈也完全可以拥有更有说服力的过程。</strong> 与《星白》相比，这是我觉得《风白》最可惜的地方。</p><h2 id="老朋友，在另一个故事的街角">老朋友，在另一个故事的街角</h2><p>还有一件事，必须单独拎出来说：真白和狩叶出场的那一刻，我内心直接全体起立！</p><p><strong>啊啊啊，是熟人！是那趟星空列车上的老朋友！</strong></p><p><img src="/images/kazeshiro/hoshishiro-kv.webp" alt="《星空列车与白的旅行》主视觉：月下的星空列车与三位少女" title="《星空列车与白的旅行》主视觉"></p><p><em>《星空列车与白的旅行》主视觉，左起：狩叶、诺瓦、音理。图源：しらたま官方网站。</em></p><p>对没玩过前作的人来说，这或许只是一次小小的客串；可对曾经认真陪她们走完那段旅途的玩家而言，那是一种难以言说的狂喜与慰藉。原来我们的故事翻过了最后一页，她们的日子却还在继续；原来那些曾让人心疼、让人挂念的角色，也能在另一个故事的街角，与我们不期而遇。</p><p>这是我格外偏爱的世界观延续方式。它不需要长篇大论地解释什么，只是让熟悉的人再出现一次，就足以让人轻轻感叹一句：真好啊。</p><h2 id="晚安，普卡">晚安，普卡</h2><p>说到底，若单论剧本的完整度与情感冲击，我依然更偏爱《星白》。那份藏在圆满之下的遗憾，至今想起来都有些发疼。但《风白》在我心里同样有着无可替代的位置：它有可爱的普卡与风花，有个性鲜明、吵吵闹闹的伙伴们，有老朋友带来的惊喜，也有一个让我终于能够笑着接受的结局。</p><p>我可以一边抱怨后期太赶，一边坚定地宣布：<strong>普卡可爱捏！</strong> 这两件事完全不矛盾。理性评分归理性评分，白玉厨的私心归私心，谁也别想剥夺我喜欢小兔子的权利。（严肃）</p><p>或许成长就是这样吧。我们没法永远留住某个人、某段时光，甚至没法保证记忆始终完整。可离别从不意味着过往的一切归零，遗忘也抹不去爱曾确凿存在过的痕迹。</p><blockquote><p>风花终会融化，但那只闯进生活里的白兔，已经让一些人的冬天不再寒冷。</p></blockquote><p>那么，就带着那些未必完整、却确实温暖过我们的记忆，继续往前走吧。</p><p>晚安，普卡。愿你和风花，都能拥有很多很多个平凡又可爱的明天。</p><details class="toggle" ><summary class="toggle-button" style="">作品档案：《融化的风花与白兔》</summary><div class="toggle-content"><table><thead><tr><th>项目</th><th>内容</th></tr></thead><tbody><tr><td>原名</td><td>とける風花とシロうさぎ</td></tr><tr><td>社团</td><td>サークルしらたまこ（オリジナルビジュアルノベルプロジェクト第二弾）</td></tr><tr><td>发售</td><td>2026 年 8 月 16 日（コミックマーケット108、BOOTH 及各店铺）</td></tr><tr><td>平台 / 介质</td><td>PC / DVD-ROM，全年龄，除主人公外全程语音</td></tr><tr><td>剧本</td><td>さかき傘</td></tr><tr><td>企划・原画・角色设计</td><td>しらたま</td></tr><tr><td>音乐</td><td>project lights</td></tr><tr><td>主题曲</td><td>OP「風花のブーケ」（保科めぐみ）／ ED「Snowy Night」（齋藤亜里菜）</td></tr><tr><td>主要声优</td><td>普卡：菱川花菜　乃良：立花日菜　秋穗：永野愛　春香：星谷美緒　绘利：日岡なつみ　美世：友永朱音</td></tr></tbody></table><p>资料整理自官方网站，截至 2026 年 11 月 5 日。</p></div></details><a class="btn-beautify pink larger" href="https://shiratamaco.com/kazeshiro/" title="《风白》官方网站"><i class="fas fa-snowflake"></i><span>《风白》官方网站</span></a><a class="btn-beautify red larger" href="https://www.youtube.com/watch?v=gaudTfGstVs" title="官方 PV"><i class="fab fa-youtube"></i><span>官方 PV</span></a><a class="btn-beautify blue larger" href="https://store.steampowered.com/app/1567800/" title="《星白》Steam 页面"><i class="fab fa-steam"></i><span>《星白》Steam 页面</span></a><div class="note info flat"><p><strong>图片说明</strong>：文中《融化的风花与白兔》的横幅、事件 CG、角色立绘与背景均来自<a href="https://shiratamaco.com/kazeshiro/">官方网站</a>，横幅由官网首屏的雪景与普卡立绘合成，立绘一览为博主拼合；《星空列车与白的旅行》主视觉来自<a href="https://shiratamaco.com/">しらたま官方网站</a>，游戏画面来自 <a href="https://store.steampowered.com/app/1567800/">Steam 商店页</a>。版权归 ©2026 shiratamaco 及各权利方所有，仅作评论配图使用。</p></div>]]>
    </content>
    <id>https://blog.luomo.moe/posts/b5f12026.html</id>
    <link href="https://blog.luomo.moe/posts/b5f12026.html"/>
    <published>2026-11-05T10:00:00.000Z</published>
    <summary>从《星空列车与白的旅行》的后劲里爬出来，又被一只白兔接住。《融化的风花与白兔》通关随想：可爱的普卡、会呼吸的日常、记忆与自我，以及后期节奏的那点可惜。含两作剧透。</summary>
    <title>风花融化以后，白兔还在吗？——《融化的风花与白兔》通关随想</title>
    <updated>2026-11-05T10:00:00.000Z</updated>
  </entry>
  <entry>
    <author>
      <name>洛墨-天染-依然</name>
    </author>
    <category term="游戏星海" scheme="https://blog.luomo.moe/categories/%E6%B8%B8%E6%88%8F%E6%98%9F%E6%B5%B7/"/>
    <category term="Minecraft" scheme="https://blog.luomo.moe/tags/Minecraft/"/>
    <category term="PCL" scheme="https://blog.luomo.moe/tags/PCL/"/>
    <category term="HMCL" scheme="https://blog.luomo.moe/tags/HMCL/"/>
    <category term="启动器" scheme="https://blog.luomo.moe/tags/%E5%90%AF%E5%8A%A8%E5%99%A8/"/>
    <content>
      <![CDATA[<link rel="stylesheet" class="aplayer-secondary-style-marker" href="/assets/css/APlayer.min.css"><script src="/assets/js/APlayer.min.js" class="aplayer-secondary-script-marker"></script><script class="meting-secondary-script-marker" src="/assets/js/Meting.min.js"></script><link rel="stylesheet" type="text&#x2F;css" href="/pluginsSrc/hexo-tag-hint/dist/hexo-tag-hint.min.css"><p>10 月 20 号下午，群里突然有人问：PCL 的下载页怎么没了？</p><p>一开始大家还以为是 bug，后来才发现 PCL 直接发了 2.99.0.0，把下载游戏的功能整个删了。当天晚上 PCL-CE 发了 2.15.1，HMCL 的开发版也跟着更新，两边都在中国大陆关掉了游戏下载。</p><p>不用慌，已经装好的游戏还能正常启动，存档也一个都不会少。这次动的只是「用第三方启动器下载、安装游戏本体」这一步。下面按你现在的情况分开说，挑自己那段看就行。</p><blockquote><p>文中的版本号和日期都对照过各启动器在 GitHub 上的发布页，截至 2026-10-21。事情还在变，如果你看到的和这里写的不一样，以官方最新公告为准。</p></blockquote><span id="more"></span><h2 id="先看看自己是哪种情况">先看看自己是哪种情况</h2><div class="mermaid-wrap"><pre class="mermaid-src" data-config="{}" hidden>    flowchart TD  S[&quot;你现在的状态&quot;] --&gt; Q1{&quot;电脑上有 PCL &#x2F; PCL-CE &#x2F; HMCL 吗？&quot;}  Q1 -- &quot;没有&quot; --&gt; A[&quot;还没装启动器\n→ 走官方渠道&quot;]  Q1 -- &quot;有&quot; --&gt; Q2{&quot;版本是不是\n已经关了下载？&quot;}  Q2 -- &quot;还没&quot; --&gt; B[&quot;还没更新\n→ 先关自动更新，把版本备齐&quot;]  Q2 -- &quot;已经关了&quot; --&gt; C[&quot;已经更新了\n→ 换回旧版，或者换官方启动器&quot;]  Q2 -- &quot;不知道&quot; --&gt; V[&quot;先看下面怎么查版本&quot;]  </pre></div><table><thead><tr><th>启动器</th><th>还能下载的版本</th><th>关掉下载的版本</th><th>发布日期</th></tr></thead><tbody><tr><td>PCL（龙腾猫跃原版）</td><td>2.13.1.1 及更早（2.14.0.0 预览版也还能下）</td><td>2.99.0.0，所有地区都删了下载</td><td>2026-10-20</td></tr><tr><td>PCL-CE（社区版）</td><td>2.15.0 及更早</td><td>2.15.1</td><td>2026-10-20</td></tr><tr><td>HMCL 稳定版</td><td>3.16.4（目前最新的稳定版）</td><td>暂时还没有</td><td>—</td></tr><tr><td>HMCL 开发版</td><td>3.17.0.361 及更早</td><td>3.17.0.362 起</td><td>2026-10-20</td></tr></tbody></table><p>HMCL 的稳定版暂时没动，但开发版已经加了限制，下一个稳定版多半也会带上。用 HMCL 的话，最好也照着「还没更新」那一节先准备好。</p><h3 id="怎么查版本">怎么查版本</h3><ul><li>PCL / PCL-CE：打开启动器，点顶部「更多」→「关于与鸣谢」就能看到（不同版本的菜单名可能有点出入）。怕一打开就被自动更新（原因后面会说），也可以不打开，右键启动器的 <code>.exe</code> →「属性」→「详细信息」，看「文件版本」。</li><li>HMCL：「设置」→「通用」里有当前版本，或者直接看文件名，比如 <code>HMCL-3.16.4.exe</code>。</li></ul><h2 id="动手之前">动手之前</h2><h3 id="1-启动器只认官方渠道">1. 启动器只认官方渠道</h3><p>这种时候，比玩不了游戏更麻烦的是电脑中毒。已经有人仿冒 HMCL 官网发带木马的安装包了，被点名的有 <code>hmcl.io</code> 和 <code>hmcl.app</code>。还有群里传的「PCL 解锁版」「去限制版」「绿色修改版」，不知道是谁改的，也不知道改了什么，别下，别开，也别往群里转。</p><p>文中用到的地址我都放在了<a href="#official-links">文末</a>，下载前对一下网址。</p><p><a id="backup"></a></p><h3 id="2-先把存档备份一份">2. 先把存档备份一份</h3><p>后面不管换版本还是换启动器，有备份心里就踏实：</p><ol><li>找到 <code>.minecraft</code> 文件夹。PCL 和 HMCL 默认把它放在启动器所在的文件夹里；官方启动器的在 <code>%APPDATA%\.minecraft</code>，按 <code>Win + R</code>，把这串粘进去回车就能打开。</li><li>存档在 <code>saves</code> 里。PCL 开了「版本隔离」的话，存档在 <code>.minecraft\versions\版本名\saves</code>。</li><li>把 <code>saves</code> 整个复制到 U 盘、网盘或者别的盘。嫌麻烦就直接复制整个 <code>.minecraft</code>。</li></ol><h3 id="3-别去改系统地区">3. 别去改系统地区</h3><p>PCL-CE 和 HMCL 是看 Windows 的地区、语言、时区和网络位置来判断你在哪的。改这些会连带影响电脑上别的软件，开发者也随时可能换检测方式，改了不一定有用，还可能违反相关规定。这篇不讲这个，下面说的都是正常能走的路。</p><h2 id="还没装启动器">还没装启动器</h2><p><a id="official-launcher"></a></p><h3 id="官方-Minecraft-Launcher（最推荐）">官方 Minecraft Launcher（最推荐）</h3><p>Mojang 自己的启动器，这次的事和它没关系。需要一个买过 Java 版的微软账号，没买的话只能玩试玩版。</p><ol><li>打开官网下载页 <code>https://www.minecraft.net/zh-hans/download</code>，下载 Windows 版。也可以直接下安装包：<code>https://launcher.mojang.com/download/MinecraftInstaller.msi</code>。</li><li>双击 <code>MinecraftInstaller.msi</code>，一路「下一步」。</li><li>打开 Minecraft Launcher，用微软账号登录。</li><li>左边选「Minecraft: Java Edition」。</li><li>玩最新版直接点「游玩」。想玩旧版本就点上面的「配置」→「新配置」，在「版本」里选好、保存，回到「游戏」页在左下角选这个配置，再点「游玩」。</li><li>第一次要下载游戏文件，国内可能会慢，挂着等它下完就好，只用下这一次。</li></ol><p>官方启动器只管原版。想玩模组的话，Fabric、Forge 这些加载器要去它们的官网下安装器，装完会自己出现在官方启动器的版本列表里。不过先把原版跑起来再说。</p><h3 id="网易《我的世界》中国版（不想买正版）">网易《我的世界》中国版（不想买正版）</h3><p>网易代理的版本，官网 <code>https://mc.163.com/</code> 下载，免费玩，要实名。它的启动器和账号都是自己一套，和国际版不通，存档、皮肤、好友带不过去，服务器也是分开的。</p><p>如果你主要是跟同学一起玩、也没打算买国际版，选它就挺合适。</p><h3 id="Prism-Launcher（买了正版，还想用第三方启动器）">Prism Launcher（买了正版，还想用第三方启动器）</h3><p>用惯了 PCL、HMCL 那种多版本管理、一键装模组，可以试试 Prism Launcher。国外的开源启动器，没有地区限制。</p><ol><li>在 <code>https://prismlauncher.org/download/</code> 下载 Windows 安装包，装好。</li><li>第一次打开会让你选语言和 Java，语言选简体中文，其他按默认走。</li><li>右上角「账户」→「管理账户」→「添加微软账户」，跟着提示在浏览器里登录。</li><li>左上角「添加实例」，选游戏版本；要装模组就在右边顺手选上加载器，然后确定。</li><li>选中实例，点「启动」。</li></ol><p>Prism 是从 Mojang 官方服务器下游戏，国内速度一般，耐心等等。</p><h2 id="装了启动器，还没更新">装了启动器，还没更新</h2><p>这种情况最该赶紧动手：你手上的旧版还能下游戏，只要别让它自己升级就行。</p><h3 id="PCL（龙腾猫跃原版）">PCL（龙腾猫跃原版）</h3><p>PCL 的「启动器更新」默认是「在有新版本时自动下载」。联着网直接打开，很可能就被升到 2.99.0.0 了。所以顺序是先断网再打开：</p><ol><li>断网：拔网线，或者点右下角的网络图标把 Wi-Fi 关掉。</li><li>打开 PCL。</li><li>「设置」→ 左侧「其他」，在「启动器」那一栏把「启动器更新」改成「关闭更新提示」。</li><li>关掉 PCL，连上网再打开，去「更多」→「关于与鸣谢」看一眼，版本号没变就对了。</li></ol><p>不想打开 PCL 也行，直接改配置文件：</p><ol><li>先把 PCL 关掉，任务栏里也别留着。</li><li>进 PCL 所在文件夹下的 <code>PCL</code> 子文件夹，用记事本打开 <code>Setup.ini</code>。</li><li>找到 <code>SystemSystemUpdate:</code> 开头的那行，把冒号后面的数字改成 <code>3</code>。没有这行就在最后加一行：</li></ol><figure class="highlight ini"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">SystemSystemUpdate:3</span><br></pre></td></tr></table></figure><ol start="4"><li>保存。冒号要用英文的。</li></ol><p>如果更新窗口已经弹出来了，别点「更新」，在任务栏的 PCL 图标上右键关掉，再按上面的方法关更新。重新打开发现已经是 2.99.0.0 了，那就直接看<a href="#case-c">下一节</a>。</p><h3 id="PCL-CE（社区版）">PCL-CE（社区版）</h3><p>PCL-CE 默认也会自动下载新版本，做法和原版差不多：</p><ol><li>断网，打开 PCL-CE。</li><li>在「设置」里找到启动器更新的选项，把自动下载改成只提示或者关闭。</li><li>连上网。之后再提示 2.15.1，别点。</li></ol><h3 id="HMCL">HMCL</h3><p>HMCL 不会自己偷偷升级，得你手动点才会更新，所以注意两件事就够了：</p><ol><li>「设置」→「通用」里把更新通道设成「稳定版」，别用开发版。</li><li>以后再看到更新提示，先确认新版本有没有加地区限制，拿不准就先不点。这篇文章有新情况也会更新。</li></ol><h3 id="趁现在把要玩的版本都装上">趁现在把要玩的版本都装上</h3><p>旧版还能下的时候，把以后可能用到的一次装齐：</p><ul><li>最新正式版，还有你和朋友常玩的几个版本；</li><li>要用的 Fabric、Forge、NeoForge 之类的加载器；</li><li>常玩的整合包。</li></ul><p>装完每个版本都进一次主菜单，确认文件是完整的。PCL-CE 2.15.1 在国内只让启动文件齐全的版本，缺了文件不会帮你补，现在多跑一遍，后面能省不少事。</p><p><a id="case-c"></a></p><h2 id="已经更新了">已经更新了</h2><p>先别卸载。装好的版本还能启动，存档也都在。接下来有两条路：换回官方的旧版本，或者干脆换成官方启动器。</p><p>换回旧版只是救急，旧版本说不定哪天就被停用，下载源也可能有变化。想图个长久安稳，还是换官方启动器更省心。</p><h3 id="换回官方旧版">换回官方旧版</h3><p>下面的旧版本都是从启动器作者自己的 GitHub 仓库下的。换的时候只换启动器的 <code>.exe</code>，<code>.minecraft</code> 和 <code>PCL</code> 文件夹都别动，设置和游戏都会留着。换之前记得先<a href="#backup">备份存档</a>。</p><p><strong>PCL：换回 2.13.1.1</strong></p><ol><li>打开 PCL 官方仓库的 2.14.0.0 标签：<code>https://github.com/Meloong-Git/PCL/tree/2.14.0.0</code>。</li><li>在文件列表里点「最新正式版.zip」，再点右边的下载按钮（Download raw file）。别看标签叫 2.14.0.0，压缩包里其实是 2.13.1.1 正式版，我下下来看过文件版本。</li><li>关掉正在运行的 PCL，断网。</li><li>解压，用里面的 <code>Plain Craft Launcher 2.exe</code> 替换原来的启动器。旧的 2.99 可以先改个名留着，别急着删。</li><li>打开 PCL，第一件事就是按上一节把「启动器更新」改成「关闭更新提示」。</li><li>连上网，在「更多」→「关于与鸣谢」确认版本是 2.13.1.1。</li></ol><p><strong>PCL-CE：换回 2.15.0</strong></p><ol><li>打开 <code>https://github.com/PCL-Community/PCL-CE/releases/tag/v2.15.0</code>。</li><li>在下面的「Assets」里下载 <code>PCL2_CE_Release_x64.exe</code>。一般电脑都选这个，骁龙这类 ARM 电脑选 <code>ARM64</code>。</li><li>关掉 PCL-CE，断网，用新下的文件替换原来的 <code>.exe</code>。</li><li>打开后先关自动更新，再连网。</li></ol><p><strong>HMCL：换回稳定版 3.16.4</strong></p><ol><li>在 <code>https://github.com/HMCL-dev/HMCL/releases/tag/v3.16.4</code> 下载 <code>HMCL-3.16.4.exe</code>，或者去官网 <code>https://hmcl.huangyuhui.net/download/</code> 下稳定版。</li><li>关掉 HMCL，用新文件替换旧的 <code>.exe</code>，或者放在同一个文件夹里直接运行新的。</li><li>打开后到「设置」→「通用」，更新通道选「稳定版」。</li></ol><h3 id="换成官方启动器，把存档搬过去">换成官方启动器，把存档搬过去</h3><ol><li>照<a href="#official-launcher">前面的步骤</a>装好官方启动器，登录，把要玩的版本启动一次，让它把游戏下完。</li><li>打开原来启动器的存档文件夹：<code>.minecraft\saves</code>，开了版本隔离的在 <code>.minecraft\versions\版本名\saves</code>。</li><li>把里面的存档文件夹整个复制到 <code>%APPDATA%\.minecraft\saves</code>。</li><li>在官方启动器里选同一个版本或者更高的版本进游戏，单人游戏列表里就能看到了。</li></ol><p>存档往高版本升没问题，反过来用低版本打开高版本的存档容易坏档。第一次用新版本打开老存档之前，确认备份还在。</p><h2 id="一些常见的疑问">一些常见的疑问</h2><p><strong>PCL 2.99 是不是彻底不能用了？</strong></p><p>不是。它只是没了下载功能，装好的版本照样能启动。</p><p><strong>网上说是谁举报了、哪家公司施压，靠谱吗？</strong></p><p>PCL 作者在 2.99.0.0 的发布说明里只说是为了遵守相关法律法规，还特意说了和网易、微软没关系。其他说法都没有官方证实，网上传的「通知截图」也有人指出是假的，看看就好，别急着转。</p><p><strong>什么时候能恢复？</strong></p><p>谁也说不准。想第一时间知道，就盯着各启动器的 GitHub 发布页和官方公告，群里的小道消息别太当真。</p><p><strong>已经下了一个「解锁版 PCL」怎么办？</strong></p><p>还没打开的话就别打开了，直接删掉。已经运行过的，用杀毒软件全盘扫一遍，再把常用账号的密码改一下，尤其是微软账号。</p><p><a id="official-links"></a></p><h2 id="官方地址">官方地址</h2><table><thead><tr><th>用途</th><th>地址</th></tr></thead><tbody><tr><td>官方 Minecraft Launcher</td><td><code>https://www.minecraft.net/zh-hans/download</code></td></tr><tr><td>官方安装包直链</td><td><code>https://launcher.mojang.com/download/MinecraftInstaller.msi</code></td></tr><tr><td>网易《我的世界》中国版</td><td><code>https://mc.163.com/</code></td></tr><tr><td>Prism Launcher</td><td><code>https://prismlauncher.org/download/</code></td></tr><tr><td>PCL 官方仓库</td><td><code>https://github.com/Meloong-Git/PCL</code></td></tr><tr><td>PCL-CE 官方仓库</td><td><code>https://github.com/PCL-Community/PCL-CE</code></td></tr><tr><td>HMCL 官网 / 仓库</td><td><code>https://hmcl.huangyuhui.net/</code> · <code>https://github.com/HMCL-dev/HMCL</code></td></tr></tbody></table><h2 id="参考">参考</h2><ul><li><a href="https://github.com/Meloong-Git/PCL/releases/tag/2.99.0.0">PCL 2.99.0.0 发布说明</a>（2026-10-20）</li><li><a href="https://github.com/PCL-Community/PCL-CE/pull/3673">PCL-CE #3673：中国大陆地区禁用 Minecraft 安装功能</a>、<a href="https://github.com/PCL-Community/PCL-CE/releases/tag/v2.15.1">PCL-CE 2.15.1 发布说明</a></li><li><a href="https://github.com/HMCL-dev/HMCL/pull/7017">HMCL #7017：在中国大陆地区禁用游戏下载功能</a>、<a href="https://github.com/HMCL-dev/HMCL/releases/tag/v3.17.0.362">HMCL 3.17.0.362</a></li><li><a href="https://www.nexusmc.cn/forum/post/hmcl">NexusMC：警惕仿冒 HMCL 下载网站</a></li><li><a href="https://www.lyyweb.top/archives/5g7q9sd6/">PCL 大事件！MC 启动器关闭 MC 下载？？？</a>：关 PCL 自动更新的方法是从这里看到的，我又对着 PCL 源码确认了一遍设置项。</li></ul><p>存档保住了的话，顺手把这篇转给群里那个还在问「是不是玩不了 MC 了」的朋友吧。</p>]]>
    </content>
    <id>https://blog.luomo.moe/posts/a7c12026.html</id>
    <link href="https://blog.luomo.moe/posts/a7c12026.html"/>
    <published>2026-10-21T12:00:00.000Z</published>
    <summary>10 月 20 日起，PCL、PCL-CE、HMCL 陆续在中国大陆关掉了 Minecraft 本体下载。没装启动器、装了还没更新、已经更新了，三种情况分别该怎么做，这篇一次讲清楚。</summary>
    <title>PCL、HMCL 不能下 MC 了，接下来怎么办</title>
    <updated>2026-10-21T12:00:00.000Z</updated>
  </entry>
  <entry>
    <author>
      <name>洛墨-天染-依然</name>
    </author>
    <category term="技术实践" scheme="https://blog.luomo.moe/categories/%E6%8A%80%E6%9C%AF%E5%AE%9E%E8%B7%B5/"/>
    <category term="域名" scheme="https://blog.luomo.moe/tags/%E5%9F%9F%E5%90%8D/"/>
    <category term="零基础" scheme="https://blog.luomo.moe/tags/%E9%9B%B6%E5%9F%BA%E7%A1%80/"/>
    <category term="中文域名" scheme="https://blog.luomo.moe/tags/%E4%B8%AD%E6%96%87%E5%9F%9F%E5%90%8D/"/>
    <category term="DNS" scheme="https://blog.luomo.moe/tags/DNS/"/>
    <category term="Cloudflare" scheme="https://blog.luomo.moe/tags/Cloudflare/"/>
    <category term="建站" scheme="https://blog.luomo.moe/tags/%E5%BB%BA%E7%AB%99/"/>
    <content>
      <![CDATA[<link rel="stylesheet" class="aplayer-secondary-style-marker" href="/assets/css/APlayer.min.css"><script src="/assets/js/APlayer.min.js" class="aplayer-secondary-script-marker"></script><script class="meting-secondary-script-marker" src="/assets/js/Meting.min.js"></script><link rel="stylesheet" type="text&#x2F;css" href="/pluginsSrc/hexo-tag-hint/dist/hexo-tag-hint.min.css"><p>刷到「<a href="http://xn--XX-mu9cslv6f6w9n.com">XX奶龙大学.com</a>」，随手一输，居然真能打开，有的还把你送到学校官网。</p><p>第一反应：<strong>学校终于被奶蛙接管了？</strong></p><p>实际操作：有人注册了一个有梗的域名，再给它配上网页或者跳转。互联网门牌还能这么起，属实是把招生简章玩成了精神状态检测。(¬‿¬)</p><p>这篇就从这个梗出发，带你完成一条实用路线：<strong>买一个自己的域名 → 做一个简单网页 → 上传上线 → 绑定域名与 HTTPS。</strong> 不需要先学编程，也不用先买一台服务器。你最后得到的，可以是个人主页、作品集、社团导航，或者一张朋友点进去就绷不住的整活页面。</p><span id="more"></span><h2 id="1-先吃个瓜：这些「奶龙大学」到底在干什么" class="lm-own-number">1. 先吃个瓜：这些「奶龙大学」到底在干什么</h2><p>这波梗已经玩到了高校论坛：<a href="https://f.yourtj.de/p/post/572">YourTJ 上有人分享了《上海奶龙大学.com》</a>，B站也有<a href="https://www.bilibili.com/video/BV1xypd6FEvw/">《重要通知：中国奶龙大学域名已被购》</a>这样的投稿。</p><p>挑三个网址点开看看，玩法还不太一样：</p><table><thead><tr><th>网址</th><th>打开后会发生什么</th><th>你可以怎么做</th></tr></thead><tbody><tr><td><a href="https://xn--fhqu4y0gz4ew51bfy2e.com/">上海奶龙大学.com</a></td><td>返回 HTTP 302，地址指向同济大学官网 <code>www.tongji.edu.cn</code></td><td>域名收到访问后，直接让浏览器去另一个网址</td></tr><tr><td><a href="https://xn--psszg58cf1d8u4erdxb.com/">苏州奶龙大学.com</a></td><td>页面写着“非官方跳转页”，HTML 设置 5 秒后前往西交利物浦大学官网</td><td>先显示趣味页面，再倒计时跳转</td></tr><tr><td><a href="https://xn--psszg58cy52cu6lj57b.com/">甘肃奶龙大学.com</a></td><td>自己的页面里有 GO 按钮，链接到兰州理工大学官网；还有<a href="https://github.com/TomyJan/gansunailongdaxue.com">公开源码仓库</a></td><td>域名绑定自己的页面，由读者选择是否点击</td></tr></tbody></table><p>自己做这类页面，记得写上“个人／非官方”，学校链接也要填对。玩梗归玩梗，别让朋友把你的页面当成招生入口。</p><p>你完全可以把同一套方法用到正常项目上：一个短域名指向作品集，一个社团域名展示活动，一个个人域名连接自己的博客。</p><h2 id="2-域名、网站、DNS：三位各干各的，别让奶蛙独自背锅" class="lm-own-number">2. 域名、网站、DNS：三位各干各的，别让奶蛙独自背锅</h2><p>把网站想成一间店：</p><p>下面用 <code>example.com</code> 举例，这是保留的演示域名；操作时换成自己买到的域名。</p><table><thead><tr><th>名词</th><th>好理解的比喻</th><th>实际负责什么</th></tr></thead><tbody><tr><td>域名</td><td>门牌，比如 <code>example.com</code></td><td>给人一个好记的访问地址</td></tr><tr><td>网页文件</td><td>店里的装修和内容</td><td>决定别人打开后看到什么</td></tr><tr><td>网站托管</td><td>店铺空间</td><td>保存文件，并把网页发给访客</td></tr><tr><td>DNS 解析</td><td>找店用的导航</td><td>告诉访问者，域名该去哪里找服务</td></tr><tr><td>HTTPS</td><td>加密的通信通道</td><td>加密浏览器和网站之间的传输</td></tr></tbody></table><p><strong>域名购买成功，只代表门牌到手。网页还要制作和托管，门牌还要连到服务。</strong></p><img src="/images/domain-beginner/naiwa-dns.webp" alt="奶蛙举着域名门牌：域名买了，网站呢？你给我变出来啊" width="560" loading="lazy" style="display:block;margin:24px auto;max-width:100%;height:auto"><p>再拆一个常见网址：</p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">https://blog.example.com/about</span><br><span class="line">└协议   └子域名└注册的域名 └网页路径</span><br></pre></td></tr></table></figure><p>你购买 <code>example.com</code> 后，通常可以自行设置 <code>www.example.com</code>、<code>blog.example.com</code> 等子域名，无需把每个都另买一遍。<code>/about</code> 则是网站里面的路径，不是另一个域名。</p><h3 id="中文域名是真的，不是浏览器被奶蛙夺舍">中文域名是真的，不是浏览器被奶蛙夺舍</h3><p>域名可以包含中文，这类叫 <strong>IDN（国际化域名）</strong>。系统会把中文部分转换成以 <code>xn--</code> 开头的 ASCII 形式，也叫 Punycode。中文显示和转换后的形式可以代表同一个域名；某些浏览器或后台显示长串编码是正常现象。想了解这串编码，可以看 <a href="https://www.icann.org/resources/idn">ICANN 的 IDN 介绍</a>和 <a href="https://www.icann.org/en/icann-acronyms-and-terms/a-label-en">A-label 定义</a>。</p><p><code>.com</code> 的注册局支持包括中文在内的多语言 IDN，但<strong>具体注册商是否提供、如何查询，以其当前支持范围为准</strong>。可以通过 <a href="https://www.verisign.com/resources/internationalized-domain-names/">Verisign 的 IDN 页面</a>了解注册渠道。</p><p>一个容易走错的门：<strong>Cloudflare Registrar 当前不支持注册或转入 IDN，包括 Punycode 形式。</strong> 这条限制针对其域名注册服务，别把它和 Cloudflare 的 DNS、网站托管混为一谈。<a href="https://developers.cloudflare.com/registrar/top-level-domains/">官方支持说明</a></p><p>零基础第一次练手，选一个短一点的英文域名会更省心；想玩中文梗，再选明确支持该中文后缀的注册商。不要默认“<a href="http://xn--fiq2fv5gi9cyzgpxyyzle92bid4aymnoyc.com">域名注册页面都能买中文.com</a>”。</p><h2 id="3-买域名：别让首年优惠给你施法" class="lm-own-number">3. 买域名：别让首年优惠给你施法</h2><p>下面用<strong>阿里云的普通域名注册流程</strong>举例，其他注册商也有类似步骤。本教程的主线先用英文域名完成；中文梗域名需要额外确认商家支持和完整报价。</p><h3 id="第一步：想三个候选，别和全网奶蛙抢同一个门牌">第一步：想三个候选，别和全网奶蛙抢同一个门牌</h3><p>用自己的网名、项目名、社团名组合，准备几个备选。起名尽量短、好念、好打；完整的后缀也是名字的一部分，<code>.com</code>、<code>.net</code> 和 <code>.cn</code> 是不同的地址。</p><p><strong>可注册</strong>才是常规新注册；页面如果显示“一口价”“交易”“溢价”，先看清那是在买什么。教程不需要你为了一个梗冲进二手域名拍卖场。</p><p>先确定能接受的<strong>每年续费成本</strong>。首年便宜，第二年未必还是这个价；中文域名也不一定和英文同后缀同价。</p><img src="/images/domain-beginner/naiwa-renewal.webp" alt="奶蛙看续费单：首年真香，续费你怎么不去抢" width="560" loading="lazy" style="display:block;margin:24px auto;max-width:100%;height:auto"><h3 id="第二步：在注册商官网查询完整域名">第二步：在注册商官网查询完整域名</h3><ol><li>打开 <a href="https://wanwang.aliyun.com/domain">阿里云域名注册页面</a>，登录自己的账号；按平台要求先完成账号实名认证。</li><li>输入候选名称，确认搜索结果里的<strong>完整域名和后缀</strong>。</li><li>找到显示可注册的候选，查看首年价格、续费价格、购买年限和是否有额外服务。</li><li>新手练习可以先注册一年，不必为了上传一页 HTML 同时买服务器、数据库或建站套餐。</li></ol><p>如果准备买中文域名，查询完整中文名称；无法查询时，先检查该商家的 IDN 支持，不要把“这个平台不卖”误读成“这个名字一定被抢了”。</p><h3 id="第三步：填持有人信息，完成邮件验证与实名认证">第三步：填持有人信息，完成邮件验证与实名认证</h3><p>按平台要求创建<strong>域名信息模板</strong>，选个人或单位，填写真实信息，完成邮箱验证并提交所需证件，等待状态变为**“模板实名成功”**，再使用它注册。域名持有人是谁，取决于登记的持有人信息，不能只看“是谁的账号付了钱”。这些资料仅在注册商的认证页面提交。<a href="https://help.aliyun.com/zh/dws/user-guide/how-to-complete-domain-name-authentication">信息模板官方说明</a></p><p>支付时选择审核通过的持有人模板和正确年限，检查订单，再付款。随后进入域名控制台，确认：<strong>域名出现在列表中、注册成功，等待域名状态变为“正常”，并核对到期日期</strong>。确认注册商发来的验证邮件，不要只凭支付成功截图宣布开学。<a href="https://help.aliyun.com/zh/dws/getting-started/quickly-register-a-new-domain-name">域名注册快速入门</a></p><h3 id="第四步：把续费这件事安排上">第四步：把续费这件事安排上</h3><p>愿意长期持有就开启自动续费，确认付款方式可用；同时给自己留一个到期提醒。启用账号的双重验证，保管好登录方式。</p><p>这里的“购买”更像按期限注册使用权。到期后未续费，域名可能停止工作甚至被别人重新注册。奶蛙可以装作无事发生，网站访客不会。</p><h2 id="4-先做网页：复制一个文件，你就有内容了" class="lm-own-number">4. 先做网页：复制一个文件，你就有内容了</h2><p>我们做的是<strong>静态网站</strong>：浏览器直接展示 HTML 等文件。个人介绍、作品列表、活动导航都适合这条路线；账号登录、收款、数据库等功能需要另做服务，不是贴一个按钮就会自动出现。</p><p>这里准备了一个没有外部依赖的入门页：</p><p><strong><a href="/downloads/domain-starter-v1.zip">下载网页模板 ZIP</a></strong></p><p>下载后解压，里面只有一个 <code>index.html</code>。双击它，浏览器会显示“摸鱼大学，今天开站”。这一步打开的是你电脑里的文件，其他人还访问不到；第 5 节再把它放到网上。</p><h3 id="改成自己的内容">改成自己的内容</h3><p>右键 <code>index.html</code>，选择记事本或其他文本编辑器打开。找到并修改这几处：</p><table><thead><tr><th>找到的内容</th><th>改成什么</th></tr></thead><tbody><tr><td><code>&lt;title&gt;摸鱼大学 · 我的第一个网站&lt;/title&gt;</code></td><td>浏览器标签上的名称</td></tr><tr><td><code>&lt;h1&gt;摸鱼大学，今天开站 🐟&lt;/h1&gt;</code></td><td>网页里的大标题</td></tr><tr><td>“欢迎来到我的互联网小窝”等段落</td><td>你的个人介绍、项目说明</td></tr><tr><td><code>href=&quot;https://example.com/&quot;</code></td><td>你希望按钮打开的<strong>完整网址</strong>，例如自己的博客</td></tr><tr><td>“去看看我的主页”</td><td>按钮文字</td></tr></tbody></table><p>保存后刷新浏览器，确认文字和按钮链接正确。“非学校官网”的说明也根据页面实际用途调整，趣味校园页就保留。</p><p>想从零建文件，也可以复制下面这一版。记事本点“另存为”，文件名填 <code>index.html</code>，<strong>保存类型选“所有文件”，编码选 UTF-8</strong>。Windows 文件管理器开启“查看 → 显示 → 文件扩展名”，确保它没有偷偷变成 <code>index.html.txt</code>。</p><figure class="highlight html"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">&lt;!doctype <span class="keyword">html</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">html</span> <span class="attr">lang</span>=<span class="string">&quot;zh-CN&quot;</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">head</span>&gt;</span></span><br><span class="line">  <span class="tag">&lt;<span class="name">meta</span> <span class="attr">charset</span>=<span class="string">&quot;utf-8&quot;</span>&gt;</span></span><br><span class="line">  <span class="tag">&lt;<span class="name">meta</span> <span class="attr">name</span>=<span class="string">&quot;viewport&quot;</span> <span class="attr">content</span>=<span class="string">&quot;width=device-width, initial-scale=1&quot;</span>&gt;</span></span><br><span class="line">  <span class="tag">&lt;<span class="name">title</span>&gt;</span>我的第一个网站<span class="tag">&lt;/<span class="name">title</span>&gt;</span></span><br><span class="line">  <span class="tag">&lt;<span class="name">style</span>&gt;</span><span class="language-css"></span></span><br><span class="line"><span class="language-css">    <span class="selector-tag">body</span> &#123; <span class="attribute">margin</span>: <span class="number">0</span>; <span class="attribute">background</span>: <span class="number">#fff9e8</span>; <span class="attribute">color</span>: <span class="number">#292820</span>;</span></span><br><span class="line"><span class="language-css">      <span class="attribute">font</span>: <span class="number">17px</span>/<span class="number">1.8</span> system-ui, sans-serif; &#125;</span></span><br><span class="line"><span class="language-css">    <span class="selector-tag">main</span> &#123; <span class="attribute">max-width</span>: <span class="number">700px</span>; <span class="attribute">margin</span>: <span class="number">8vh</span> auto; <span class="attribute">padding</span>: <span class="number">24px</span>; &#125;</span></span><br><span class="line"><span class="language-css">    <span class="selector-tag">h1</span> &#123; <span class="attribute">font-size</span>: <span class="built_in">clamp</span>(<span class="number">30px</span>, <span class="number">7vw</span>, <span class="number">48px</span>); &#125;</span></span><br><span class="line"><span class="language-css">    <span class="selector-tag">a</span> &#123; <span class="attribute">display</span>: inline-block; <span class="attribute">padding</span>: <span class="number">10px</span> <span class="number">18px</span>;</span></span><br><span class="line"><span class="language-css">      <span class="attribute">background</span>: <span class="number">#ffcf45</span>; <span class="attribute">color</span>: <span class="number">#292820</span>; <span class="attribute">border-radius</span>: <span class="number">12px</span>; &#125;</span></span><br><span class="line"><span class="language-css">  </span><span class="tag">&lt;/<span class="name">style</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">head</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">body</span>&gt;</span></span><br><span class="line">  <span class="tag">&lt;<span class="name">main</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">h1</span>&gt;</span>我的小网站，今天开站<span class="tag">&lt;/<span class="name">h1</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">p</span>&gt;</span>代码是复制的，域名是自己的，快乐是真的。<span class="tag">&lt;/<span class="name">p</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">a</span> <span class="attr">href</span>=<span class="string">&quot;https://example.com/&quot;</span>&gt;</span>去看看我的主页<span class="tag">&lt;/<span class="name">a</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">p</span>&gt;</span>个人练习页，不代表任何学校或机构。<span class="tag">&lt;/<span class="name">p</span>&gt;</span></span><br><span class="line">  <span class="tag">&lt;/<span class="name">main</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">body</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">html</span>&gt;</span></span><br></pre></td></tr></table></figure><p>上传前记得把演示按钮的 <code>https://example.com/</code> 换掉。别做完自己的主页，按钮却把朋友送进“Example Domain”：那是演示保留域名，奶蛙也不会替你写自我介绍。</p><h2 id="5-上传上线：用-Cloudflare-Pages-拖进去" class="lm-own-number">5. 上传上线：用 Cloudflare Pages 拖进去</h2><p>这一步使用 <strong>Cloudflare Pages 的 Direct Upload（直接上传）</strong>，把静态文件托管到网上。小型练习站可以从免费计划开始，额度与限制看<a href="https://developers.cloudflare.com/pages/platform/limits/">当前官方说明</a>；免费托管不代表域名永久免费。</p><ol><li>注册并验证自己的 <a href="https://dash.cloudflare.com/">Cloudflare 账号</a>。</li><li>打开 <strong>Workers &amp; Pages</strong>，创建应用，选择 <strong>Pages</strong> 的上传入口。它可能显示“Drag and drop”“Upload assets”或“拖放文件”，确认进入的是 Pages 的直接上传流程。</li><li>填一个英文项目名，例如 <code>my-first-site</code>；若被占用就加自己的网名。</li><li>拖入包含<strong>修改后的 <code>index.html</code></strong> 的网页文件夹。如果使用 ZIP，先把修改后的文件重新打包；原下载 ZIP 不会随解压文件的编辑而变化。<strong>打开 ZIP 或上传的文件夹，就应直接看到 <code>index.html</code></strong>，别多套一层 <code>网页模板/网页模板/</code>。</li><li>点击部署，记录平台给你的实际地址，例如 <code>你的项目名.pages.dev</code>。这里的示例项目名不保证可用，以你的后台为准。</li></ol><p>打开 <code>https://你的项目名.pages.dev</code>，再用手机试一次，点点按钮。能正常打开，你的小网站就上线了，接下来给它挂上自己的域名。<a href="https://developers.cloudflare.com/pages/get-started/direct-upload/">直接上传操作说明</a></p><p>这个模板直接上传就能用，不用装 Node.js。以后想改成 Git 自动发布，需要另建一个 Git 集成项目，Direct Upload 项目不能直接切换过去。</p><p>如果平台原生地址在你的网络打不开，先解决这个问题再绑域名。换一个门牌不能保证托管线路立刻变快；国内访问体验请用自己的网络和手机流量实测。</p><h2 id="6-绑定域名：让自己的门牌找到这个网页" class="lm-own-number">6. 绑定域名：让自己的门牌找到这个网页</h2><p>先选你希望别人输入的地址：</p><ul><li><code>example.com</code>：直接使用购买的根域名，也叫 apex；走 <strong>A 路线</strong>。</li><li><code>www.example.com</code> 或 <code>blog.example.com</code>：使用子域名；已有其他网站或邮箱、暂时不想迁移 DNS，可以走 <strong>B 路线</strong>。</li></ul><p>本文的注册商和托管商不同，这是正常的：注册商负责域名登记，Cloudflare Pages 负责网页，DNS 负责把两边连起来。</p><h3 id="A-路线：根域名交给-Cloudflare-DNS">A 路线：根域名交给 Cloudflare DNS</h3><p>Pages 绑定根域名，需要把该域名作为 Cloudflare zone 接入，并让域名的 nameserver 指向 Cloudflare。这是 Pages 根域名绑定的要求，不能拿“在原注册商填一条 CNAME”来替代。<a href="https://developers.cloudflare.com/pages/configuration/custom-domains/#add-a-custom-apex-domain">根域名绑定官方说明</a></p><p><strong>A1. 添加域名。</strong> 在 Cloudflare 选择添加／接入域名，填写自己买到的根域名，选择合适计划；本教程使用免费计划即可。检查扫描出的 DNS 记录。全新空域名通常简单；已经有网站或邮箱的，要保留原有记录，尤其邮箱使用的 MX 和相关 TXT。</p><p><strong>A2. 修改 nameserver（NS）。</strong> Cloudflare 会分配两条具体的 nameserver。把这两条完整复制下来，回到注册商的域名控制台，找到“DNS 服务器／修改 DNS 服务器／Nameserver”，替换成后台分配的值。这一步改的是<strong>负责回答整个域名 DNS 的服务器</strong>，不是添加一条普通 NS 记录，也不是过户域名。</p><p><strong>A3. 等待接入生效。</strong> 回到 Cloudflare，按提示检查，直到域名状态显示 Active／已激活。NS 修改和缓存刷新可能需要较长时间，别在两个后台反复改来改去。<a href="https://developers.cloudflare.com/dns/zone-setups/full-setup/setup/">Cloudflare DNS 接入步骤</a></p><p><strong>A4. 添加 Pages 自定义域名。</strong> 打开你的 Pages 项目 → <strong>Custom domains（自定义域名）</strong> → 添加自己买的根域名 → 按提示确认。Cloudflare 会处理所需 DNS 记录。等待自定义域名和证书状态正常后，打开 <code>https://你的域名</code>。</p><p>想同时支持 <code>www</code>，就在同一项目再添加 <code>www.你的域名</code>。根域名和 <code>www</code> 是两个主机名，<strong>绑定其中一个不会自动等于另一个也绑定了</strong>。</p><h3 id="B-路线：保留现有-DNS，用子域名">B 路线：保留现有 DNS，用子域名</h3><p>先在 Pages 项目的 <strong>Custom domains</strong> 中添加 <code>www.你的域名</code> 或 <code>blog.你的域名</code>，然后到当前生效的 DNS 服务商后台添加记录。例如：</p><table><thead><tr><th>类型</th><th>主机记录／名称</th><th>记录值／目标</th></tr></thead><tbody><tr><td><code>CNAME</code></td><td><code>www</code></td><td><code>你的实际项目名.pages.dev</code></td></tr></tbody></table><p>DNS 表里的主机记录有的填写 <code>www</code>，有的要求完整的 <code>www.你的域名</code>，依后台格式填写。记录值填<strong>主机名</strong>，不带 <code>https://</code>，不带 <code>/</code>，不带网页路径。</p><p>同一个 <code>www</code> 已经有 A、AAAA 或 CNAME 时，要先确认原记录用途再处理冲突。这个方法绑定的是 <code>www.你的域名</code>，裸的根域名不会因此自动打开。<a href="https://developers.cloudflare.com/pages/configuration/custom-domains/#add-a-custom-subdomain">子域名绑定官方说明</a></p><p><strong>务必先在 Pages 里添加自定义域名，再设置外部 CNAME。</strong> 只填一条解析、没有告诉 Pages 要接待这个主机名，可能出现 522。它需要的不是一句“你怎么不去抢”，而是完整的绑定步骤。</p><h3 id="DNS-表里的几种类型，看到不慌">DNS 表里的几种类型，看到不慌</h3><table><thead><tr><th>类型</th><th>用途</th><th>新手怎么理解</th></tr></thead><tbody><tr><td>A</td><td>主机名指向 IPv4 地址</td><td>托管商给你 IP 时按提示用</td></tr><tr><td>AAAA</td><td>主机名指向 IPv6 地址</td><td>与 A 类似，只是地址类型不同</td></tr><tr><td>CNAME</td><td>主机名指向另一主机名</td><td>Pages 子域名绑定常用它</td></tr><tr><td>TXT</td><td>文本记录</td><td>验证域名归属等用途，照平台提供的内容填</td></tr><tr><td>MX</td><td>邮件接收服务</td><td>用域名邮箱时会用到，迁移 DNS 时保留</td></tr></tbody></table><p><code>@</code> 通常代表根域名；TTL 是解析记录可以被缓存多久。<strong>DNS 解析不是网页跳转。</strong> CNAME 不会让浏览器自动显示另一个网址，也不能把记录值填成 <code>https://学校官网/某个路径</code>。<a href="https://developers.cloudflare.com/dns/manage-dns-records/reference/dns-record-types/">DNS 记录类型说明</a></p><h2 id="7-想复刻「奶龙大学」的效果？有三种做法" class="lm-own-number">7. 想复刻「奶龙大学」的效果？有三种做法</h2><h3 id="做自己的页面：最适合第一次练习">做自己的页面：最适合第一次练习</h3><p>前面的模板已经完成这件事：访问你的域名，显示你写的内容。按钮想去哪里，就把 <code>href</code> 改成那个完整 HTTPS 地址。作品集、博客入口、社团链接都可以放，按钮比自动跳转更容易让访客看清用途。</p><h3 id="页面显示几秒，再跳转">页面显示几秒，再跳转</h3><p>在自己 <code>index.html</code> 的 <code>&lt;head&gt;</code> 中添加一行：</p><figure class="highlight html"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">meta</span> <span class="attr">http-equiv</span>=<span class="string">&quot;refresh&quot;</span> <span class="attr">content</span>=<span class="string">&quot;5;url=https://example.com/&quot;</span>&gt;</span></span><br></pre></td></tr></table></figure><p>把地址换成你的真实目标，然后重新上传；页面正文写清目标，并保留一个能手动点击的链接。它是<strong>HTML 层面的页面跳转</strong>，和服务器直接返回 302 不同。苏州案例采用的就是这类配置。<a href="https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Elements/meta/http-equiv#refresh">MDN 对 meta refresh 的说明</a></p><h3 id="直接返回-HTTP-跳转">直接返回 HTTP 跳转</h3><p>Pages 的静态站点可以通过 <code>_redirects</code> 文件设置规则。这个文件<strong>没有扩展名</strong>，放在和 <code>index.html</code> 同一层；例如内容只有一行：</p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">/ https://example.com/ 302</span><br></pre></td></tr></table></figure><p>换成真实目标后，重新上传<strong>整个文件夹或包含两个文件的 ZIP</strong>，而不是只上传 <code>_redirects</code>。这条规则只匹配首页 <code>/</code>，浏览器访问首页后会去目标网址；其他路径不会被这行自动接管。<a href="https://developers.cloudflare.com/pages/configuration/redirects/">Pages 重定向规则文档</a></p><p>第一次玩梗先用 <strong>302（临时跳转）</strong>，方便之后换目标。明确长期不变再考虑 301（永久跳转），因为浏览器和其他客户端可能缓存它。<a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Status/302">HTTP 302</a>／<a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Status/301">HTTP 301</a></p><p>用学校或机构作为目标时，先确认准确官网地址，在页面或对外介绍中写明非官方性质。<strong>不能靠给目标官网建 CNAME 来复刻这种跳转</strong>：对方服务器和证书通常并没有配置你的域名。</p><h2 id="8-网站打不开？照这个顺序查，别上来就重买域名" class="lm-own-number">8. 网站打不开？照这个顺序查，别上来就重买域名</h2><p>先判断坏在哪一层：</p><p><strong>电脑里的 HTML → 平台提供的 pages.dev 地址 → 自定义域名 → HTTPS。</strong></p><table><thead><tr><th>现象</th><th>优先检查</th></tr></thead><tbody><tr><td>本地双击打开的是代码／没反应</td><td>文件是不是 <code>index.html.txt</code>；保存类型与 UTF-8 是否正确</td></tr><tr><td><code>pages.dev</code> 显示 404 或错误页面</td><td>上传根目录有没有 <code>index.html</code>，是否多套了一层目录，部署是否成功</td></tr><tr><td><code>pages.dev</code> 能打开，自定义域名不存在</td><td>注册和认证是否完成；NS 是否生效；记录是不是写在真正生效的 DNS 后台</td></tr><tr><td>自定义域名显示 522</td><td>Pages 的 Custom domains 里是否已添加该完整域名，再核对 CNAME</td></tr><tr><td>根域名能开，<code>www</code> 不能开，或反过来</td><td>两个主机名是否分别绑定；别把 <code>www</code> 当成自动赠送的跳转</td></tr><tr><td>域名状态正常，HTTPS 仍报错</td><td>自定义域名及证书是否仍在验证；有自定义 CAA 时检查是否允许签发</td></tr><tr><td>改 NS 后出现 SERVFAIL</td><td>如果原域名开启了 DNSSEC，按服务商迁移说明处理旧 DS 与新 DNSSEC 设置</td></tr><tr><td>网页还是旧内容</td><td>是否重新上传到同一项目的生产环境；用无痕窗口或手机流量比较缓存</td></tr><tr><td>中文地址在后台显示 <code>xn--…</code></td><td>先确认对应同一个 IDN，这是编码形式，不是域名乱码损坏</td></tr></tbody></table><p>DNSSEC 迁移和证书签发属于有具体配置才需要处理的事项，参照<a href="https://developers.cloudflare.com/dns/zone-setups/full-setup/setup/">接入指导</a>与<a href="https://developers.cloudflare.com/pages/configuration/custom-domains/#caa-records">自定义域名证书排障</a>。</p><p>想多确认一层，Windows 开始菜单搜“终端”，在 PowerShell 里运行这两条。把域名换成自己的，这不是必做步骤：</p><figure class="highlight powershell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">nslookup www.example.com</span><br><span class="line">curl.exe <span class="literal">-I</span> https://www.example.com/</span><br></pre></td></tr></table></figure><p>前者看有没有 DNS 应答，后者查看 HTTP 状态与响应头。<code>200</code> 表示请求成功；做了跳转则通常会看到 <code>301</code> 或 <code>302</code> 和 <code>Location</code> 目标。</p><h2 id="9-实名和备案，别一股脑全叫“认证”" class="lm-own-number">9. 实名和备案，别一股脑全叫“认证”</h2><p><strong>域名实名认证</strong>回答“谁持有这个域名”，是注册商的域名管理流程。</p><p><strong>ICP备案</strong>则与中国内地接入的网站服务有关。使用中国内地服务器等相关资源对外提供网站服务，需要按接入商要求完成备案；一般非中国内地节点不通过这套内地服务器流程申请 ICP 备案。具体部署地域、CDN 与业务要求应向接入商确认，不能仅凭域名后缀或买域名的位置判断。<a href="https://help.aliyun.com/zh/icp-filing/basic-icp-service/user-guide/icp-filing-application-overview">阿里云备案流程</a>、<a href="https://help.aliyun.com/zh/icp-filing/basic-icp-service/user-guide/icp-filing-server-access-information-check">服务器与接入检查</a></p><p>论坛里的“萌备案”是社区玩法，<strong>不是工信部 ICP 备案的替代品</strong>。萌可以萌，概念别混。</p><h2 id="10-下课前，确认你真的拥有一个可用网站" class="lm-own-number">10. 下课前，确认你真的拥有一个可用网站</h2><p>做完以后，用这份清单检查：</p><ul><li>[ ] 域名已在自己的注册商账号下，持有人、认证状态和到期日正确。</li><li>[ ] <code>index.html</code> 的标题、内容和按钮已换成自己的，按钮没有遗留 <code>example.com</code>。</li><li>[ ] Pages 的原生地址能打开正确页面。</li><li>[ ] 自定义域名状态正常，<code>https://</code> 可以访问，没有证书错误。</li><li>[ ] 需要的根域名和 <code>www</code> 分别检查过。</li><li>[ ] 手机 Wi-Fi 和流量都试过，按钮可用，页面没有挤出屏幕。</li><li>[ ] 修改内容后知道如何重新上传，电脑里保留了网页源文件。</li><li>[ ] 想长期保留的域名安排了续费和账号保护。</li></ul><p>以后改内容：在电脑里编辑 <code>index.html</code> → 本地刷新检查 → 打开同一个 Pages 项目创建新部署 → 选择生产环境 → 上传完整文件 → 检查域名页面。项目和域名保持原样，一般不需要每次重设 DNS。</p><p>网站搭好以后，慢慢把自己的介绍、作品和喜欢的链接放进去就行。今天先整点奶蛙，哪天有了新想法，再回来改这一页。</p><img src="/images/domain-beginner/naiwa-launch.webp" alt="恶搞奶蛙竖起大拇指：网站上线了，现在我也是有站之人了" width="640" loading="lazy" style="display:block;margin:24px auto;max-width:100%;height:auto"><p>奶蛙：你现在也算是有站之人了。至于有人访问没——先把链接发给朋友，别让网站和你的早八出勤率一样安静。(¬‿¬)</p><p>想继续做可长期更新的博客，可以接着看本站的<a href="/posts/3d61fa8b.html">《Hexo 博客 GitHub + Vercel 部署全流程》</a>。先把这一页跑通，再升级装备，互联网小窝就从这里开始。</p><p><em>配图：参考奶蛙表情包，用 AI 生成。</em></p>]]>
    </content>
    <id>https://blog.luomo.moe/posts/7a1d0a11.html</id>
    <link href="https://blog.luomo.moe/posts/7a1d0a11.html"/>
    <published>2026-10-14T04:00:00.000Z</published>
    <summary>从上海、苏州、甘肃奶龙大学的真实案例出发，手把手买域名、上传网页、设置 DNS、绑定 HTTPS，附可下载模板、跳转示例和排障清单。奶蛙负责整活，你负责拥有自己的互联网门牌。</summary>
    <title>从「奶龙大学.com」到自己的小网站：零基础域名购买与建站教程</title>
    <updated>2026-10-14T12:40:09.000Z</updated>
  </entry>
  <entry>
    <author>
      <name>洛墨-天染-依然</name>
    </author>
    <category term="产品介绍" scheme="https://blog.luomo.moe/categories/%E4%BA%A7%E5%93%81%E4%BB%8B%E7%BB%8D/"/>
    <category term="Misskey" scheme="https://blog.luomo.moe/tags/Misskey/"/>
    <category term="Fediverse" scheme="https://blog.luomo.moe/tags/Fediverse/"/>
    <category term="ActivityPub" scheme="https://blog.luomo.moe/tags/ActivityPub/"/>
    <category term="社区" scheme="https://blog.luomo.moe/tags/%E7%A4%BE%E5%8C%BA/"/>
    <content>
      <![CDATA[<link rel="stylesheet" class="aplayer-secondary-style-marker" href="/assets/css/APlayer.min.css"><script src="/assets/js/APlayer.min.js" class="aplayer-secondary-script-marker"></script><script class="meting-secondary-script-marker" src="/assets/js/Meting.min.js"></script><link rel="stylesheet" type="text&#x2F;css" href="/pluginsSrc/hexo-tag-hint/dist/hexo-tag-hint.min.css"><p>写博客的时候，我总会想把一个话题讲完整。但更多时候，值得记下来的只是一张照片、一段刚跑通的代码、一首突然想分享的歌，或者一句「今天也辛苦了」。</p><p>这些小小的片段，可以在 <strong>三月第七站</strong> 找到位置。它位于 <a href="https://misskey.march7th.cn/">misskey.march7th.cn</a>，是一个基于开源软件 <strong>Misskey</strong> 的联邦社交社区：在这里记录日常、分享图片与灵感、回应朋友，也能沿着联邦网络认识其他站点的人。</p><a class="btn-beautify blue larger" href="https://misskey.march7th.cn/" title="前往三月第七站"><i class="fas fa-train"></i><span>前往三月第七站</span></a><a class="btn-beautify green larger" href="https://misskey-hub.net/cn/" title="认识 Misskey"><i class="fas fa-book"></i><span>认识 Misskey</span></a><blockquote><p><strong>加入前先看这里：</strong> 截至 2026 年 10 月 10 日，三月第七站采用 <strong>邀请注册</strong>，首页提示需要邀请码。你可以先浏览公开内容；想在本站创建账号，请向站点管理员咨询邀请码，是否发放以实际运营安排为准。已有账号可以直接登录。</p></blockquote><span id="more"></span><h2 id="一座可以聊天的站台">一座可以聊天的站台</h2><p>如果把 <a href="https://luomo.moe/">Luomo Home</a> 想成一条夜行线，三月第七站就是其中供旅客停下来聊聊的一站。</p><p>博客负责把想法整理成文章；社区则留住文章之间的日常。这里可以分享服务器折腾的进展，贴一张旅途中拍到的天空，聊聊喜欢的作品，或者留下一条只有几行的近况。内容不必先攒够一篇长文，交流也可以从一个小小的表情开始。</p><p><img src="/img/march7th-misskey-t001/welcome-panel.webp" alt="三月第七站公开欢迎面板，显示邀请注册入口"></p><p><em>上图为 2026 年 10 月 10 日公开欢迎面板的实际截图。页面与注册规则可能随运营调整。</em></p><h2 id="在这里可以做什么">在这里可以做什么</h2><h3 id="用帖子收集日常">用帖子收集日常</h3><p>Misskey 把发布的内容称为 <strong>Note（帖子）</strong>。三月第七站当前公开配置的帖子文字上限为 <strong>3,000 字符</strong>，既能容纳一句随手记录，也能写下一段完整的想法。</p><p>帖子可以搭配图片等附件，加入话题标签，也可以用投票向大家征集意见。看到感兴趣的内容，可以回复、引用或转发，让话题继续长下去。操作说明可参考 <a href="https://misskey-hub.net/cn/docs/for-users/features/note/">Misskey 官方帖子指南</a>。</p><h3 id="用表情回应朋友">用表情回应朋友</h3><p>在 Misskey，回应不止一个点赞按钮。你可以挑选一个表情来表示「好耶」「抱抱」或者「我也这么想」，也可以使用站点提供的自定义表情。</p><p>这让短短的一条动态也有来有回。跨站交互时，不同软件对表情回应的支持会有差异，有些回应会被转换为点赞；具体行为见 <a href="https://misskey-hub.net/cn/docs/for-users/features/reaction/">Misskey 官方回应说明</a>。</p><h3 id="为内容选择合适的公开范围">为内容选择合适的公开范围</h3><p>发帖前，可以选择公开、首页、关注者或指定用户等可见范围；长内容和剧透还可以用 <strong>CW（内容警告）</strong> 折叠，让别人决定是否展开。</p><p>其中「首页」仍然是公开内容，只是减少在非关注者时间线里的展示。可见范围是在决定内容发给谁，不等于端到端加密；涉及密码、个人证件或其他敏感资料时，仍应使用合适的私密渠道。各范围的区别见 <a href="https://misskey-hub.net/cn/docs/for-users/features/note/#visibility">官方可见性说明</a>。</p><h2 id="联邦宇宙，究竟是什么意思">联邦宇宙，究竟是什么意思</h2><p><strong>Misskey 是软件，三月第七站是运行这套软件的一个具体站点。</strong> 类似于同一套博客软件可以搭出许多不同的网站，每个 Misskey 站点也有自己的域名、管理员、规则和社区氛围。</p><p>这些站点可以通过开放协议 <strong>ActivityPub</strong> 相互连接，组成通常叫作 <strong>Fediverse（联邦宇宙）</strong> 的社交网络。用户可以在自己的站点里关注远程账号，与其他 Misskey 站点，以及 Mastodon 等兼容软件上的用户交流。跨站互通会受双方运营规则、屏蔽设置和功能兼容性影响。更多背景见 <a href="https://misskey-hub.net/cn/docs/for-users/onboarding/get-started/">Misskey 官方服务器入门</a>。</p><p>远程账号通常写成下面这样：</p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">@用户名@站点域名</span><br></pre></td></tr></table></figure><p>三月第七站当前公开配置启用了联邦通信；欢迎页也能看到来自其他站点的转发内容。因此，这里既有自己的小社区，也有通往更大网络的窗口。联邦模式的范围与限制由管理员管理，参见 <a href="https://misskey-hub.net/cn/docs/for-admin/features/federation/">官方联邦模式说明</a>。</p><h2 id="第一次到站，可以这样开始">第一次到站，可以这样开始</h2><ol><li><strong>先看看氛围。</strong> 打开 <a href="https://misskey.march7th.cn/">三月第七站</a>，浏览欢迎页中的公开时间线。先熟悉内容与社区规则，再决定是否加入。</li><li><strong>确认加入方式。</strong> 目前本站注册需要邀请码；取得邀请码后，使用首页「在本服务器上注册」入口，按页面要求创建账号。不要把邀请码、密码或授权令牌贴进公开帖子。</li><li><strong>给自己一个容易认出的名字。</strong> 登录后设置显示名、头像与简介，再发一条简单的自我介绍，告诉大家你喜欢什么、最近在折腾什么。</li><li><strong>从一次回应开始交流。</strong> 关注感兴趣的人，给喜欢的帖子加个表情；想把话题聊下去时，再回复或引用。关注其他站点的账号时，可以使用完整的联邦账号地址。</li></ol><p>如果你已经有其他兼容联邦站点的账号，也可以先从原站点关注三月第七站的用户；跨站关注不需要再在每个站点注册一遍，但也不会自动变成本站账号。</p><h2 id="与博客和主页一起，留住旅途">与博客和主页一起，留住旅途</h2><p>我把三月第七站接到了 <a href="https://luomo.moe/#services">Luomo Home 的星轨航图</a>，作为第五个服务入口。线路图、发车信息板和站点介绍都会指向这个社区；想了解它时，可以读这篇文章，想看看大家正在聊什么时，就直接到站逛逛。</p><p>博客里留下认真整理过的经验，社区里留下那些正在发生的生活。愿这里有你想分享的小事，也有人愿意接住你的那一句话。</p><a class="btn-beautify blue larger" href="https://misskey.march7th.cn/" title="下一站，三月第七站"><i class="fas fa-train"></i><span>下一站，三月第七站</span></a>]]>
    </content>
    <id>https://blog.luomo.moe/posts/7a032026.html</id>
    <link href="https://blog.luomo.moe/posts/7a032026.html"/>
    <published>2026-10-09T16:00:00.000Z</published>
    <summary>认识三月第七站：一个基于 Misskey 的联邦社交社区，用帖子记录日常、分享图片与灵感、用表情回应朋友，并连接其他联邦站点。附当前邀请注册方式与新人上手路线。</summary>
    <title>三月第七站：把日常、灵感与朋友，连接到联邦宇宙</title>
    <updated>2026-10-09T16:00:00.000Z</updated>
  </entry>
  <entry>
    <author>
      <name>洛墨-天染-依然</name>
    </author>
    <category term="技术实践" scheme="https://blog.luomo.moe/categories/%E6%8A%80%E6%9C%AF%E5%AE%9E%E8%B7%B5/"/>
    <category term="VPN" scheme="https://blog.luomo.moe/tags/VPN/"/>
    <category term="FlClash" scheme="https://blog.luomo.moe/tags/FlClash/"/>
    <category term="Windows" scheme="https://blog.luomo.moe/tags/Windows/"/>
    <category term="Android" scheme="https://blog.luomo.moe/tags/Android/"/>
    <content>
      <![CDATA[<link rel="stylesheet" class="aplayer-secondary-style-marker" href="/assets/css/APlayer.min.css"><script src="/assets/js/APlayer.min.js" class="aplayer-secondary-script-marker"></script><script class="meting-secondary-script-marker" src="/assets/js/Meting.min.js"></script><link rel="stylesheet" type="text&#x2F;css" href="/pluginsSrc/hexo-tag-hint/dist/hexo-tag-hint.min.css"><p>Windows 电脑和安卓手机都使用 <strong>FlClash</strong>。先安装客户端，再导入管理员单独发给你的个人 Mihomo 订阅；安装软件本身不会自动开通服务。</p><p>按设备开始：<a href="#windows-install">Windows 安装</a> · <a href="#android-install">安卓安装</a> · <a href="#sync">同步与日常维护</a> · <a href="#legacy">依赖旧代理时的迁移</a>。</p><p>需要保存或转发教程时，可以下载：</p><ul><li><a href="/downloads/luomo-vpn-guide-t001/Luomo-VPN-FlClash-Guide-t001.pdf">PDF 图文教程</a></li><li><a href="/downloads/luomo-vpn-guide-t001/Luomo-VPN-FlClash-Guide-t001.html">单文件离线 HTML</a></li><li><a href="/downloads/luomo-vpn-guide-t001/Luomo-VPN-FlClash-Guide-Package-t001.zip">完整教程包（ZIP）</a></li></ul><blockquote><p>菜单按 FlClash <strong>v0.8.99</strong> 核对。操作图均为示意；官方界面示例注明版本，不是你的设备截图。安卓流程未完成手机实机测试。订阅链接、二维码和备份可能含个人凭据，请和通用教程分开保管。</p></blockquote><span id="more"></span><p><a id="start"></a></p><h2 id="第一次使用：按这个顺序">第一次使用：按这个顺序</h2><p>按“安装 FlClash → 导入自己的订阅 → 选择规则和节点 → 启动并验证”完成第一次连接。软件只需安装一次；日常主要使用启动、选择节点、停止和同步。</p><p><img src="/images/tutorials/flclash-vpn-t001/01-flow.svg" alt="操作示意 · 每一步完成后再进入下一步"></p><p>操作示意 · 每一步完成后再进入下一步。</p><ul><li>Windows：从<a href="#windows-install">下载安装</a>开始，依次完成订阅导入、选择节点、启动与日常使用。</li><li>安卓：从<a href="#android-install">下载安装</a>开始，依次完成订阅导入、允许 VPN、连接与后台设置。</li><li>两端维护：<a href="#sync">同步订阅</a>、<a href="#replace-url">换发链接</a>、<a href="#software-update">升级软件</a>、<a href="#backup">备份与恢复</a>。</li></ul><blockquote><p>需要准备：设备、能正常联网的网络、适配的 FlClash 安装文件，以及管理员发给你的<strong>个人 Mihomo 订阅链接</strong>。安装客户端不会自动开通服务。</p></blockquote><p><a id="prepare"></a></p><h2 id="开始前：准备文件，认清界面">开始前：准备文件，认清界面</h2><p>首次使用只需要 FlClash 与个人订阅，无需进入服务管理后台</p><table><thead><tr><th>准备项目</th><th>Windows</th><th>Android 安卓</th></tr></thead><tbody><tr><td>官方安装文件</td><td><code>windows-amd64-setup.exe</code>；ARM 电脑另选 arm64</td><td><code>android-arm64-v8a.apk</code>；其他架构另行确认</td></tr><tr><td>个人订阅</td><td>向管理员领取 Mihomo / Clash Verge（完整）格式</td><td>与 Windows 使用同类格式；设备限制按服务约定</td></tr><tr><td>开始前</td><td>下载文件并保留原配置；切换连接时避免代理冲突</td><td>下载文件并保留原配置；切换前停止其他 VPN</td></tr><tr><td>首次成功标准</td><td>已选配置 + 规则模式 + 有效节点 + 实际网页可用</td><td>已选配置 + 规则模式 + VPN 权限 + 实际网页可用</td></tr></tbody></table><p><img src="/images/tutorials/flclash-vpn-t001/official-preview-v0.8.99.webp" alt="官方 v0.8.99 标签下的 preview.png：桌面示例为 macOS，手机是布局展示；用于认识 FlClash，不是本次 Windows 或手机实拍。"></p><p>官方 v0.8.99 标签下的 preview.png：桌面示例为 macOS，手机是布局展示；用于认识 FlClash，不是本次 Windows 或手机实拍。</p><blockquote><p>主要入口：<strong>仪表盘</strong>看模式和运行状态；<strong>代理</strong>选节点；<strong>配置</strong>管理订阅；<strong>工具</strong>找更新与备份。图标、入口位置随版本和窗口宽度变化，优先按文字寻找。</p></blockquote><p>新安装统一按正文使用 FlClash。依赖旧代理联网时，先记下原系统代理地址、下载离线材料与备份并准备恢复入口；旧内核保持运行。新 FlClash 使用不同且未占用的混合端口，单独验证后再切系统代理，失败恢复原地址，详见<a href="#legacy">保留旧连接的迁移步骤</a>。首次打开新客户端也可能改变代理设置，不能保证无缝迁移。</p><p><a id="subscription"></a></p><h2 id="领取订阅：复制哪一条链接">领取订阅：复制哪一条链接</h2><p>链接提供你的节点和规则；FlClash 负责使用这份配置</p><ol><li>向管理员说明你使用 <strong>FlClash（Windows / Android）</strong>，领取属于你的<strong>完整 HTTPS 个人订阅链接</strong>。选择 <strong>Mihomo / Clash Verge（完整）</strong> 格式。</li><li>使用管理员“按客户端复制订阅”生成的链接。不要复制管理后台、登录页、分享网页地址，也不要把其他客户端的节点文本直接填入 URL。</li><li>复制整条链接。粘贴后检查 <code>https://</code> 开头、结尾参数是否齐全；避免空格、换行、引号、句号与聊天软件折叠后遗漏的字符。</li><li>管理员提示“同步中”时，等其确认签发完成。链接过期、撤销、限额或设备限制需要管理员确认；重新安装软件不会自动恢复服务。</li></ol><table><thead><tr><th>格式</th><th>本教程怎么处理</th></tr></thead><tbody><tr><td>默认 Mihomo YAML / <code>format=mihomo</code></td><td>FlClash 使用这类完整格式，直接粘贴管理员提供的链接</td></tr><tr><td><code>format=clash</code></td><td>原版旧内核 CFW 的兼容格式；不作为本教程 FlClash 首选</td></tr><tr><td>v2rayN / v2rayNG / sing-box 等</td><td>各有对应内容格式；需要管理员重新复制 FlClash 适配链接</td></tr></tbody></table><blockquote><p><strong>不可用占位示例，请勿照抄：</strong><br><code>https://example.invalid/个人订阅占位?format=mihomo</code><br>不需要自己拼接参数或修改授权字段。</p></blockquote><p>订阅链接、个人配置文件、二维码和备份都可能含凭据。通用教程可以转发，个人材料请单独保管。无需给普通新人分配管理后台账号。</p><p><a id="windows-install"></a></p><h2 id="Windows：下载并安装-FlClash">Windows：下载并安装 FlClash</h2><p>普通 Intel / AMD 64 位电脑选 amd64 安装版</p><p><img src="/images/tutorials/flclash-vpn-t001/02-win-install.svg" alt="Windows 安装操作示意 · 在官方 Assets 中选择对应 setup.exe"></p><p>Windows 安装操作示意 · 在官方 Assets 中选择对应 setup.exe</p><blockquote><p><strong>当前依赖旧代理联网：</strong> 先按<a href="#legacy">保留旧连接的迁移步骤</a>下载离线文件、备份，记下 <code>127.0.0.1:原端口</code> 并准备恢复入口，保留旧内核。首次打开 FlClash 也可能改变系统代理；会恢复原设置后再迁移，新客户端须用不同未占用端口独立验证。</p></blockquote><ol><li>先打开 <a href="https://github.com/chen08209/FlClash/releases/latest">FlClash 官方最新发布页</a>，展开 <strong>Assets</strong>，下载并保留原配置。当前联网不依赖旧代理时，在安装前停止并退出其他代理；网络打不开时向管理员领取官方原始文件。</li><li>普通 Intel / AMD 电脑选 <code>FlClash-版本号-windows-amd64-setup.exe</code>。ARM 电脑选 <code>windows-arm64-setup.exe</code>；不会判断时看“设置 → 系统 → 系统信息 / 关于 → 系统类型”。</li><li>双击安装器，核对文件名和来源，按向导确认安装位置、快捷方式并安装。官方 Windows setup 安装器需要管理员权限；系统弹出权限确认时，核对是否为刚运行的可信安装器，再允许。不确定的文件先交管理员确认。</li><li>完成后打开 FlClash。首次出现“免责声明”等说明时，先阅读并接受后再继续。Windows v0.8.99 的语言入口是<strong>工具 / Tools → 语言 / Language → 中文简体</strong>；不同版本布局可能变化。</li><li>进入主界面后继续<a href="#windows-import">导入自己的订阅</a>。没有配置和节点是正常的首次状态，安装完成并不等于连接已就绪。</li></ol><blockquote><p>初学者选 <strong>setup.exe</strong>；ZIP 为便携版，Source code 为源码。本次核对 <strong>v0.8.99</strong>（2026-10-03）。依赖旧代理的迁移按<a href="#legacy">保留旧连接的迁移步骤</a>操作；不会恢复原连接时，请管理员协助。</p></blockquote><p><a id="windows-import"></a></p><h2 id="Windows：导入并选中个人订阅">Windows：导入并选中个人订阅</h2><p>操作顺序：配置 → ＋ → URL → 提交</p><p><img src="/images/tutorials/flclash-vpn-t001/08-desktop-import.svg" alt="桌面导入操作示意 · URL 框只粘贴自己的完整链接"></p><p>桌面导入操作示意 · URL 框只粘贴自己的完整链接</p><ol><li>在聊天或私人文件中复制自己的完整 <strong>Mihomo 订阅链接</strong>。返回 FlClash，进入左侧<strong>配置</strong>；窄窗口中的导航位置可能不同。</li><li>点击添加按钮 <strong>＋</strong>，在添加方式中选择 <strong>URL</strong>。在“从 URL 导入”窗口粘贴链接；如果版本提供名称字段，可填 <code>Luomo</code>，否则用下载后显示的名称即可。</li><li>核对链接完整，点击<strong>提交</strong>，等待获取完成。出现错误先记录文字；不要不断点击提交、创建多条重复配置。</li><li>回到配置列表，点击刚导入的配置，让它成为<strong>当前选中的配置</strong>。按版本提供的高亮、选中标记或仪表盘配置名称确认。</li><li>进入<strong>代理</strong>，确认能看到代理组 / 节点，再继续<a href="#windows-nodes">规则模式和节点选择</a>。导入成功但没选中，仍可能使用旧配置或无法启动。</li></ol><blockquote><p>成功标志：列表有配置、当前配置已选中、代理页有节点 / 代理组。出现 403、404、超时、解析错误时，按<a href="#windows-help">Windows 排障</a>处理并向管理员确认链接状态。</p></blockquote><p><strong>用文件或二维码导入：</strong> 只使用管理员明确发给你的个人文件 / 二维码。文件可能是一次性快照，没有订阅 URL 就不能像远程订阅一样同步；新人优先使用 URL。</p><p><a id="windows-nodes"></a></p><h2 id="Windows：选择规则模式和节点">Windows：选择规则模式和节点</h2><p>先设置分流方式，再选择代理组中的可用线路</p><p><img src="/images/tutorials/flclash-vpn-t001/06-panel.svg" alt="模式与代理组操作示意 · 组名与节点名由实际订阅决定"></p><p>模式与代理组操作示意 · 组名与节点名由实际订阅决定</p><ol><li>进入<strong>仪表盘</strong>，找到<strong>出站模式</strong>，选择<strong>规则 / Rule</strong>。它按订阅中的规则决定直连或走代理，是日常默认选择。</li><li>进入<strong>代理</strong>。先找到承担日常出站的主要代理组，再在组内选择<strong>自动选择 / 可用公网节点</strong>。有的组会指向另一个组；沿着实际组名查看最终使用的线路。</li><li>可点击延迟测试 / 刷新图标辅助判断节点；测试期间等它完成。无结果、超时的节点先换一个试。数字小只表示测试请求延迟，不能保证下载快或所有网站可用。</li><li>如果列表有 <strong>Public / CF / AUTO-PUBLIC</strong> 等名称，按管理员说明选择。普通设备先不要选标明 <strong>ZeroTier / 私网</strong> 的线路，它们可能需要额外内部网络条件。</li><li>记住当前可用的组与节点。节点切换后打开实际网站验证；已有长连接可能仍走原节点，必要时关闭并重开目标应用。</li></ol><blockquote><p>节点名不是永久固定标准。规则模式下的直连流量可能不经过你选的节点，这是配置的正常分流；一个应用是否被接管，还取决于系统代理 / 虚拟网卡等入口。</p></blockquote><p>不要为“全都走代理”随意切到全局；全局也需要给全局代理组选择有效节点，并且不会自动接管所有不读取系统代理的应用。<a href="#concepts">出站模式与流量入口</a>解释这些区别。</p><p><a id="windows-connect"></a></p><h2 id="Windows：启动、验证和停止">Windows：启动、验证和停止</h2><p>系统代理是普通浏览器使用的入门方式</p><p><img src="/images/tutorials/flclash-vpn-t001/05-shortcuts.svg" alt="FlClash 日常入口操作示意 · 运行按钮图标 / 文字随版本变化"></p><p>FlClash 日常入口操作示意 · 运行按钮图标 / 文字随版本变化</p><blockquote><p>首次迁移且依赖旧代理联网时，先按<a href="#legacy">保留旧连接的迁移步骤</a>设置不同未占用端口，保留旧内核并独立测试新连接；不要直接按下面的日常步骤抢占系统代理。</p></blockquote><ol><li>确认已选自己的配置、规则模式和可用节点。回到<strong>仪表盘</strong>，在快捷选项中打开<strong>系统代理</strong>；先保留虚拟网卡 / TUN 的默认关闭状态。</li><li>点击<strong>启动 / 播放运行按钮</strong>，等待完成。界面出现运行计时、流量或暂停图标，只说明客户端正在运行。</li><li>先用浏览器打开一个普通网页，再打开你实际需要的网站。若后者失败，换另一条公网节点后重试；同时确认系统代理仍开启。</li><li>不用时，点击<strong>同一个运行按钮暂停 / 停止</strong>。确认运行计时结束，并检查<strong>系统代理已关闭</strong>；如果仍开启，手动关掉。</li><li>需要完全退出时，检查系统托盘，使用 FlClash 的退出入口。关闭主窗口可能只是缩到托盘；不能仅凭窗口消失判断连接已停止。</li></ol><blockquote><p><strong>需要虚拟网卡 / TUN 时：</strong> 它用于更广泛的流量接管，现行 Windows 文字说明要求管理员模式。仅当目标应用确实不使用系统代理、且管理员指导需要时，再按软件要求启用并验证。</p></blockquote><p>停止后普通网页仍打不开，先确认没有其他客户端运行，再检查 Windows“设置 → 网络和 Internet → 代理”是否残留属于已停止客户端的手动代理。企业网络的代理配置先请其管理员处理。</p><p><a id="windows-daily"></a></p><h2 id="Windows：以后每天这样用">Windows：以后每天这样用</h2><p>已经导入过的有效订阅无需每天重新添加</p><h3 id="每天的-4-个动作">每天的 4 个动作</h3><ol><li>打开 FlClash，确认当前个人配置。</li><li>检查规则模式与常用公网 / 自动节点。</li><li>打开系统代理并启动，实际检查目标网页。</li><li>用完暂停 / 停止，确认系统代理关闭；需要时从托盘退出。</li></ol><h3 id="开机后没连上，先看两个不同选项">开机后没连上，先看两个不同选项</h3><table><thead><tr><th>选项</th><th>含义</th><th>建议</th></tr></thead><tbody><tr><td>自启动</td><td>跟随系统打开 FlClash</td><td>熟悉手动使用后，确有需要再开启</td></tr><tr><td>自动运行</td><td>应用打开后自动运行连接</td><td>不等于自启动；仍需有效配置和网络</td></tr><tr><td>最小化到托盘 / 关闭行为</td><td>决定关窗口是退出还是后台保留</td><td>以运行状态、系统代理与托盘为准</td></tr></tbody></table><p>这些选项在工具的“启动与后台”等设置中，路径可能随版本调整。先确认手动流程稳定，再改一项并在下次启动时验证。机器重启、休眠恢复或网络切换后，也要检查状态。</p><h3 id="新增节点、链接换发与升级">新增节点、链接换发与升级</h3><p>管理员通知节点变化：<a href="#sync">同步订阅</a>同步。换发个人链接：<a href="#replace-url">替换个人 URL</a>替换 URL。升级软件：先<a href="#backup">备份与恢复</a>备份，再按<a href="#software-update">升级 FlClash</a>操作。服务到期或限制：联系管理员确认。</p><blockquote><p>第一次成功后保留稳定设置。网络、DNS、覆写和虚拟网卡等高级选项无需为了日常连接全部开启；遇到问题按<a href="#windows-help">Windows 排障</a>逐项排查。</p></blockquote><p><a id="android-install"></a></p><h2 id="Android：下载并安装-FlClash">Android：下载并安装 FlClash</h2><p>先选兼容的 APK，再只给当前安装来源所需权限</p><p><img src="/images/tutorials/flclash-vpn-t001/07-download.svg" alt="官方下载文件选择操作示意 · Android 多数新手机选 arm64-v8a.apk"></p><p>官方下载文件选择操作示意 · Android 多数新手机选 arm64-v8a.apk</p><ol><li>用手机浏览器打开 <a href="https://github.com/chen08209/FlClash/releases/latest">FlClash 官方最新发布页</a>，展开 <strong>Assets</strong>。多数近年手机选 <code>FlClash-版本号-android-arm64-v8a.apk</code>；不是 Windows 安装器。</li><li>旧 32 位手机可能需要 <code>armeabi-v7a.apk</code>，部分模拟器需要 <code>x86_64.apk</code>。不确定时向管理员提供手机型号、Android 版本，先确认架构与兼容性。</li><li>下载后打开 APK。系统提示允许“此来源安装应用”时，只为当前浏览器 / 文件管理器临时允许，然后返回安装。安装完成后可关闭这项授权；不同品牌的路径和文字不同。</li><li>点“打开”，处理首次显示的说明后进入 FlClash。需要中文时在工具 / 视图的语言选项中选择简体中文，位置以该版本为准。</li><li>先停止其他 VPN，保留默认网络与 DNS 设置，继续<a href="#android-import">导入自己的订阅</a>。普通安装无需 root，也无需另装根证书。</li></ol><blockquote><p>本次核对官方版本为 <strong>v0.8.99</strong>。教程中手机官方界面帧来自 <strong>v0.8.94</strong>，只帮助认识布局；不是当前手机实拍。已安装旧版时按<a href="#software-update">升级 FlClash</a>先备份再覆盖升级，不要直接卸载。</p></blockquote><p><a id="android-import"></a></p><h2 id="Android：导入个人订阅">Android：导入个人订阅</h2><p>操作顺序同电脑：配置 → ＋ → URL → 提交</p><p><img src="/images/tutorials/flclash-vpn-t001/09-mobile-import.svg" alt="手机导入操作示意 · 添加按钮位置随布局变化，提交使用真实个人 URL"></p><p>手机导入操作示意 · 添加按钮位置随布局变化，提交使用真实个人 URL</p><ol><li>在聊天中复制自己的<strong>完整 Mihomo 订阅链接</strong>。返回 FlClash，点击底部<strong>配置</strong>。</li><li>点击添加按钮 <strong>＋</strong>，再选 <strong>URL</strong>（通过 URL 获取配置）。按钮可能位于页角或浮动操作区，以实际界面为准。</li><li>粘贴链接。出现名称字段时可填 <code>Luomo</code>；没有该字段时不用额外寻找。核对 URL 后点击<strong>提交</strong>，等待获取完成。</li><li>回到配置列表，点击新配置使它被选中。核对高亮 / 选中标记，或仪表盘里的当前配置名称。</li><li>进入<strong>代理</strong>，确认存在节点 / 代理组。继续<a href="#android-connect">安卓规则与 VPN 连接</a>选择规则与节点，再启动 VPN。</li></ol><blockquote><p>导入成功只完成配置步骤，VPN 尚未自动获得访问权限。出现 404、超时或解析错误时先看<a href="#android-help">安卓排障</a>，不要反复新增同一 URL。</p></blockquote><h3 id="只有个人二维码-文件怎么办">只有个人二维码 / 文件怎么办</h3><p>用配置页提供的二维码 / 文件方式导入管理员明确发给你的材料。同一手机不方便扫描屏幕上的二维码时，优先请管理员给复制 URL。一次性文件不一定包含可同步的订阅地址。不要复用别人的二维码或历史共享链接。</p><p><a id="android-connect"></a></p><h2 id="Android：规则、节点和-VPN-连接">Android：规则、节点和 VPN 连接</h2><p>首次启动需系统允许 FlClash 建立 VPN</p><p><img src="/images/tutorials/flclash-vpn-t001/10-mobile-connect.svg" alt="手机启动与权限操作示意 · 系统弹窗以手机实际文字为准"></p><p>手机启动与权限操作示意 · 系统弹窗以手机实际文字为准</p><ol><li>进入<strong>仪表盘</strong>，将<strong>出站模式</strong>设为<strong>规则</strong>。进入<strong>代理</strong>，在主要代理组中选择自动 / 可用公网节点。普通设备不直接选择 ZeroTier / 私网节点。</li><li>回到仪表盘，点击<strong>启动 / 播放运行按钮</strong>。首次弹出“连接请求 / VPN 连接”时，确认请求来自 <strong>FlClash</strong>，点击<strong>确定 / 允许</strong>。</li><li>等待完成。运行计时、暂停图标、系统 VPN / 钥匙标识等用于观察状态；不同手机可能只显示其中一部分。</li><li>用浏览器打开一个普通网页，再打开实际目标网页。失败时换另一个公网节点；必要时停止再启动、关闭并重开目标应用。</li><li>不用时点击<strong>同一个运行按钮暂停 / 停止</strong>，确认运行状态结束。返回桌面、关掉教程或切到其他应用，不会自动停止 VPN。</li></ol><blockquote><p>系统通常只允许一个主要 VPN 通道。其他 VPN、过滤器或企业网络软件可能与 FlClash 冲突；先在原应用中停止，再切换。企业 VPN 按其管理要求使用。</p></blockquote><p>VPN 标识和低延迟不等于所有网站都能访问。规则、节点地区、目标网站限制与手机网络都可能影响结果；以你实际需要的网站 / 应用为验证对象。</p><p><a id="android-daily"></a></p><h2 id="Android：日常使用与后台掉线">Android：日常使用与后台掉线</h2><p>保留稳定设置，锁屏断开时检查应用电池 / 后台管理</p><h3 id="每天的-4-个动作-2">每天的 4 个动作</h3><ol><li>打开 FlClash，确认个人配置。</li><li>规则模式，选择自动 / 常用公网节点。</li><li>点启动，实际检查目标应用。</li><li>用完点暂停 / 停止，下次无需重复导入。</li></ol><h3 id="锁屏几分钟就断开">锁屏几分钟就断开</h3><p>在手机“设置 → 应用 → FlClash”寻找电池、后台活动 / 后台运行选项，允许此应用按需要运行。有品牌自启动管理时，再检查 FlClash 是否被拦截。仅调整需要保留的应用。</p><p>不同手机路径不同。重启、网络切换或系统清理后台后，重新打开 FlClash 检查运行状态，必要时停止后再启动。</p><p><img src="/images/tutorials/flclash-vpn-t001/official-mobile-dashboard-v0.8.94.png" alt="官方 v0.8.94 mobile.gif 原尺寸帧：认识仪表盘和右下运行按钮；不是本次手机实拍，不代表当前服务状态。"></p><p>官方 v0.8.94 mobile.gif 原尺寸帧：认识仪表盘和右下运行按钮；不是本次手机实拍，不代表当前服务状态。</p><blockquote><p><strong>只有一个应用不走代理：</strong> 查看“工具 → 访问控制”。“只允许选中应用”时必须选中目标应用；“排除选中应用”时目标应用不能在排除列表。不熟悉时先关闭额外访问控制，再实际验证。</p></blockquote><p>同步与自动更新看<a href="#sync">同步订阅</a>，软件升级看<a href="#software-update">升级 FlClash</a>。不要把清空数据、反复卸载或随意改 DNS 作为后台问题的第一步。</p><p><a id="sync"></a></p><h2 id="同步订阅：更新节点和规则">同步订阅：更新节点和规则</h2><p>两端入口相同：配置 → 当前配置 ⋮ → 同步</p><p><img src="/images/tutorials/flclash-vpn-t001/11-mobile-sync.svg" alt="同步与自动更新操作示意 · Windows 使用同名菜单，间隔单位为分钟"></p><p>同步与自动更新操作示意 · Windows 使用同名菜单，间隔单位为分钟</p><ol><li><strong>手动同步：<strong>进入配置，找到正在使用的个人配置，点其旁的 <strong>⋮ / 更多</strong>，选择</strong>同步</strong>，等待结束。它重新获取远程订阅，不是升级 FlClash。</li><li><strong>同步后核对：</strong> 确认此配置仍被选中、代理组和节点正常。节点名称改变后，原选择可能不再适用；重选有效公网 / 自动组，启动并验证真实网页。</li><li><strong>设置自动更新：<strong>配置 → ⋮ → 编辑，打开</strong>自动更新</strong>，在间隔中按<strong>分钟</strong>填写，例如 <code>1440</code> 分钟 = 24 小时，再保存。不要把分钟误填成秒或小时。</li><li><strong>理解执行条件：</strong> 自动同步会受客户端是否运行、后台限制、网络以及链接状态影响。关机 / 离线时不能保证到点更新；需要时仍用手动同步。</li></ol><blockquote><p>管理员通知节点 / 规则变更、较久未使用，或多条节点持续失败时同步一次。无需每几分钟反复刷新；失败时先查看报错和个人链接状态。</p></blockquote><p>本地文件导入的配置可能没有 URL，不能自动从服务拉取变化；请领取个人远程订阅或按管理员说明替换文件。同步不能续期服务，也不能让被撤销的链接重新生效。</p><p><a id="replace-url"></a></p><h2 id="管理员换发链接：替换个人-URL">管理员换发链接：替换个人 URL</h2><p>仅在链接换发或确认填错时做，不需要每天改链接</p><p><img src="/images/tutorials/flclash-vpn-t001/04-win-url.svg" alt="替换 URL 操作示意 · example.invalid 是不可用占位域名"></p><p>替换 URL 操作示意 · example.invalid 是不可用占位域名</p><ol><li>先保留现有个人链接与<a href="#backup">备份与恢复</a>的本地备份。确认管理员给你的新链接属于你，并且仍是 <strong>Mihomo / Clash Verge（完整）</strong> 格式。</li><li>FlClash → <strong>配置</strong> → 当前配置旁 <strong>⋮</strong> → <strong>编辑</strong>。找到 URL，将旧内容<strong>整条替换</strong>为新链接；不用自己保留或拼接旧授权参数。</li><li>核对没有空格、换行和多余标点，点击<strong>保存</strong>。现行 v0.8.99 在 URL 改变后会重新获取配置；等待完成。</li><li>回到配置页确认当前选中项，进入代理页检查新节点。若该版本没有立即重新获取，可再使用<strong>同步</strong>；不要同时创建很多重复配置。</li><li>检查规则模式与节点，重新启动，实际打开目标网页。确认新链接可用后，按管理员要求处理旧链接；撤销的链接不能用于恢复服务。</li></ol><blockquote><p>如果找不到可编辑 URL，可能导入的是本地文件。可使用“配置 → ＋ → URL”新增远程配置，成功后选中它；先保留旧配置作为排查证据，避免误删唯一可用项。</p></blockquote><h3 id="同步、换链接、续期的边界">同步、换链接、续期的边界</h3><p>节点和规则变了：同步原链接。个人 URL 换发：替换 URL。授权到期 / 额度 / 设备限制：联系管理员确认。客户端安装与软件更新都不会自动购买、续期或解除服务限制。</p><p><a id="software-update"></a></p><h2 id="升级-FlClash：先备份再安装">升级 FlClash：先备份再安装</h2><p>工具 → 关于 → 检查更新；更新节点仍用“同步”</p><p><img src="/images/tutorials/flclash-vpn-t001/03-updates.svg" alt="更新类型操作示意 · 软件安装完成后重新验证连接"></p><p>更新类型操作示意 · 软件安装完成后重新验证连接</p><h3 id="升级前，两端都先做">升级前，两端都先做</h3><p>记录现用 FlClash 版本和安装来源，按<a href="#backup">备份与恢复</a>导出本地备份，另外保留个人订阅链接。进入<strong>工具 → 关于 → 检查更新</strong>，核对官方新版本说明与设备兼容性；也可访问官方 Releases。</p><table><thead><tr><th>平台</th><th>安装新版</th><th>完成后</th></tr></thead><tbody><tr><td>Windows</td><td>暂停连接，确认系统代理关闭并退出旧程序。下载同架构、同来源的新版 setup.exe，按安装向导更新。</td><td>打开软件，检查原配置和选中项；缺失时恢复个人备份 / 重新导入 URL。</td></tr><tr><td>Android</td><td>停止 VPN。下载与原安装同来源、同签名、兼容架构的新版 APK，打开后覆盖安装。</td><td>正常覆盖通常保留数据，仍需核对配置、VPN 权限、规则和节点。</td></tr></tbody></table><h3 id="每次升级的最后-4-个检查">每次升级的最后 4 个检查</h3><ol><li>关于页版本已变为预期版本。</li><li>自己的配置仍在并被选中，必要时同步一次。</li><li>规则模式与有效公网 / 自动节点正常。</li><li>重新启动，普通网页与目标网站实际可用；用完能停止。</li></ol><blockquote><p>安卓提示“应用未安装 / 签名冲突”时，先核对来源、架构和文件完整性。不要立即卸载旧应用或清空数据。更高版本备份可能无法恢复到低版本，降级前请管理员判断。</p></blockquote><p><a id="backup"></a></p><h2 id="备份与恢复：保存自己的配置">备份与恢复：保存自己的配置</h2><p>两端入口：工具 → 备份与恢复 → 本地</p><p><img src="/images/tutorials/flclash-vpn-t001/12-backup.svg" alt="本地备份 / 恢复操作示意 · 不要求设置 WebDAV"></p><p>本地备份 / 恢复操作示意 · 不要求设置 WebDAV</p><h3 id="做一次能找到的本地备份">做一次能找到的本地备份</h3><ol><li>进入<strong>工具 → 备份与恢复 → 本地 → 备份</strong>。按平台的保存 / 导出窗口选择位置；手机可能需要选择保存文件或分享至个人文件管理器。</li><li>记录文件位置、日期与 FlClash 版本。Windows 可放个人文档文件夹；手机放自己能找到的私人目录。核对文件确已保存，不要只看到“成功”就忘记它在哪里。</li><li>另外保留自己的完整订阅 URL。升级、换手机、重装或大幅调整前再做一次备份；备份不能替代有效服务授权。</li></ol><h3 id="恢复时，先保护当前数据">恢复时，先保护当前数据</h3><ol><li>先停止连接，给现有状态也做一份备份。进入<strong>本地 → 恢复</strong>，按提示选<strong>仅恢复配置文件</strong>或<strong>恢复所有数据</strong>，再选择自己的备份文件。</li><li>只需找回订阅时优先考虑“仅恢复配置文件”；需要完整设置才选“恢复所有数据”。读取恢复策略 / 覆盖提示，确认会影响的范围后再继续。不同设备的后台、代理 / VPN 权限需重新检查。</li><li>完成后检查个人配置、选中状态、规则模式、节点和同步；再启动验证真实网页。来自更高版本的备份先升级到兼容版本再恢复。</li></ol><blockquote><p>备份可能含订阅与节点凭据，只在私人位置保存。恢复不续期、不解除设备限制，也不保证不同平台的系统设置完全通用；个人 URL 可用于在新设备重新导入。</p></blockquote><p><a id="concepts"></a></p><h2 id="规则、全局、直连与流量入口">规则、全局、直连与流量入口</h2><p>分清“怎么分流”与“哪些应用进入客户端”</p><table><thead><tr><th>名称</th><th>作用</th><th>新人怎么选</th></tr></thead><tbody><tr><td>规则 / Rule</td><td>按订阅规则决定直连或代理组</td><td>日常默认；规则内容取决于你的配置</td></tr><tr><td>全局 / Global</td><td>进入客户端的流量主要使用全局代理组</td><td>临时排查时用，仍要选有效全局节点</td></tr><tr><td>直连 / Direct</td><td>进入客户端的流量直接连接目标</td><td>不会通过代理节点；不是更快的代理</td></tr><tr><td>自动选择 / AUTO</td><td>代理组按自身测试策略选节点</td><td>有可用公网自动组时优先；不保证每个目标最优</td></tr><tr><td>系统代理</td><td>让读取系统代理的 Windows 应用进入客户端</td><td>浏览器入门方式；不会自动接管所有应用</td></tr><tr><td>虚拟网卡 / TUN</td><td>使用虚拟网卡进行更广的流量接管</td><td>Windows 按权限和管理员指导使用</td></tr><tr><td>Android VPN</td><td>通过系统许可的 VPN 通道接管流量</td><td>首次允许 FlClash；访问控制可影响应用范围</td></tr><tr><td>ZeroTier / 私网节点</td><td>线路需要额外内部网络条件</td><td>普通新人先选公网，不必为它另装 ZeroTier</td></tr></tbody></table><h3 id="怎么判断是真的可用">怎么判断是真的可用</h3><p><strong>基础网络：</strong> 停止代理后，普通网页能打开。<strong>使用效果：</strong> 选对配置和节点后，目标网页 / 应用能工作。运行计时、延迟数字、流量计数只能辅助观察。</p><blockquote><p>切换“全局”不能自动接管不读取系统代理的 Windows 应用。客户端显示已用流量也不一定等于服务端计费；额度、设备数、并发和访问范围按服务管理员说明确认。</p></blockquote><p>代理不代表匿名，也不能保证所有网站都接受该地区或出口 IP。单一网站失败时，先判断其他网页是否正常，再换节点验证，不要立刻改动全部网络设置。</p><p><a id="windows-help"></a></p><h2 id="Windows-排障：先按现象处理">Windows 排障：先按现象处理</h2><p>每次只改相关的一项，保留配置与脱敏报错</p><table><thead><tr><th>现象</th><th>依次检查</th></tr></thead><tbody><tr><td>导入超时 / DNS 错误</td><td>检查现有联网和完整 URL；依赖旧代理时保留连接。必要时更换正常网络，勿乱改 DNS / 证书验证。</td></tr><tr><td>403 / 404 / 解析失败</td><td>核对链接与 Mihomo 格式；让管理员确认签发、过期 / 撤销状态。网页地址或错误格式不能作为配置导入。</td></tr><tr><td>无节点 / 无法启动</td><td>检查配置是否导入并被选中；代理组是否存在。保留报错，同步有效 URL；不要重复创建很多配置。</td></tr><tr><td>端口占用 / 内核启动失败</td><td>给新 FlClash 换不同且未占用的混合端口。依赖旧代理时保留旧内核，按<a href="#legacy">保留旧连接的迁移步骤</a>迁移；没有联网依赖才停止其他代理。</td></tr><tr><td>运行中，浏览器打不开</td><td>确认系统代理开启、规则模式、有效公网节点；换节点再试，停止后检查原网络是否可用。</td></tr><tr><td>浏览器可用，其他程序不行</td><td>该程序可能不读取系统代理；确认应用自身网络设置，需要 TUN 时按管理员指导配置。</td></tr><tr><td>关窗口后仍在连接</td><td>查看托盘和运行状态；点暂停 / 停止，确认系统代理关闭，再按需要退出程序。</td></tr><tr><td>停止后普通网页仍失败</td><td>确认其他客户端未运行，检查基础网络与残留手动代理；企业代理先找企业管理员。</td></tr></tbody></table><p><img src="/images/tutorials/flclash-vpn-t001/13-diagnose.svg" alt="排障操作示意 · 先现有联网，再配置与节点，最后真实网页；依赖旧代理时保留回退连接"></p><p>排障操作示意 · 先现有联网，再配置与节点，最后真实网页；依赖旧代理时保留回退连接</p><blockquote><p>升级后配置消失：先检查选中项和旧数据，再按<a href="#backup">备份与恢复</a>恢复 / 重新导入 URL。求助时按<a href="#cheatsheet">速查与求助模板</a>模板提供版本、时间、网络与脱敏报错。</p></blockquote><p><a id="android-help"></a></p><h2 id="Android-排障：安装到后台连接">Android 排障：安装到后台连接</h2><p>手机菜单按品牌变化，避免反复卸载或清空数据</p><table><thead><tr><th>现象</th><th>依次检查</th></tr></thead><tbody><tr><td>APK 无法安装 / 不兼容</td><td>核对手机架构、系统兼容性、来源与文件完整性；重新下载正确 APK。</td></tr><tr><td>签名冲突 / 应用未安装</td><td>核对是否跨渠道安装，使用原渠道新版；先导出备份，不要立即卸载旧应用。</td></tr><tr><td>导入超时 / DNS 错误</td><td>浏览器检查基础网络，核对完整链接；必要时换 Wi-Fi / 移动数据。</td></tr><tr><td>403 / 404 / 解析失败</td><td>确认个人 URL 和 Mihomo 格式；管理员核对授权、签发与换发状态。</td></tr><tr><td>点击启动没连上</td><td>检查已选配置、系统 VPN 许可；停止其他 VPN，再查看报错、节点和网页。</td></tr><tr><td>有 VPN 标识但网页打不开</td><td>检查规则模式与有效公网节点；换节点，停止再启动，重开目标应用。</td></tr><tr><td>锁屏就断 / 重启后未运行</td><td>检查应用电池 / 后台与品牌自启动管理；重启后先打开软件检查状态。</td></tr><tr><td>只有一个应用不能用</td><td>检查访问控制是否排除它，或允许列表中遗漏它；也可能是网站地区 / 账号限制。</td></tr><tr><td>节点延迟低，下载很慢</td><td>延迟不是带宽。换公网节点、检查信号与网络时段，以真实访问判断。</td></tr></tbody></table><h3 id="求助前记录">求助前记录</h3><p>手机型号、Android 版本、FlClash 版本、发生时间、Wi-Fi / 移动数据、失败步骤、报错文字、停止代理后普通网页是否可用，以及已尝试的处理。</p><p>截图遮住个人 URL、二维码、节点凭据和个人信息。不要公开上传完整配置 / 日志包。按管理员指定私人渠道进一步检查授权。</p><p><img src="/images/tutorials/flclash-vpn-t001/official-mobile-tools-v0.8.94.png" alt="官方 v0.8.94 mobile.gif 工具页原尺寸帧示例；不同版本入口可能改变。"></p><p>官方 v0.8.94 mobile.gif 工具页原尺寸帧示例；不同版本入口可能改变。</p><blockquote><p>修改前记录原设置，一次只改一项。成功后保留这套配置；仍失败时提供证据，不继续随意改 DNS、覆写和网络高级选项。</p></blockquote><p><a id="legacy"></a></p><h2 id="附录：旧客户端与保留联网迁移">附录：旧客户端与保留联网迁移</h2><p>依赖旧代理时保留旧内核，新旧端口不同，先独立测试再切换</p><h3 id="已有-Luomo-Windows-Mihomo-一键包">已有 Luomo Windows Mihomo 一键包</h3><p>20260731 包由 <code>Install-Mihomo.cmd</code> 安装；桌面 <strong>Luomo Mihomo</strong> 启动、<strong>Stop</strong> 停止、<strong>Update</strong> 更新订阅。个人 URL 在 <code>subscription.url</code> 中保存，改后 Update。升级包须管理员确认；这些脚本入口不适用于 FlClash。</p><h3 id="已有原版旧内核-Clash-for-Windows">已有原版旧内核 Clash for Windows</h3><p>领取 <strong>“Clash for Windows（旧版）”</strong> 格式（通常 <code>format=clash</code>）；<strong>Profiles → URL → Download → 选中</strong>，<strong>Proxies → Rule</strong> 选节点，<strong>General → System Proxy</strong> 控制代理。旧格式筛选后节点可能更少；不提供未核实的新安装来源。</p><h3 id="依赖旧代理联网：保留回退连接">依赖旧代理联网：保留回退连接</h3><ol><li>先下载离线安装文件、保存个人 URL 与备份；记录原系统代理 <code>127.0.0.1:原端口</code> 和恢复方法。<strong>旧内核保持运行</strong>，首次打开新软件也可能改变系统代理，须具备恢复能力。</li><li>保持 FlClash 的<strong>系统代理、虚拟网卡关闭</strong>；若原代理被改变，先恢复原地址。进入<strong>工具 → 常规 → 端口 → 混合端口 → 提交</strong>，选择与旧不同且未占用的端口，如 <code>7892</code>。先用下方 PowerShell 命令检查：有输出行表示已占用，无输出才是候选。</li><li>导入有效 Mihomo 配置，选规则和公网节点，启动新内核。用明确指定<strong>新端口</strong>的工具单独检查 HTTPS；直接开读取系统代理的浏览器，可能测到旧连接。</li><li>新端口测试成功后，再开启 FlClash 系统代理，核对 Windows 代理地址已改为新端口，实际检查普通网页与目标网站。</li><li>切换失败：先停止新连接，再按下方入口恢复原系统代理地址；旧内核继续运行。成功后旧内核仍保留作回退，不要把旧配置目录直接复制为 FlClash 数据。</li></ol><figure class="highlight powershell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># PowerShell：先检查端口（示例 7892）：</span></span><br><span class="line"><span class="built_in">Get-NetTCPConnection</span> <span class="literal">-State</span> Listen <span class="literal">-LocalPort</span> <span class="number">7892</span> <span class="literal">-ErrorAction</span> SilentlyContinue</span><br><span class="line"></span><br><span class="line"><span class="comment"># 启动新内核后独立测试（7892 换成实际新端口）：</span></span><br><span class="line">curl.exe <span class="literal">-x</span> http://<span class="number">127.0</span>.<span class="number">0.1</span>:<span class="number">7892</span> <span class="literal">--max-time</span> <span class="number">20</span> <span class="literal">-sS</span> <span class="literal">-o</span> NUL <span class="literal">-w</span> <span class="string">&quot;HTTP %&#123;http_code&#125;&quot;</span> `</span><br><span class="line">  https://cp.cloudflare.com/generate_204</span><br><span class="line"><span class="comment"># 预期最终输出 HTTP 204；CONNECT 的 200 不代表此项通过。</span></span><br></pre></td></tr></table></figure><blockquote><p><strong>恢复原地址：</strong> Windows 设置 → 网络和 Internet → 代理 → 手动设置代理（Windows 11 点“编辑”）→ 开启“使用代理服务器”，地址填原地址（本地客户端通常为 <code>127.0.0.1</code>），端口填记录的原端口，再保存。公司网络 / PAC 请管理员处理。<br><strong>端口提示：</strong> 新旧客户端可能都默认用 7890；允许 1024-49151。软件仅检查本配置内重复，不检测其他程序占用；7892 只是示例，勿盲目结束进程。</p></blockquote><p><a id="cheatsheet"></a></p><h2 id="速查与首次上手自查">速查与首次上手自查</h2><p>可以单独截图给已经完成配置的人</p><table><thead><tr><th>操作</th><th>Windows FlClash</th><th>Android FlClash</th></tr></thead><tbody><tr><td>导入订阅</td><td>配置 → ＋ → URL → 提交 → 选中</td><td>配置 → ＋ → URL → 提交 → 选中</td></tr><tr><td>模式 / 节点</td><td>仪表盘规则 → 代理页选有效节点</td><td>仪表盘规则 → 代理页选有效节点</td></tr><tr><td>启动 / 停止</td><td>系统代理开启 → 启动；结束暂停并确认代理关闭</td><td>启动 → 首次允许 VPN；同一按钮暂停 / 停止</td></tr><tr><td>更新节点</td><td>配置 → 当前配置 ⋮ → 同步</td><td>配置 → 当前配置 ⋮ → 同步</td></tr><tr><td>换个人 URL</td><td>配置 → ⋮ → 编辑 → URL → 保存</td><td>配置 → ⋮ → 编辑 → URL → 保存</td></tr><tr><td>更新软件</td><td>先备份；工具 → 关于 → 检查更新</td><td>先备份；工具 → 关于 → 检查更新</td></tr><tr><td>备份 / 恢复</td><td>工具 → 备份与恢复 → 本地</td><td>工具 → 备份与恢复 → 本地</td></tr></tbody></table><h3 id="首次完成自查">首次完成自查</h3><ul><li><p>[ ] 安装来源可信、架构兼容；导入并选中自己的 Mihomo 订阅</p></li><li><p>[ ] 规则模式与有效公网 / 自动节点，实际目标网页可用</p></li><li><p>[ ] 会启动和停止；Windows 系统代理 / Android VPN 状态正确</p></li><li><p>[ ] 会同步、换链接；知道升级前备份、备份保存在哪里</p></li></ul><h3 id="脱敏求助模板">脱敏求助模板</h3><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">设备型号 / 系统 / FlClash 版本：</span><br><span class="line">发生时间 / Wi-Fi 或移动数据：</span><br><span class="line">停止代理后普通网页是否正常：</span><br><span class="line">失败步骤与脱敏报错：</span><br><span class="line">已尝试的处理：</span><br></pre></td></tr></table></figure><blockquote><p>这份清单用于人工核对，不会自动检测连接。求助时不要填写完整个人 URL，也不要附未遮挡的二维码或凭据。</p></blockquote><p><a id="sources"></a></p><h2 id="版本说明与参考资料">版本说明与参考资料</h2><p>菜单与文字按 <strong>FlClash v0.8.99</strong> 核对，核对日期为 <strong>2026-10-09</strong>。后续版本、不同屏幕布局和手机品牌可能调整入口位置，优先按“配置、同步、代理、工具、关于”等文字寻找。</p><table><thead><tr><th>项目</th><th>参考资料与说明</th></tr></thead><tbody><tr><td>官方下载</td><td><a href="https://github.com/chen08209/FlClash/releases/latest">FlClash Releases 最新版</a>；本文对应 <a href="https://github.com/chen08209/FlClash/releases/tag/v0.8.99">v0.8.99 发布</a>，发布于 2026-10-03。</td></tr><tr><td>菜单与订阅</td><td><a href="https://github.com/chen08209/FlClash/tree/v0.8.99/lib/views/profiles">v0.8.99 配置页</a>、<a href="https://github.com/chen08209/FlClash/blob/v0.8.99/arb/intl_zh_CN.arb">简体中文文字</a>；自动更新间隔的单位是分钟。</td></tr><tr><td>更新与备份</td><td><a href="https://github.com/chen08209/FlClash/blob/v0.8.99/lib/views/about.dart">关于页</a>、<a href="https://github.com/chen08209/FlClash/blob/v0.8.99/lib/views/backup_and_restore.dart">备份与恢复页</a>。</td></tr><tr><td>13 张操作图</td><td>原创 SVG 操作示意，不是实机截图。位置、节点名称和占位链接应按自己的版本与订阅确认。</td></tr><tr><td>3 张官方示例</td><td><a href="https://github.com/chen08209/FlClash/blob/v0.8.99/snapshots/preview.png">v0.8.99 preview.png</a>与 <a href="https://github.com/chen08209/FlClash/blob/v0.8.94/snapshots/mobile.gif">v0.8.94 mobile.gif</a> 的原尺寸帧。桌面展示为 macOS，手机图只用于认识布局。</td></tr><tr><td>图片来源许可</td><td>来源项目标示 <a href="https://github.com/chen08209/FlClash/blob/v0.8.99/LICENSE">GPL-3.0</a>，图片保留版本与出处说明。</td></tr></tbody></table><p>安卓步骤依据官方菜单和界面示例整理，<strong>未完成手机实机安装、后台保活与真实连接测试</strong>。各人的系统界面、订阅规则、服务额度、设备限制与访问结果，以自己的设备和管理员现行说明为准。</p><p>管理员向新人分发材料时，应单独提供有效个人订阅和兼容安装文件，并说明设备与配额限制。通用教程可以转发，个人链接、二维码、配置和备份请单独保管。</p>]]>
    </content>
    <id>https://blog.luomo.moe/posts/fa8c2026.html</id>
    <link href="https://blog.luomo.moe/posts/fa8c2026.html"/>
    <published>2026-10-08T16:00:00.000Z</published>
    <summary>Windows 和安卓统一使用 FlClash，逐步完成安装、个人订阅导入、规则与节点选择、启停、同步、升级、备份恢复、排障，以及保留旧连接的安全迁移。</summary>
    <title>Luomo VPN 新人教程：Windows 和安卓 FlClash 安装、配置、更新与使用</title>
    <updated>2026-10-08T16:00:00.000Z</updated>
  </entry>
  <entry>
    <author>
      <name>洛墨-天染-依然</name>
    </author>
    <category term="技术实践" scheme="https://blog.luomo.moe/categories/%E6%8A%80%E6%9C%AF%E5%AE%9E%E8%B7%B5/"/>
    <category term="Hexo" scheme="https://blog.luomo.moe/tags/Hexo/"/>
    <category term="Butterfly" scheme="https://blog.luomo.moe/tags/Butterfly/"/>
    <category term="Vercel" scheme="https://blog.luomo.moe/tags/Vercel/"/>
    <category term="GitHub" scheme="https://blog.luomo.moe/tags/GitHub/"/>
    <category term="pnpm" scheme="https://blog.luomo.moe/tags/pnpm/"/>
    <category term="CI/CD" scheme="https://blog.luomo.moe/tags/CI-CD/"/>
    <content>
      <![CDATA[<link rel="stylesheet" class="aplayer-secondary-style-marker" href="/assets/css/APlayer.min.css"><script src="/assets/js/APlayer.min.js" class="aplayer-secondary-script-marker"></script><script class="meting-secondary-script-marker" src="/assets/js/Meting.min.js"></script><link rel="stylesheet" type="text&#x2F;css" href="/pluginsSrc/hexo-tag-hint/dist/hexo-tag-hint.min.css"><p>这篇文章把 <ruby>Luomoの云日常<rt>本站博客</rt></ruby> 自己放到解剖台上：源码托管在 GitHub，Hexo 把 Markdown 编译成静态文件，Butterfly 负责页面结构与样式，Vercel 连接 <code>main</code> 分支并将构建结果发布到 <code>blog.luomo.moe</code>。这不是一份只在空目录里成功过的“理论教程”，而是对当前生产仓库、部署记录和几次真实事故的复盘。</p><mark class="hl-label blue">Hexo静态生成</mark> <mark class="hl-label purple">Butterfly主题</mark> <mark class="hl-label black">GitHub源代码</mark> <mark class="hl-label green">Vercel生产部署</mark><a class="btn-beautify blue larger" href="https://blog.luomo.moe" title="打开博客"><i class="fas fa-blog"></i><span>打开博客</span></a><a class="btn-beautify purple larger" href="https://github.com/luomo66ccff/hexo" title="查看 GitHub 仓库"><i class="fab fa-github"></i><span>查看 GitHub 仓库</span></a><a class="btn-beautify green larger" href="https://hexo.io/docs/" title="Hexo 官方文档"><i class="fas fa-book"></i><span>Hexo 官方文档</span></a><a class="btn-beautify orange larger" href="https://vercel.com/docs/git" title="Vercel Git 部署文档"><i class="fas fa-cloud"></i><span>Vercel Git 部署文档</span></a><div class="note success flat"><p>本文所有版本号、目录、构建命令和故障原因都来自当前仓库或真实部署记录。账号令牌、百度推送 Token、评论系统密钥等不会写进文章，更不会塞进 Git 历史供全世界考古。</p></div><span id="more"></span><h2 id="先看最终架构">先看最终架构</h2><div class="mermaid-wrap"><pre class="mermaid-src" data-config="{}" hidden>    flowchart LR  A[&quot;source&#x2F;_posts&#x2F;*.md\n文章与 Front-matter&quot;] --&gt; B[&quot;Markdown-it 与内容插件\nKaTeX &#x2F; Ruby &#x2F; Hint &#x2F; Spoiler&quot;]  B --&gt; C[&quot;Hexo 7.3.0\n生成路由与静态资源&quot;]  T[&quot;Butterfly 4.13.0\nPug + Stylus + Tag Plugins&quot;] --&gt; C  X[&quot;自定义 CSS &#x2F; JS\n白色主题与 PJAX 兼容&quot;] --&gt; C  C --&gt; P[&quot;public&#x2F;\nHTML &#x2F; CSS &#x2F; JS &#x2F; XML&quot;]  P --&gt; V[&quot;Vercel Production\n静态托管与边缘缓存&quot;]  G[&quot;GitHub main 分支&quot;] --&gt; V  V --&gt; D[&quot;blog.luomo.moe\nHTTPS 自定义域名&quot;]  </pre></div><p>整条链路里没有运行时数据库，也没有常驻 Node.js 服务。Node.js 只在构建阶段执行 Hexo；部署完成后，访客拿到的是预先生成的 HTML、CSS、JavaScript、图片、搜索索引、Feed 和站点地图。<span class="hint--info hint--rounded hint--top" data-hint="静态文件托管生产域名分发" ontouchstart>构建时生成</span> 是理解这套架构的三个关键词。</p><h2 id="当前生产基线">当前生产基线</h2><p>截至本文发布前，我重新执行了版本检查、干净构建与 Vercel 控制面审计：</p><table><thead><tr><th>项目</th><th>当前值</th><th>说明</th></tr></thead><tbody><tr><td>GitHub 仓库</td><td><code>luomo66ccff/hexo</code></td><td>Vercel 项目已真实连接该仓库</td></tr><tr><td>生产分支</td><td><code>main</code></td><td>推送后触发 Production 部署</td></tr><tr><td>Hexo</td><td><code>7.3.0</code></td><td>实际安装版本，不是只看范围声明</td></tr><tr><td>Butterfly</td><td><code>4.13.0</code></td><td>主题源码随仓库版本化</td></tr><tr><td>本地验证环境</td><td>Node.js <code>24.14.0</code>、pnpm <code>11.16.0</code></td><td>本文构建基线</td></tr><tr><td>Vercel 项目运行时</td><td>Node.js <code>20.x</code></td><td>当前仍可构建，但已经收到弃用警告</td></tr><tr><td>Vercel 项目根目录</td><td><code>.</code></td><td>博客位于仓库根目录</td></tr><tr><td>构建输出</td><td><code>public/</code></td><td>由 <code>hexo generate</code> 生成</td></tr><tr><td>最近生产状态</td><td><code>READY</code></td><td>控制面显示最新 Production 已就绪</td></tr><tr><td>正式域名</td><td><code>https://blog.luomo.moe</code></td><td>指向最新生产部署</td></tr></tbody></table><div class="note warning flat"><p>Vercel 已提示 Node.js 20.x 将在 2026 年 10 月 1 日后阻止新构建。新建项目应直接选 24.x；本站也需要在截止日前完成 24.x 迁移和一次完整回归，不能等构建日历翻脸时再临场表演滑跪。</p></div><h2 id="仓库结构：哪些文件真正参与部署">仓库结构：哪些文件真正参与部署</h2><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line">hexo-blog/</span><br><span class="line">├─ _config.yml                    # Hexo 站点、路由与插件配置</span><br><span class="line">├─ package.json                   # 依赖与 clean/build/server 脚本</span><br><span class="line">├─ pnpm-lock.yaml                 # 可复现依赖图</span><br><span class="line">├─ pnpm-workspace.yaml            # 将仓库根声明为 pnpm workspace</span><br><span class="line">├─ vercel.json                    # Vercel 构建、输出与缓存响应头</span><br><span class="line">├─ source/</span><br><span class="line">│  ├─ _posts/                    # 已发布 Markdown 文章</span><br><span class="line">│  ├─ _drafts/                   # 草稿，不进入正式构建</span><br><span class="line">│  ├─ css/luomo-theme.css        # 白色主题覆盖层</span><br><span class="line">│  ├─ js/luomo-theme.js          # 项目筛选与 PJAX 重初始化</span><br><span class="line">│  └─ service-worker.js          # 旧离线缓存的一次性退役脚本</span><br><span class="line">├─ themes/butterfly/              # Butterfly 4.13.0 主题源码</span><br><span class="line">└─ public/                        # 构建产物，忽略提交，由 Vercel 生成</span><br></pre></td></tr></table></figure><p>这里最重要的边界是：<code>source/</code>、主题和配置属于源码，<code>public/</code> 属于可重复生成的产物。仓库的 <code>.gitignore</code> 会排除 <code>public/</code>、<code>node_modules/</code>、<code>db.json</code>、<code>.vercel/</code> 与本地环境文件，避免把几万份可再生文件和项目链接元数据一起扔进 Git。</p><h2 id="从零准备运行环境">从零准备运行环境</h2><p>为了避开即将退役的 Node.js 20，新部署建议直接使用 Node.js 24 LTS，并通过 Corepack 或独立安装获得 pnpm：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">node --version</span><br><span class="line">corepack <span class="built_in">enable</span></span><br><span class="line">corepack prepare pnpm@11.9.0 --activate</span><br><span class="line">pnpm --version</span><br></pre></td></tr></table></figure><p>然后克隆仓库并严格按照锁文件安装：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">git <span class="built_in">clone</span> https://github.com/luomo66ccff/hexo.git</span><br><span class="line"><span class="built_in">cd</span> hexo</span><br><span class="line">pnpm install --frozen-lockfile</span><br></pre></td></tr></table></figure><p><code>--frozen-lockfile</code> 会在 <code>package.json</code> 与锁文件不一致时直接失败。失败虽然很不给面子，却比生产构建偷偷换一批依赖、上线后集体失忆更安全。</p><h3 id="pnpm-版本还有一个现实差异">pnpm 版本还有一个现实差异</h3><p>仓库的 <code>packageManager</code> 声明为 <code>pnpm@11.9.0</code>，当前本地验证实际使用 <code>11.16.0</code>；现有 Vercel 项目则根据项目创建时间和锁文件格式选择了 pnpm 9.x。也就是说，“写了 packageManager”不自动等于每个环境都精确执行该版本。</p><p>若团队需要完全一致，可以启用 Vercel 支持的 Corepack 机制，并把本地、CI 与 Vercel 的 pnpm 版本统一；在完成迁移前，至少要保留锁文件安装和 Preview 验证，不能一边跨大版本一边直冲生产。</p><h2 id="package-json：把流程收口成四个命令"><code>package.json</code>：把流程收口成四个命令</h2><p>仓库使用的脚本非常克制：</p><figure class="highlight json"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="punctuation">&#123;</span></span><br><span class="line">  <span class="attr">&quot;scripts&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">    <span class="attr">&quot;build&quot;</span><span class="punctuation">:</span> <span class="string">&quot;hexo generate&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;clean&quot;</span><span class="punctuation">:</span> <span class="string">&quot;hexo clean&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;deploy&quot;</span><span class="punctuation">:</span> <span class="string">&quot;hexo deploy&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;server&quot;</span><span class="punctuation">:</span> <span class="string">&quot;hexo server&quot;</span></span><br><span class="line">  <span class="punctuation">&#125;</span></span><br><span class="line"><span class="punctuation">&#125;</span></span><br></pre></td></tr></table></figure><p>日常真正使用的是：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">pnpm run clean     <span class="comment"># 删除 db.json 与 public，排除旧产物污染</span></span><br><span class="line">pnpm run build     <span class="comment"># 生成 public</span></span><br><span class="line">pnpm run server    <span class="comment"># 在 http://localhost:4000 预览</span></span><br></pre></td></tr></table></figure><p>虽然还保留了 <code>hexo deploy</code> 脚本，但本站生产链路并不依赖它。仓库也没有安装 <code>hexo-deployer-git</code>，所以正确发布方式是提交源码并推送 GitHub，再由 Vercel 构建；不要把 <code>hexo deploy</code> 和 Vercel Git 集成同时当作两位司机抢方向盘。</p><h2 id="为什么需要-pnpm-workspace-yaml">为什么需要 <code>pnpm-workspace.yaml</code></h2><p>当前文件内容如下：</p><figure class="highlight yaml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="attr">packages:</span></span><br><span class="line">  <span class="bullet">-</span> <span class="string">.</span></span><br><span class="line"></span><br><span class="line"><span class="attr">allowBuilds:</span></span><br><span class="line">  <span class="attr">core-js:</span> <span class="literal">true</span></span><br><span class="line">  <span class="attr">ejs:</span> <span class="literal">true</span></span><br><span class="line">  <span class="attr">hexo-util:</span> <span class="literal">true</span></span><br><span class="line">  <span class="attr">highlight.js:</span> <span class="literal">true</span></span><br></pre></td></tr></table></figure><p><code>packages: - .</code> 明确告诉 pnpm：仓库根就是工作区包。它看起来像在认真声明“我家住我家”，但这行曾经解决过 Vercel 对工作区根识别不完整的问题。<code>allowBuilds</code> 则只允许列出的依赖执行构建脚本，降低安装阶段随意运行脚本的范围。</p><p><a href="https://pnpm.io/workspaces">pnpm 官方工作区文档</a>要求工作区根包含 <code>pnpm-workspace.yaml</code>。即使这里只有一个包，把根目录显式列出也能让本地与远端对项目边界形成同一套认知。</p><h2 id="Hexo-主配置：域名、路由与生成目录">Hexo 主配置：域名、路由与生成目录</h2><p><code>_config.yml</code> 中与部署最相关的部分可以压缩为：</p><figure class="highlight yaml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line"><span class="attr">title:</span> <span class="string">Luomoの云日常</span></span><br><span class="line"><span class="attr">language:</span> <span class="string">zh-CN</span></span><br><span class="line"><span class="attr">timezone:</span> <span class="string">Asia/Shanghai</span></span><br><span class="line"></span><br><span class="line"><span class="attr">url:</span> <span class="string">https://blog.luomo.moe</span></span><br><span class="line"><span class="attr">permalink:</span> <span class="string">posts/:abbrlink.html</span></span><br><span class="line"><span class="attr">source_dir:</span> <span class="string">source</span></span><br><span class="line"><span class="attr">public_dir:</span> <span class="string">public</span></span><br><span class="line"><span class="attr">render_drafts:</span> <span class="literal">false</span></span><br><span class="line"><span class="attr">theme:</span> <span class="string">butterfly</span></span><br><span class="line"></span><br><span class="line"><span class="attr">markdown:</span></span><br><span class="line">  <span class="attr">plugins:</span></span><br><span class="line">    <span class="bullet">-</span> <span class="string">&#x27;@renbaoshuo/markdown-it-katex&#x27;</span></span><br><span class="line">    <span class="bullet">-</span> <span class="string">markdown-it-ruby</span></span><br></pre></td></tr></table></figure><p><code>url</code> 必须写正式域名，否则 Feed、站点地图、规范链接和百度提交列表可能继续散发旧地址。<code>permalink</code> 使用 <code>hexo-abbrlink</code> 生成稳定短链接，标题改名时不会顺手给所有外链来一记背刺。</p><p>草稿放在 <code>source/_drafts/</code>，因为 <code>render_drafts: false</code>，正式构建不会发布；需要预览时使用 <code>hexo server --draft</code>，不要为了看草稿把它先塞进 <code>_posts</code> 再祈祷自己记得删。</p><h2 id="我扫描到的-28-个直接依赖">我扫描到的 28 个直接依赖</h2><p>依赖多不代表每篇文章都要全员上台。下面按职责列出当前安装结果，版本来自实际依赖树：</p><details class="toggle" style="border: 1px solid #425b89"><summary class="toggle-button" style="background-color: #425b89;color: #ffffff">展开完整插件审计</summary><div class="toggle-content"><table><thead><tr><th>类别</th><th>包与实际版本</th><th>当前职责</th></tr></thead><tbody><tr><td>核心</td><td><code>hexo 7.3.0</code>、<code>hexo-util 3.3.0</code>、<code>hexo-server 3.0.0</code></td><td>生成器、工具与本地预览</td></tr><tr><td>渲染器</td><td><code>hexo-renderer-markdown-it 7.1.1</code>、<code>ejs 2.0.0</code>、<code>pug 3.0.0</code>、<code>stylus 3.0.1</code></td><td>Markdown、模板与样式编译</td></tr><tr><td>基础生成器</td><td><code>archive 2.0.0</code>、<code>category 2.0.0</code>、<code>index 3.0.0</code>、<code>tag 2.0.0</code></td><td>首页、归档、分类与标签</td></tr><tr><td>发现与订阅</td><td><code>searchdb 1.5.0</code>、<code>feed 4.0.0</code>、<code>sitemap 3.0.1</code>、<code>baidu-sitemap 0.1.9</code></td><td>搜索索引、Atom、两类站点地图</td></tr><tr><td>链接与 SEO</td><td><code>abbrlink 2.2.1</code>、<code>filter-nofollow 2.0.2</code>、<code>baidu-url-submit 0.0.6</code></td><td>固定链接、外链属性与提交列表</td></tr><tr><td>数学与排版</td><td><code>markdown-it-katex 2.0.2</code>、<code>markdown-it-ruby 0.1.1</code>、<code>wordcount 6.0.1</code></td><td>公式、Ruby 注音、字数与阅读时间</td></tr><tr><td>交互内容</td><td><code>hexo-spoiler 1.7.4</code>、<code>hexo-tag-hint 0.3.1</code></td><td>模糊隐藏与行内提示</td></tr><tr><td>媒体标签</td><td><code>hexo-tag-aplayer 3.0.4</code>、<code>hexo-tag-bilibili 0.3.1</code></td><td>音乐和视频嵌入，本文不强行使用</td></tr><tr><td>说说生态</td><td><code>hexo-butterfly-artitalk 1.0.4</code>、<code>hexo-butterfly-hpptalk 1.0.4</code></td><td>说说页面能力，需要独立配置凭据</td></tr><tr><td>备用主题</td><td><code>hexo-theme-landscape 1.0.0</code></td><td>Hexo 默认主题依赖，当前未启用</td></tr></tbody></table></div></details><p>全站构建时，搜索、Feed、站点地图、nofollow 与字数统计会自然参与；本文另外使用了 KaTeX、Ruby、Hint、Spoiler，以及 Butterfly 自带的 Mermaid、Note、Tabs、Timeline、Button、Label 和 HideToggle。APlayer 与 Bilibili 不服务于部署主题，硬塞一段音乐视频只会让教程像项目经理突然唱起片尾曲。</p><h2 id="Butterfly-与白色主题如何叠加">Butterfly 与白色主题如何叠加</h2><p>本站把 Butterfly 4.13.0 的源码放在 <code>themes/butterfly/</code> 中，并启用了这些关键项：</p><figure class="highlight yaml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br></pre></td><td class="code"><pre><span class="line"><span class="attr">display_mode:</span> <span class="string">light</span></span><br><span class="line"></span><br><span class="line"><span class="attr">darkmode:</span></span><br><span class="line">  <span class="attr">enable:</span> <span class="literal">false</span></span><br><span class="line">  <span class="attr">button:</span> <span class="literal">false</span></span><br><span class="line"></span><br><span class="line"><span class="attr">preloader:</span></span><br><span class="line">  <span class="attr">enable:</span> <span class="literal">false</span></span><br><span class="line"></span><br><span class="line"><span class="attr">wordcount:</span></span><br><span class="line">  <span class="attr">enable:</span> <span class="literal">true</span></span><br><span class="line"></span><br><span class="line"><span class="attr">search:</span></span><br><span class="line">  <span class="attr">use:</span> <span class="string">local_search</span></span><br><span class="line"></span><br><span class="line"><span class="attr">pjax:</span></span><br><span class="line">  <span class="attr">enable:</span> <span class="literal">true</span></span><br><span class="line"></span><br><span class="line"><span class="attr">lazyload:</span></span><br><span class="line">  <span class="attr">enable:</span> <span class="literal">true</span></span><br><span class="line"></span><br><span class="line"><span class="attr">mermaid:</span></span><br><span class="line">  <span class="attr">enable:</span> <span class="literal">true</span></span><br></pre></td></tr></table></figure><p>自定义样式不直接堆进文章，而是由主题注入：</p><figure class="highlight yaml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="attr">inject:</span></span><br><span class="line">  <span class="attr">head:</span></span><br><span class="line">    <span class="bullet">-</span> <span class="string">&lt;link</span> <span class="string">rel=&quot;stylesheet&quot;</span> <span class="string">href=&quot;/css/luomo-theme.css?v=20260804-white&quot;&gt;</span></span><br><span class="line">  <span class="attr">bottom:</span></span><br><span class="line">    <span class="bullet">-</span> <span class="string">&lt;script</span> <span class="string">src=&quot;/js/luomo-theme.js?v=20260626-butterfly&quot;&gt;&lt;/script&gt;</span></span><br></pre></td></tr></table></figure><p><code>source/css/luomo-theme.css</code> 将正文卡片、表格、代码块、导航和页脚统一为白色；移动端表格单独允许横向滚动。<code>source/js/luomo-theme.js</code> 在 <code>DOMContentLoaded</code> 和 <code>pjax:complete</code> 后重新初始化交互，避免 PJAX 换页后按钮看得见却像灵魂已离职。</p><p>长期维护时，优先把个性化放在 <code>source/css</code>、<code>source/js</code> 和独立主题配置中。本站历史上修改过少量 Butterfly 内部模板与过滤器，所以升级主题前必须对比这些补丁，不能直接覆盖整个主题目录。</p><h2 id="vercel-json：生产构建的唯一明确契约"><code>vercel.json</code>：生产构建的唯一明确契约</h2><p>当前仓库使用：</p><figure class="highlight json"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><span class="line"><span class="punctuation">&#123;</span></span><br><span class="line">  <span class="attr">&quot;$schema&quot;</span><span class="punctuation">:</span> <span class="string">&quot;https://openapi.vercel.sh/vercel.json&quot;</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;buildCommand&quot;</span><span class="punctuation">:</span> <span class="string">&quot;pnpm run build&quot;</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;installCommand&quot;</span><span class="punctuation">:</span> <span class="string">&quot;pnpm install --frozen-lockfile&quot;</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;outputDirectory&quot;</span><span class="punctuation">:</span> <span class="string">&quot;public&quot;</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;framework&quot;</span><span class="punctuation">:</span> <span class="string">&quot;hexo&quot;</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;headers&quot;</span><span class="punctuation">:</span> <span class="punctuation">[</span></span><br><span class="line">    <span class="punctuation">&#123;</span></span><br><span class="line">      <span class="attr">&quot;source&quot;</span><span class="punctuation">:</span> <span class="string">&quot;/(.*)&quot;</span><span class="punctuation">,</span></span><br><span class="line">      <span class="attr">&quot;headers&quot;</span><span class="punctuation">:</span> <span class="punctuation">[</span></span><br><span class="line">        <span class="punctuation">&#123;</span></span><br><span class="line">          <span class="attr">&quot;key&quot;</span><span class="punctuation">:</span> <span class="string">&quot;Cache-Control&quot;</span><span class="punctuation">,</span></span><br><span class="line">          <span class="attr">&quot;value&quot;</span><span class="punctuation">:</span> <span class="string">&quot;public, max-age=0, s-maxage=300, must-revalidate&quot;</span></span><br><span class="line">        <span class="punctuation">&#125;</span></span><br><span class="line">      <span class="punctuation">]</span></span><br><span class="line">    <span class="punctuation">&#125;</span></span><br><span class="line">  <span class="punctuation">]</span></span><br><span class="line"><span class="punctuation">&#125;</span></span><br></pre></td></tr></table></figure><p>四个核心字段各管一层：</p><table><thead><tr><th>字段</th><th>作用</th></tr></thead><tbody><tr><td><code>framework</code></td><td>让 Vercel 按 Hexo 项目理解仓库</td></tr><tr><td><code>installCommand</code></td><td>严格按锁文件安装依赖</td></tr><tr><td><code>buildCommand</code></td><td>运行 <code>hexo generate</code></td></tr><tr><td><code>outputDirectory</code></td><td>只发布 <code>public/</code>，不把源码端上桌</td></tr></tbody></table><p>控制台的 Project Inspect 可能显示框架默认命令，但仓库中的显式配置会进入实际构建。判断最终执行了什么要看部署日志；本站生产日志确实运行了 <code>pnpm install --frozen-lockfile</code> 与 <code>pnpm run build</code>。</p><h3 id="缓存头为什么这样写">缓存头为什么这样写</h3><p><code>max-age=0</code> 要求浏览器重新验证，<code>s-maxage=300</code> 允许共享边缘缓存保留 300 秒，<code>must-revalidate</code> 避免过期内容被随意继续使用。它适合更新频率不高的静态博客，同时降低长时间卡住旧 HTML 的概率。</p><p>一次发布的体感时间可以近似拆成：</p><p><span class="katex-display"><span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML" display="block"><semantics><mrow><msub><mi>T</mi><mrow><mi>v</mi><mi>i</mi><mi>s</mi><mi>i</mi><mi>b</mi><mi>l</mi><mi>e</mi></mrow></msub><mo>≈</mo><msub><mi>T</mi><mrow><mi>i</mi><mi>n</mi><mi>s</mi><mi>t</mi><mi>a</mi><mi>l</mi><mi>l</mi></mrow></msub><mo>+</mo><msub><mi>T</mi><mrow><mi>g</mi><mi>e</mi><mi>n</mi><mi>e</mi><mi>r</mi><mi>a</mi><mi>t</mi><mi>e</mi></mrow></msub><mo>+</mo><msub><mi>T</mi><mrow><mi>u</mi><mi>p</mi><mi>l</mi><mi>o</mi><mi>a</mi><mi>d</mi></mrow></msub><mo>+</mo><msub><mi>T</mi><mrow><mi>a</mi><mi>l</mi><mi>i</mi><mi>a</mi><mi>s</mi></mrow></msub></mrow><annotation encoding="application/x-tex">T_{visible} \approx T_{install} + T_{generate} + T_{upload} + T_{alias}</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="katex-base"><span class="katex-strut" style="height:0.8333em;vertical-align:-0.15em;"></span><span class="mord"><span class="mord mathnormal" style="margin-right:0.1389em;">T</span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.3361em;"><span style="top:-2.55em;margin-left:-0.1389em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="katex-sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord mathnormal mtight" style="margin-right:0.0359em;">v</span><span class="mord mathnormal mtight">i</span><span class="mord mathnormal mtight">s</span><span class="mord mathnormal mtight">ib</span><span class="mord mathnormal mtight" style="margin-right:0.0197em;">l</span><span class="mord mathnormal mtight">e</span></span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.15em;"><span></span></span></span></span></span></span><span class="mspace" style="margin-right:0.2778em;"></span><span class="mrel">≈</span><span class="mspace" style="margin-right:0.2778em;"></span></span><span class="katex-base"><span class="katex-strut" style="height:0.8333em;vertical-align:-0.15em;"></span><span class="mord"><span class="mord mathnormal" style="margin-right:0.1389em;">T</span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.3361em;"><span style="top:-2.55em;margin-left:-0.1389em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="katex-sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord mathnormal mtight">in</span><span class="mord mathnormal mtight">s</span><span class="mord mathnormal mtight">t</span><span class="mord mathnormal mtight">a</span><span class="mord mathnormal mtight" style="margin-right:0.0197em;">l</span><span class="mord mathnormal mtight" style="margin-right:0.0197em;">l</span></span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.15em;"><span></span></span></span></span></span></span><span class="mspace" style="margin-right:0.2222em;"></span><span class="mbin">+</span><span class="mspace" style="margin-right:0.2222em;"></span></span><span class="katex-base"><span class="katex-strut" style="height:0.9694em;vertical-align:-0.2861em;"></span><span class="mord"><span class="mord mathnormal" style="margin-right:0.1389em;">T</span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.2806em;"><span style="top:-2.55em;margin-left:-0.1389em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="katex-sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord mathnormal mtight" style="margin-right:0.0359em;">g</span><span class="mord mathnormal mtight">e</span><span class="mord mathnormal mtight">n</span><span class="mord mathnormal mtight" style="margin-right:0.0278em;">er</span><span class="mord mathnormal mtight">a</span><span class="mord mathnormal mtight">t</span><span class="mord mathnormal mtight">e</span></span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.2861em;"><span></span></span></span></span></span></span><span class="mspace" style="margin-right:0.2222em;"></span><span class="mbin">+</span><span class="mspace" style="margin-right:0.2222em;"></span></span><span class="katex-base"><span class="katex-strut" style="height:0.9694em;vertical-align:-0.2861em;"></span><span class="mord"><span class="mord mathnormal" style="margin-right:0.1389em;">T</span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.3361em;"><span style="top:-2.55em;margin-left:-0.1389em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="katex-sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord mathnormal mtight">u</span><span class="mord mathnormal mtight" style="margin-right:0.0197em;">pl</span><span class="mord mathnormal mtight">o</span><span class="mord mathnormal mtight">a</span><span class="mord mathnormal mtight">d</span></span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.2861em;"><span></span></span></span></span></span></span><span class="mspace" style="margin-right:0.2222em;"></span><span class="mbin">+</span><span class="mspace" style="margin-right:0.2222em;"></span></span><span class="katex-base"><span class="katex-strut" style="height:0.8333em;vertical-align:-0.15em;"></span><span class="mord"><span class="mord mathnormal" style="margin-right:0.1389em;">T</span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.3361em;"><span style="top:-2.55em;margin-left:-0.1389em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="katex-sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord mathnormal mtight">a</span><span class="mord mathnormal mtight" style="margin-right:0.0197em;">l</span><span class="mord mathnormal mtight">ia</span><span class="mord mathnormal mtight">s</span></span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.15em;"><span></span></span></span></span></span></span></span></span></span></span></p><p>边缘缓存与 DNS 状态会影响个别访问，但 Vercel 的部署本身是独立产物，Production 就绪后再把域名别名切过去。不要在构建还红着时把“CDN 有缓存”当作电子护身符。</p><h2 id="在-Vercel-导入-GitHub-仓库">在 Vercel 导入 GitHub 仓库</h2><h3 id="1-导入仓库">1. 导入仓库</h3><p>进入 Vercel Dashboard，选择 <strong>Add New → Project</strong>，授权 GitHub 后导入 <code>luomo66ccff/hexo</code>。本站控制面当前确认的绑定关系是：</p><table><thead><tr><th>设置</th><th>值</th></tr></thead><tbody><tr><td>Git Provider</td><td>GitHub</td></tr><tr><td>Repository</td><td><code>luomo66ccff/hexo</code></td></tr><tr><td>Production Branch</td><td><code>main</code></td></tr><tr><td>Root Directory</td><td><code>.</code></td></tr><tr><td>Framework Preset</td><td>Hexo</td></tr><tr><td>Node.js Version</td><td>建议 <code>24.x</code></td></tr></tbody></table><p>因为仓库已有 <code>vercel.json</code>，不必在控制台重复维护三套命令。若控制台有旧的手工覆盖值，要么清空让仓库配置接管，要么确保两边完全一致；最怕一边改成 pnpm，另一边还在召唤 npm，最后构建日志现场举行包管理器武林大会。</p><h3 id="2-理解-Preview-与-Production">2. 理解 Preview 与 Production</h3><p>Vercel 的 Git 集成会为非生产分支和 Pull Request 创建 Preview，<code>main</code> 则跟踪 Production。推荐流程是：</p><div class="timeline blue"><div class='timeline-item headline'>        <div class='timeline-item-title'>          <div class='item-circle'><p>一次正常发布</p></div>        </div>      </div><div class='timeline-item'>        <div class='timeline-item-title'>          <div class='item-circle'><ol><li>本地写作</li></ol></div>        </div>        <div class='timeline-item-content'><p>在 <code>source/_posts/</code> 新建文章，填写 Front-matter，并只提交真正需要的素材。</p></div>      </div><div class='timeline-item'>        <div class='timeline-item-title'>          <div class='item-circle'><ol start="2"><li>干净构建</li></ol></div>        </div>        <div class='timeline-item-content'><p>执行 <code>pnpm run clean &amp;&amp; pnpm run build</code>，确认新页面、搜索、Feed 和 Sitemap 都生成。</p></div>      </div><div class='timeline-item'>        <div class='timeline-item-title'>          <div class='item-circle'><ol start="3"><li>本地视觉验收</li></ol></div>        </div>        <div class='timeline-item-content'><p>运行 <code>pnpm run server</code>，分别检查桌面端和手机端，关注白色主题、代码块、表格、Mermaid 与横向溢出。</p></div>      </div><div class='timeline-item'>        <div class='timeline-item-title'>          <div class='item-circle'><ol start="4"><li>Git 提交</li></ol></div>        </div>        <div class='timeline-item-content'><p>明确审查 <code>git status</code> 和差异，只提交本篇文章及关联资源。</p></div>      </div><div class='timeline-item'>        <div class='timeline-item-title'>          <div class='item-circle'><ol start="5"><li>Preview 或 Production</li></ol></div>        </div>        <div class='timeline-item-content'><p>功能分支推送后先看 Preview；确认无误再合并 <code>main</code>。个人站也可在完整本地 QA 后直接推送 <code>main</code>。</p></div>      </div><div class='timeline-item'>        <div class='timeline-item-title'>          <div class='item-circle'><ol start="6"><li>控制面确认</li></ol></div>        </div>        <div class='timeline-item-content'><p>等待 Vercel 状态变为 Ready，核对提交哈希、生成文件、生产域名和错误日志。</p></div>      </div></div><h2 id="本地发布命令">本地发布命令</h2><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">pnpm install --frozen-lockfile</span><br><span class="line">pnpm run clean</span><br><span class="line">pnpm run build</span><br><span class="line">pnpm run server</span><br></pre></td></tr></table></figure><p>浏览器检查完成后：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">git status --short</span><br><span class="line">git diff --check</span><br><span class="line">git add <span class="built_in">source</span>/_posts/your-post.md</span><br><span class="line">git commit -m <span class="string">&quot;docs: 发布 Hexo 部署教程&quot;</span></span><br><span class="line">git push origin main</span><br></pre></td></tr></table></figure><p>推送 <code>main</code> 后，Git 集成会自动创建 Production 部署。<a href="https://vercel.com/docs/git">Vercel Git 部署文档</a>也说明非生产分支和 Pull Request 会获得独立 Preview，生产分支则生成 Production。</p><h2 id="CLI-发布与排障备用路线">CLI 发布与排障备用路线</h2><p>Git 自动部署是日常主路，CLI 是手动发布、检查和回滚的备用通道：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line">npx vercel login</span><br><span class="line">npx vercel <span class="built_in">link</span></span><br><span class="line">npx vercel project inspect hexo</span><br><span class="line">npx vercel list hexo --status READY</span><br><span class="line"></span><br><span class="line"><span class="comment"># Preview</span></span><br><span class="line">npx vercel deploy</span><br><span class="line"></span><br><span class="line"><span class="comment"># 明确发布 Production</span></span><br><span class="line">npx vercel deploy --prod</span><br></pre></td></tr></table></figure><p><code>vercel link</code> 会创建 <code>.vercel/project.json</code>，其中是项目与组织标识，不是部署源码；本站将整个 <code>.vercel/</code> 加入 <code>.gitignore</code>。自动化环境中的令牌必须放在 Secret 或环境变量中，禁止写进命令历史和仓库。</p><div class="tabs"><div class="nav-tabs"><button type="button" class="tab active"><i class="fas fa-magnifying-glass"></i>检查部署</button><button type="button" class="tab"><i class="fas fa-rotate-left"></i>回滚</button><button type="button" class="tab"><i class="fas fa-box"></i>预构建发布</button></div><div class="tab-contents"><div class="tab-item-content active"><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">npx vercel inspect &lt;deployment-url&gt;</span><br><span class="line">npx vercel logs &lt;deployment-url&gt; --level error --since 1h</span><br></pre></td></tr></table></figure><p>确认目标为 Production、状态为 Ready、提交 SHA 正确，并查看构建或运行错误。</p></div><div class="tab-item-content"><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">npx vercel rollback</span><br><span class="line"><span class="comment"># 或指定一份已知正常的部署</span></span><br><span class="line">npx vercel rollback &lt;deployment-url-or-id&gt;</span><br></pre></td></tr></table></figure><p>静态博客没有数据库迁移，回滚通常就是把生产域名重新指向上一份完整静态产物。</p></div><div class="tab-item-content"><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">npx vercel pull --<span class="built_in">yes</span> --environment=production</span><br><span class="line">npx vercel build --prod</span><br><span class="line">npx vercel deploy --prebuilt --prod</span><br></pre></td></tr></table></figure><p>适合需要在构建与发布之间插入额外测试门禁的 CI；普通个人博客无需为了显得专业把流水线堆成跨海大桥。</p></div></div><div class="tab-to-top"><button type="button" aria-label="scroll to top"><i class="fas fa-arrow-up"></i></button></div></div><h2 id="绑定-blog-luomo-moe">绑定 <code>blog.luomo.moe</code></h2><p>在 Vercel 项目的 <strong>Settings → Domains</strong> 添加 <code>blog.luomo.moe</code>。这是子域名，通常需要在 DNS 服务商处添加 Vercel 当时显示的 CNAME 目标。</p><p>不要从旧教程里复制某个固定 CNAME 值。<a href="https://vercel.com/docs/domains/working-with-domains/add-a-domain">Vercel 自定义域名文档</a>要求以项目面板显示的 DNS 目标为准；域名被其他账号占用时还可能要求 TXT 验证。DNS 验证成功后，Vercel 会自动申请 HTTPS 证书。</p><p>站点配置也必须同步：</p><figure class="highlight yaml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># _config.yml</span></span><br><span class="line"><span class="attr">url:</span> <span class="string">https://blog.luomo.moe</span></span><br></pre></td></tr></table></figure><p>还要检查 <code>source/robots.txt</code>、Feed、Sitemap、Open Graph、评论系统白名单和第三方回调地址。只改浏览器地址栏能访问，不代表整站的机器读者已经跟着搬家。</p><h2 id="四次真实踩坑复盘">四次真实踩坑复盘</h2><h3 id="坑一：Vercel-识别-pnpm-工作区不稳定">坑一：Vercel 识别 pnpm 工作区不稳定</h3><p>早期仓库只有 <code>allowBuilds</code>，没有 <code>packages</code>。加入下面两行后，仓库根被明确识别为工作区包：</p><figure class="highlight yaml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="attr">packages:</span></span><br><span class="line">  <span class="bullet">-</span> <span class="string">.</span></span><br></pre></td></tr></table></figure><p>同时将 <code>.vercel/</code> 和 <code>.env.local</code> 排除提交。修复重点不是“多写一份 YAML”，而是让本地 pnpm、Vercel 安装阶段和项目根目录对同一边界达成一致。</p><h3 id="坑二：页面黑屏和文章排版错位">坑二：页面黑屏和文章排版错位</h3><p>这次故障由多件小事叠加：</p><ol><li>自定义 CSS 使用了 <code>.article-container</code>，而实际正文节点是 <code>#article-container</code>；</li><li>全屏预加载器让异常加载表现成整页遮罩；</li><li>文章头图与全站图片懒加载规则发生重复处理；</li><li>表格在手机端没有自己的横向滚动容器；</li><li>Note 标签参数与当前 Butterfly 版本语法不一致。</li></ol><p>修复后关闭全屏预加载器、改正选择器、让带 <code>nolazyload</code> 的图片绕过过滤器，并为表格添加移动端滚动。<!-- spoiler-1ffd9:black --><span class="spoiler" onclick="this.classList.toggle('spoiler')"><span class="spoiler-blur spoiler-1ffd9">“把背景改白”只能遮住部分症状，真正的黑屏还可能来自覆盖层、缓存和资源加载。</span></span></p><h3 id="坑三：主题配置是-light，视觉却仍然很黑">坑三：主题配置是 light，视觉却仍然很黑</h3><p><code>display_mode: light</code> 只决定 Butterfly 默认模式，旧自定义 CSS 仍可用深色变量把整站重新涂黑。因此白色主题迁移同时修改了：</p><ul><li>页面背景与玻璃卡片变量；</li><li>导航、正文、标题和页脚文字色；</li><li>代码块、表格、引用与 Note；</li><li>前后文章导航；</li><li>移动端正文内边距与表格滚动；</li><li>禁用自动暗色模式与暗色切换按钮。</li></ul><p>验证主题不能只检查 <code>data-theme=&quot;light&quot;</code>，还要读取最终计算样式，并在真实尺寸截图中确认正文背景和文字对比度。</p><h3 id="坑四：插件删了，旧-Service-Worker-还活着">坑四：插件删了，旧 Service Worker 还活着</h3><p>移除 <code>hexo-offline</code> 只会阻止新构建继续生成缓存逻辑，已经注册在访客浏览器里的 Service Worker 不会自动原地圆寂。它仍可能把旧 HTML、CSS 和脚本塞回来，让生产站出现“代码已经改了，浏览器坚持活在前朝”的现象。</p><p>本站使用一次性迁移 Worker：安装后接管页面、删除旧 Cache Storage、注销自身，再让页面重新导航。<code>vercel.json</code> 对 <code>/service-worker.js</code> 单独发送 <code>no-store, no-cache, must-revalidate</code>，确保浏览器能够拿到退役脚本，而不是继续缓存旧 Worker。</p><div class="note danger flat"><p>不要随意把 Service Worker 文件直接删掉后就宣布胜利。要清理已经安装的旧 Worker，需要先在同一作用域发布能接管并注销自己的版本，等待足够的迁移窗口后再移除。</p></div><h2 id="构建产物应该验收什么">构建产物应该验收什么</h2><p>一次干净构建不是只看退出码为 0。本文写作前的基线构建生成 69 个文件，其中 30 个为 HTML；新增文章后还要检查以下内容：</p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">public/posts/&lt;abbrlink&gt;.html     新文章页面</span><br><span class="line">public/index.html                首页收录</span><br><span class="line">public/search.json               本地搜索索引</span><br><span class="line">public/atom.xml                  Atom Feed</span><br><span class="line">public/sitemap.xml               通用站点地图</span><br><span class="line">public/baidusitemap.xml          百度站点地图</span><br><span class="line">public/baidu_urls.txt            最新链接列表</span><br></pre></td></tr></table></figure><p><code>hexo-baidu-url-submit</code> 在公开仓库中只保留生成配置，真实 Token 必须通过私有配置或环境变量提供。看到 <code>baidu_urls.txt</code> 生成，不等于已经向百度成功提交；二者不能用一个绿色日志互相冒充。</p><h2 id="桌面端与手机端视觉-QA">桌面端与手机端视觉 QA</h2><details class="toggle" style="border: 1px solid #425b89"><summary class="toggle-button" style="background-color: #425b89;color: #ffffff">展开发布前检查表</summary><div class="toggle-content"><ol><li>页面主题为 light，正文卡片计算背景是白色或接近白色；</li><li>页面 <code>scrollWidth</code> 不大于视口宽度，手机底部没有横向滚动条；</li><li>Mermaid 已从源码块转换为 SVG，而不是原样露出文本；</li><li>KaTeX 公式、Ruby、Hint、Spoiler、Tabs、Timeline 与折叠块均成功渲染；</li><li>代码块可读、长命令可换行或局部滚动；</li><li>表格只在自身容器滚动，不把整页撑宽；</li><li>图片无 404，头图没有被懒加载重复改写；</li><li>页面中不存在未解析的 Hexo 标签、模板空值或渲染错误；</li><li>搜索、Feed 和 Sitemap 包含新文章；</li><li>浏览器控制台没有新的 JavaScript 错误。</li></ol></div></details><h2 id="生产发布后的判断顺序">生产发布后的判断顺序</h2><ol><li>Vercel 部署是否从 Building 进入 Ready；</li><li>部署关联的 Git 提交 SHA 是否与远端 <code>main</code> 一致；</li><li>构建日志是否生成目标文章；</li><li>Production 域名是否已经指向该部署；</li><li>若页面异常，先区分构建失败、域名未切换、边缘缓存和浏览器 Service Worker；</li><li>问题影响全站时先回滚，再慢慢写事故文学。</li></ol><p>Vercel 每次部署都有独立 URL。Preview 没问题但正式域名异常时，不要立刻重写 Hexo；先检查 Production alias、DNS 与缓存层，避免对着正确代码进行错误手术。</p><h2 id="还需要继续收紧的地方">还需要继续收紧的地方</h2><table><thead><tr><th>项目</th><th>当前状态</th><th>后续动作</th></tr></thead><tbody><tr><td>Node.js</td><td>Vercel 仍为 20.x</td><td>在 2026-10-01 前升级 24.x并回归</td></tr><tr><td>pnpm</td><td>本地声明、实际本地和 Vercel 版本不完全一致</td><td>统一 Corepack 与 packageManager 策略</td></tr><tr><td>Butterfly</td><td>主题源码入库且有少量内部补丁</td><td>升级前逐文件合并，不整目录覆盖</td></tr><tr><td>Service Worker</td><td>正在承担一次性旧缓存退役</td><td>经过足够迁移窗口后评估移除</td></tr><tr><td>外部凭据</td><td>公开配置中不含真实 Token</td><td>继续放在 Vercel 环境变量或私有配置</td></tr><tr><td>SEO 域名</td><td>多份生成物依赖 <code>url</code> 与 robots</td><td>域名变更时做全仓扫描和产物验收</td></tr></tbody></table><h2 id="最后的复盘">最后的复盘</h2><p>这套博客的部署价值不在于“Vercel 点一下就上线”，而在于把内容、渲染、主题、生成、版本控制和托管边界拆得足够清楚。Markdown 是源，<code>public/</code> 是产物，GitHub 是版本事实，Vercel Deployment 是不可变发布单元，自定义域名只负责把访客带到已经通过验证的生产版本。</p><p>真正让站点稳定的也不是某个神奇插件，而是锁文件、干净构建、Preview、移动端 QA、缓存认知和可回滚发布。把这些步骤做实，写完文章推一次 <code>main</code> 就能优雅上线；把它们跳过，三分钟部署也可能附赠三小时“为什么我这还是黑的”。</p><a class="btn-beautify blue center larger" href="https://github.com/luomo66ccff/hexo" title="从真实仓库开始复现"><i class="fab fa-github"></i><span>从真实仓库开始复现</span></a>]]>
    </content>
    <id>https://blog.luomo.moe/posts/3d61fa8b.html</id>
    <link href="https://blog.luomo.moe/posts/3d61fa8b.html"/>
    <published>2026-08-08T11:45:00.000Z</published>
    <summary>以 Luomoの云日常的真实仓库和生产配置为样本，完整拆解 Hexo、Butterfly、插件、GitHub、Vercel、自定义域名、缓存、回滚，以及黑屏和旧 Service Worker 等实际故障的处理路径。</summary>
    <title>从 Markdown 到全球 CDN：我的 Hexo 博客 GitHub + Vercel 部署全流程</title>
    <updated>2026-08-08T11:45:00.000Z</updated>
  </entry>
  <entry>
    <author>
      <name>洛墨-天染-依然</name>
    </author>
    <category term="技术实践" scheme="https://blog.luomo.moe/categories/%E6%8A%80%E6%9C%AF%E5%AE%9E%E8%B7%B5/"/>
    <category term="Vercel" scheme="https://blog.luomo.moe/tags/Vercel/"/>
    <category term="Serverless" scheme="https://blog.luomo.moe/tags/Serverless/"/>
    <category term="Node.js" scheme="https://blog.luomo.moe/tags/Node-js/"/>
    <category term="Hono" scheme="https://blog.luomo.moe/tags/Hono/"/>
    <category term="API" scheme="https://blog.luomo.moe/tags/API/"/>
    <category term="GitHub" scheme="https://blog.luomo.moe/tags/GitHub/"/>
    <content>
      <![CDATA[<link rel="stylesheet" class="aplayer-secondary-style-marker" href="/assets/css/APlayer.min.css"><script src="/assets/js/APlayer.min.js" class="aplayer-secondary-script-marker"></script><script class="meting-secondary-script-marker" src="/assets/js/Meting.min.js"></script><link rel="stylesheet" type="text&#x2F;css" href="/pluginsSrc/hexo-tag-hint/dist/hexo-tag-hint.min.css"><p>这次我选择拆解 <ruby>HotAPI<rt>热榜接口部署仓</rt></ruby>。它不是我原创的一整套爬虫内核，而是我在 GitHub 上维护、交给 Vercel 托管的部署适配项目：上游核心来自 <a href="https://github.com/imsyy/DailyHotApi">imsyy/DailyHotApi</a>，遵循 MIT 协议；我的工作重点是把 npm 包固定成可复现依赖，再用极薄的 Node.js 入口与 <code>vercel.json</code> 把它变成公开 Serverless API。</p><mark class="hl-label blue">GitHub部署仓</mark> <mark class="hl-label black">Vercel函数</mark> <mark class="hl-label green">JSON与RSS</mark> <mark class="hl-label purple">上游MIT</mark><a class="btn-beautify orange larger" href="https://hotapi-ten.vercel.app" title="打开 HotAPI"><i class="fas fa-fire"></i><span>打开 HotAPI</span></a><a class="btn-beautify purple larger" href="https://github.com/luomo66ccff/hotapi" title="查看 GitHub 部署仓"><i class="fab fa-github"></i><span>查看 GitHub 部署仓</span></a><a class="btn-beautify blue larger" href="https://github.com/imsyy/DailyHotApi" title="查看上游核心"><i class="fab fa-github"></i><span>查看上游核心</span></a><div class="note warning flat"><p>先把署名边界钉牢：本文讲的是“如何部署、约束和维护上游 API”，不是把上游作者 imsyy 的采集器改姓。薄适配层同样有工程价值，但它的价值来自可复现部署、运行时边界和运维策略，而不是抢走核心实现的作者席位。</p></div><span id="more"></span><h2 id="为什么一个三行入口仍然值得写">为什么一个三行入口仍然值得写</h2><p>项目入口只有三行：</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> serveHotApi <span class="keyword">from</span> <span class="string">&quot;dailyhot-api&quot;</span>;</span><br><span class="line"></span><br><span class="line"><span class="title function_">serveHotApi</span>();</span><br></pre></td></tr></table></figure><p>三行不等于没有架构。它把职责切得很清楚：上游包负责 Hono 路由、数据抓取、格式归一化、JSON/RSS 输出和缓存；部署仓负责版本锁定、ESM 入口、Vercel Function 构建、全路径转发与 GitHub 更新入口。<span class="hint--info hint--rounded hint--top" data-hint="业务代码很少版本和运行时要固定路由与发布边界要明确" ontouchstart>薄部署仓</span> 的目标正是少写重复代码，而不是少做验证。</p><p>截至本文审计时，Vercel 项目 <code>hotapi</code> 的生产部署状态为 <code>Ready</code>，构建结果是一枚位于 <code>iad1</code> 的 Node.js Function，解包前显示约 <code>939.08 KB</code>。GitHub 仓库的锁文件固定了 <code>dailyhot-api 2.0.6</code>；上游已经发布 <code>2.0.8</code>，所以“生产正在跑什么”和“上游最新是什么”必须分开记录。</p><h2 id="仓库虽小，边界要完整">仓库虽小，边界要完整</h2><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line">hotapi/</span><br><span class="line">├─ index.js                 # Serverless 入口</span><br><span class="line">├─ package.json             # ESM、依赖与脚本</span><br><span class="line">├─ package-lock.json        # 可复现安装契约</span><br><span class="line">├─ vercel.json              # Function 构建与全路径路由</span><br><span class="line">├─ public/                  # 图标等静态资源</span><br><span class="line">└─ .github/</span><br><span class="line">   └─ dependabot.yml        # 每日检查 npm 版本更新</span><br></pre></td></tr></table></figure><p><code>package.json</code> 中有三个不能随手删的关键点：</p><table><thead><tr><th>配置</th><th>当前值</th><th>作用</th></tr></thead><tbody><tr><td><code>type</code></td><td><code>module</code></td><td>让 <code>index.js</code> 使用原生 ESM <code>import</code></td></tr><tr><td><code>dependencies</code></td><td><code>dailyhot-api: ^2.0.6</code></td><td>引入上游运行时；实际部署版本由锁文件决定</td></tr><tr><td><code>devDependencies</code></td><td><code>@vercel/node</code></td><td>把入口构建成 Vercel Node.js Function</td></tr></tbody></table><p>Vercel 官方文档也明确：无框架 JavaScript Function 若使用 ESM，需要在 <code>package.json</code> 设置 <code>&quot;type&quot;: &quot;module&quot;</code>，或者改用 <code>.mjs</code>。这里选择前者，入口文件保持普通 <code>.js</code> 即可。</p><h2 id="请求是怎样穿过这三层的">请求是怎样穿过这三层的</h2><div class="mermaid-wrap"><pre class="mermaid-src" data-config="{}" hidden>    flowchart LR  U[&quot;浏览器 &#x2F; RSS 阅读器 &#x2F; 脚本&quot;] --&gt; V[&quot;Vercel 路由层&quot;]  V --&gt; F[&quot;Node.js Function\nindex.js&quot;]  F --&gt; H[&quot;dailyhot-api\nHono 应用&quot;]  H --&gt; R[&quot;动态路由注册表&quot;]  R --&gt; S[&quot;公开数据源 &#x2F; RSS &#x2F; 页面&quot;]  R --&gt; C[&quot;NodeCache\n实例内缓存&quot;]  H --&gt; J[&quot;JSON 响应&quot;]  H --&gt; X[&quot;RSS XML 响应&quot;]  </pre></div><div class="timeline orange"><div class='timeline-item headline'>        <div class='timeline-item-title'>          <div class='item-circle'><p>一次热榜请求的生命史</p></div>        </div>      </div><div class='timeline-item'>        <div class='timeline-item-title'>          <div class='item-circle'><ol><li>Vercel 接收路径</li></ol></div>        </div>        <div class='timeline-item-content'><p><code>/bilibili?limit=10</code> 先命中部署仓的 catch-all 路由，再交给 <code>index.js</code> 对应的 Node.js Function。</p></div>      </div><div class='timeline-item'>        <div class='timeline-item-title'>          <div class='item-circle'><ol start="2"><li>Hono 匹配动态路由</li></ol></div>        </div>        <div class='timeline-item-content'><p>上游在启动时扫描 <code>routes/</code>。2.0.6 标签中共有 42 个路由文件，其中 40 个可用，<code>52pojie</code> 与 <code>hostloc</code> 被显式排除。</p></div>      </div><div class='timeline-item'>        <div class='timeline-item-title'>          <div class='item-circle'><ol start="3"><li>尝试读取缓存</li></ol></div>        </div>        <div class='timeline-item-content'><p>默认 TTL 为 3600 秒。命中当前 Function 实例的 NodeCache 就直接返回；<code>cache=false</code> 会主动绕过缓存。</p></div>      </div><div class='timeline-item'>        <div class='timeline-item-title'>          <div class='item-circle'><ol start="4"><li>获取并标准化数据</li></ol></div>        </div>        <div class='timeline-item-content'><p>未命中缓存时，具体路由向公开数据源请求内容，再统一成标题、链接、热度、更新时间和列表项等字段。</p></div>      </div><div class='timeline-item'>        <div class='timeline-item-title'>          <div class='item-circle'><ol start="5"><li>决定输出格式</li></ol></div>        </div>        <div class='timeline-item-content'><p>默认返回 JSON；带 <code>rss=true</code> 时生成 RSS XML。<code>limit</code> 参数在响应前裁剪条目数。</p></div>      </div></div><h2 id="动态路由为什么比手写-40-次更稳">动态路由为什么比手写 40 次更稳</h2><p>上游的注册器扫描路由目录，为每个文件创建 <code>/&lt;route&gt;</code> 入口，并在请求时动态导入对应实现。逻辑可以压缩成下面这段伪代码：</p><figure class="highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">for</span> (<span class="keyword">const</span> route <span class="keyword">of</span> routeFiles) &#123;</span><br><span class="line">  app.<span class="title function_">get</span>(<span class="string">`/<span class="subst">$&#123;route&#125;</span>`</span>, <span class="title function_">async</span> (context) =&gt; &#123;</span><br><span class="line">    <span class="keyword">const</span> noCache = context.<span class="property">req</span>.<span class="title function_">query</span>(<span class="string">&quot;cache&quot;</span>) === <span class="string">&quot;false&quot;</span>;</span><br><span class="line">    <span class="keyword">const</span> limit = <span class="title class_">Number</span>(context.<span class="property">req</span>.<span class="title function_">query</span>(<span class="string">&quot;limit&quot;</span>));</span><br><span class="line">    <span class="keyword">const</span> rss = context.<span class="property">req</span>.<span class="title function_">query</span>(<span class="string">&quot;rss&quot;</span>) === <span class="string">&quot;true&quot;</span>;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">const</span> &#123; handleRoute &#125; = <span class="keyword">await</span> <span class="keyword">import</span>(<span class="string">`./routes/<span class="subst">$&#123;route&#125;</span>.js`</span>);</span><br><span class="line">    <span class="keyword">const</span> result = <span class="keyword">await</span> <span class="title function_">handleRoute</span>(context, noCache);</span><br><span class="line">    <span class="keyword">return</span> rss ? <span class="title function_">toRss</span>(result) : context.<span class="title function_">json</span>(<span class="title function_">trim</span>(result, limit));</span><br><span class="line">  &#125;);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>这样新增一个榜单通常只需新增一个路由文件，不必再修改总入口。<code>/all</code> 还能返回路由目录，前端或监控脚本不需要把列表硬编码到自己的版本里。</p><p>常用请求如下：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># 查看可用接口</span></span><br><span class="line">curl --fail https://hotapi-ten.vercel.app/all</span><br><span class="line"></span><br><span class="line"><span class="comment"># 只取前 10 条</span></span><br><span class="line">curl --fail <span class="string">&quot;https://hotapi-ten.vercel.app/bilibili?limit=10&quot;</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># 输出 RSS</span></span><br><span class="line">curl --fail <span class="string">&quot;https://hotapi-ten.vercel.app/zhihu?rss=true&quot;</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># 绕过缓存，仅用于排障或人工刷新</span></span><br><span class="line">curl --fail <span class="string">&quot;https://hotapi-ten.vercel.app/36kr?cache=false&quot;</span></span><br></pre></td></tr></table></figure><div class="note danger flat"><p>不要把 <code>cache=false</code> 塞进公开前端的默认请求。那等于让每位访客都拿着小锤子敲上游站点，缓存当场表演“我装了，但没完全装”。</p></div><h2 id="缓存：Serverless-最容易被误解的一层">缓存：Serverless 最容易被误解的一层</h2><p>上游 2.0.6 默认使用 <code>NodeCache</code>，TTL 是 3600 秒。单进程常驻服务器上，理想情况下同一路由每小时只回源一次；但 Vercel 会冷启动、并发扩容，也可能同时存在多个 Function 实例。各实例内存不共享，因此更准确的近似是：</p><p><span class="katex-display"><span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML" display="block"><semantics><mrow><msub><mi>R</mi><mrow><mi>o</mi><mi>r</mi><mi>i</mi><mi>g</mi><mi>i</mi><mi>n</mi></mrow></msub><mo>≈</mo><mfrac><mrow><msub><mi>I</mi><mrow><mi>w</mi><mi>a</mi><mi>r</mi><mi>m</mi></mrow></msub><mo>+</mo><msub><mi>I</mi><mrow><mi>c</mi><mi>o</mi><mi>l</mi><mi>d</mi></mrow></msub></mrow><msub><mi>T</mi><mrow><mi>T</mi><mi>T</mi><mi>L</mi></mrow></msub></mfrac></mrow><annotation encoding="application/x-tex">R_{origin} \approx \frac{I_{warm} + I_{cold}}{T_{TTL}}</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="katex-base"><span class="katex-strut" style="height:0.9694em;vertical-align:-0.2861em;"></span><span class="mord"><span class="mord mathnormal" style="margin-right:0.0077em;">R</span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.3117em;"><span style="top:-2.55em;margin-left:-0.0077em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="katex-sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord mathnormal mtight" style="margin-right:0.0278em;">or</span><span class="mord mathnormal mtight">i</span><span class="mord mathnormal mtight" style="margin-right:0.0359em;">g</span><span class="mord mathnormal mtight">in</span></span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.2861em;"><span></span></span></span></span></span></span><span class="mspace" style="margin-right:0.2778em;"></span><span class="mrel">≈</span><span class="mspace" style="margin-right:0.2778em;"></span></span><span class="katex-base"><span class="katex-strut" style="height:2.1963em;vertical-align:-0.836em;"></span><span class="mord"><span class="mopen nulldelimiter"></span><span class="mfrac"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:1.3603em;"><span style="top:-2.314em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord"><span class="mord mathnormal" style="margin-right:0.1389em;">T</span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.3283em;"><span style="top:-2.55em;margin-left:-0.1389em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="katex-sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord mathnormal mtight" style="margin-right:0.1389em;">T</span><span class="mord mathnormal mtight" style="margin-right:0.1389em;">T</span><span class="mord mathnormal mtight">L</span></span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.15em;"><span></span></span></span></span></span></span></span></span><span style="top:-3.23em;"><span class="pstrut" style="height:3em;"></span><span class="frac-line" style="border-bottom-width:0.04em;"></span></span><span style="top:-3.677em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord"><span class="mord mathnormal" style="margin-right:0.0785em;">I</span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.1514em;"><span style="top:-2.55em;margin-left:-0.0785em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="katex-sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord mathnormal mtight" style="margin-right:0.0269em;">w</span><span class="mord mathnormal mtight">a</span><span class="mord mathnormal mtight" style="margin-right:0.0278em;">r</span><span class="mord mathnormal mtight">m</span></span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.15em;"><span></span></span></span></span></span></span><span class="mspace" style="margin-right:0.2222em;"></span><span class="mbin">+</span><span class="mspace" style="margin-right:0.2222em;"></span><span class="mord"><span class="mord mathnormal" style="margin-right:0.0785em;">I</span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.3361em;"><span style="top:-2.55em;margin-left:-0.0785em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="katex-sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord mathnormal mtight">co</span><span class="mord mathnormal mtight" style="margin-right:0.0197em;">l</span><span class="mord mathnormal mtight">d</span></span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.15em;"><span></span></span></span></span></span></span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.836em;"><span></span></span></span></span></span><span class="mclose nulldelimiter"></span></span></span></span></span></span></p><p>其中 <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><msub><mi>I</mi><mrow><mi>w</mi><mi>a</mi><mi>r</mi><mi>m</mi></mrow></msub></mrow><annotation encoding="application/x-tex">I_{warm}</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="katex-base"><span class="katex-strut" style="height:0.8333em;vertical-align:-0.15em;"></span><span class="mord"><span class="mord mathnormal" style="margin-right:0.0785em;">I</span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.1514em;"><span style="top:-2.55em;margin-left:-0.0785em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="katex-sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord mathnormal mtight" style="margin-right:0.0269em;">w</span><span class="mord mathnormal mtight">a</span><span class="mord mathnormal mtight" style="margin-right:0.0278em;">r</span><span class="mord mathnormal mtight">m</span></span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.15em;"><span></span></span></span></span></span></span></span></span></span> 是窗口内实际承接请求的暖实例数，<span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><msub><mi>I</mi><mrow><mi>c</mi><mi>o</mi><mi>l</mi><mi>d</mi></mrow></msub></mrow><annotation encoding="application/x-tex">I_{cold}</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="katex-base"><span class="katex-strut" style="height:0.8333em;vertical-align:-0.15em;"></span><span class="mord"><span class="mord mathnormal" style="margin-right:0.0785em;">I</span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.3361em;"><span style="top:-2.55em;margin-left:-0.0785em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="katex-sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord mathnormal mtight">co</span><span class="mord mathnormal mtight" style="margin-right:0.0197em;">l</span><span class="mord mathnormal mtight">d</span></span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.15em;"><span></span></span></span></span></span></span></span></span></span> 是发生冷启动的实例数，<span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><msub><mi>T</mi><mrow><mi>T</mi><mi>T</mi><mi>L</mi></mrow></msub></mrow><annotation encoding="application/x-tex">T_{TTL}</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="katex-base"><span class="katex-strut" style="height:0.8333em;vertical-align:-0.15em;"></span><span class="mord"><span class="mord mathnormal" style="margin-right:0.1389em;">T</span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.3283em;"><span style="top:-2.55em;margin-left:-0.1389em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="katex-sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord mathnormal mtight" style="margin-right:0.1389em;">T</span><span class="mord mathnormal mtight" style="margin-right:0.1389em;">T</span><span class="mord mathnormal mtight">L</span></span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.15em;"><span></span></span></span></span></span></span></span></span></span> 是缓存秒数。这个公式不是计费器，而是在提醒我们：进程内缓存只能减少单实例回源，不能承诺全局每小时一次。</p><p>如果流量上升，需要跨实例稳定缓存，应升级到支持 Redis 的上游版本并接入兼容服务，或者把适合缓存的响应交给 CDN；同时必须遵守数据源更新频率和使用条款，不能用“我有缓存”给高频抓取穿隐身衣。</p><h2 id="vercel-json-到底做了什么"><code>vercel.json</code> 到底做了什么</h2><p>仓库当前配置使用显式构建器：</p><figure class="highlight json"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line"><span class="punctuation">&#123;</span></span><br><span class="line">  <span class="attr">&quot;version&quot;</span><span class="punctuation">:</span> <span class="number">2</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;builds&quot;</span><span class="punctuation">:</span> <span class="punctuation">[</span></span><br><span class="line">    <span class="punctuation">&#123;</span></span><br><span class="line">      <span class="attr">&quot;src&quot;</span><span class="punctuation">:</span> <span class="string">&quot;index.js&quot;</span><span class="punctuation">,</span></span><br><span class="line">      <span class="attr">&quot;use&quot;</span><span class="punctuation">:</span> <span class="string">&quot;@vercel/node&quot;</span></span><br><span class="line">    <span class="punctuation">&#125;</span></span><br><span class="line">  <span class="punctuation">]</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;routes&quot;</span><span class="punctuation">:</span> <span class="punctuation">[</span></span><br><span class="line">    <span class="punctuation">&#123;</span></span><br><span class="line">      <span class="attr">&quot;src&quot;</span><span class="punctuation">:</span> <span class="string">&quot;/(.*)&quot;</span><span class="punctuation">,</span></span><br><span class="line">      <span class="attr">&quot;dest&quot;</span><span class="punctuation">:</span> <span class="string">&quot;/&quot;</span></span><br><span class="line">    <span class="punctuation">&#125;</span></span><br><span class="line">  <span class="punctuation">]</span></span><br><span class="line"><span class="punctuation">&#125;</span></span><br></pre></td></tr></table></figure><p>第一段把 <code>index.js</code> 编译为 Node.js Function；第二段把所有访问路径送进同一个 Hono 应用，让 <code>/all</code>、<code>/bilibili</code>、<code>/zhihu</code> 不必各自创建 Function 文件。Vercel 目前仍支持 <code>routes</code>，但官方建议普通重写和响应头优先使用更高层的 <code>rewrites</code>、<code>headers</code> 配置。这个仓库的显式 Function 适配属于需要先做回归验证再迁移的情况，不要只因为新语法看起来更潮就闭眼改配置。</p><h3 id="当前-CORS-配置需要收紧">当前 CORS 配置需要收紧</h3><p>原仓库在 Vercel 路由层同时声明了：</p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">Access-Control-Allow-Credentials: true</span><br><span class="line">Access-Control-Allow-Origin: *</span><br></pre></td></tr></table></figure><p>浏览器的凭据型 CORS 不接受通配来源；而热榜 API 本身通常只需要公开的 <code>GET</code> 与预检 <code>OPTIONS</code>，也没必要默认放行 <code>PATCH</code>、<code>DELETE</code>、<code>POST</code>、<code>PUT</code>。更稳妥的原则是二选一：</p><ol><li>公开只读 API：允许 <code>Origin: *</code>，移除 credentials，只开放 <code>GET, OPTIONS</code>；</li><li>私有前端 API：把 Origin 写成明确域名，保留 credentials，并补上 <code>Vary: Origin</code>。</li></ol><p>上游 Hono 应用本身已经有 CORS 中间件，部署层不应再发一套互相打架的头。正式上线前应统一到一个地方管理，并用浏览器预检请求验证，而不是让两层 CORS 像两位保安互相检查工作证。</p><!-- spoiler-1ffd9:black --><span class="spoiler" onclick="this.classList.toggle('spoiler')"><span class="spoiler-blur spoiler-1ffd9">看到“全放行最省事”时，请默念：省下来的配置时间，通常会变成未来的事故复盘时间。</span></span><h2 id="从-GitHub-部署到-Vercel">从 GitHub 部署到 Vercel</h2><p>下面给出一条可复现、可回滚的部署路线。仓库当前生产项目使用 Node.js 22；上游 2.0.6 要求 Node.js 20 或更高版本。</p><h3 id="1-拉取代码并锁定安装">1. 拉取代码并锁定安装</h3><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">git <span class="built_in">clone</span> https://github.com/luomo66ccff/hotapi.git</span><br><span class="line"><span class="built_in">cd</span> hotapi</span><br><span class="line">node --version</span><br><span class="line">npm ci</span><br></pre></td></tr></table></figure><p>这里用 <code>npm ci</code>，不使用 <code>npm install</code>。前者严格读取 <code>package-lock.json</code>，确保部署复现 2.0.6；后者可能因为 <code>^2.0.6</code> 自动解析到新的兼容版本，让“重新部署”偷偷变成“顺便升级”。</p><h3 id="2-本地用-Vercel-运行时验证">2. 本地用 Vercel 运行时验证</h3><p>这个部署仓没有普通 <code>dev</code> 脚本，最接近生产路径的方式是：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">npx vercel dev</span><br></pre></td></tr></table></figure><p>另开终端做四个冒烟测试：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">curl --fail http://localhost:3000/all</span><br><span class="line">curl --fail <span class="string">&quot;http://localhost:3000/bilibili?limit=3&quot;</span></span><br><span class="line">curl --fail <span class="string">&quot;http://localhost:3000/zhihu?rss=true&quot;</span></span><br><span class="line">curl -i -X POST http://localhost:3000/bilibili</span><br></pre></td></tr></table></figure><p>预期分别是：路由目录、三条 JSON 数据、XML 响应，以及不支持方法的 <code>405</code>。某个数据源失败不必立刻判定整站坏掉；先用 <code>/all</code> 区分运行时故障与单路由上游变化。</p><h3 id="3-导入-GitHub-仓库">3. 导入 GitHub 仓库</h3><p>在 Vercel 控制台选择 <strong>Add New → Project</strong>，导入 <code>luomo66ccff/hotapi</code>。设置如下：</p><table><thead><tr><th>项目</th><th>值</th></tr></thead><tbody><tr><td>Framework Preset</td><td>Other</td></tr><tr><td>Root Directory</td><td><code>.</code></td></tr><tr><td>Install Command</td><td><code>npm ci</code></td></tr><tr><td>Node.js Version</td><td>22.x</td></tr><tr><td>Build / Function config</td><td>读取仓库根目录 <code>vercel.json</code></td></tr><tr><td>Production Branch</td><td><code>main</code></td></tr></tbody></table><p>也可以用 CLI：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">npx vercel <span class="built_in">link</span></span><br><span class="line">npx vercel deploy</span><br><span class="line">npx vercel deploy --prod</span><br></pre></td></tr></table></figure><p>第一次先发布 Preview，完成接口检查后再发 Production。GitHub 连接建立后，分支或 Pull Request 生成预览部署，<code>main</code> 进入生产；这样依赖升级不会直接在正式接口上玩俄罗斯轮盘。</p><h3 id="4-环境变量建议">4. 环境变量建议</h3><p>默认配置可以启动，但公开服务建议显式记录这些值：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">CACHE_TTL=3600</span><br><span class="line">DISALLOW_ROBOT=true</span><br><span class="line">RSS_MODE=false</span><br><span class="line">USE_LOG_FILE=false</span><br><span class="line">ALLOWED_HOST=your-frontend.example</span><br><span class="line">ALLOWED_DOMAIN=https://your-frontend.example</span><br></pre></td></tr></table></figure><p><code>USE_LOG_FILE=false</code> 很重要：Vercel Function 的本地文件系统不是持久日志盘，运行日志应写到标准输出，再由 Vercel Logs 收集。</p><div class="note warning flat"><p>锁定的上游 2.0.6 有一个已修复的配置错误：<code>REQUEST_TIMEOUT</code> 实际误读了 <code>CACHE_TTL</code>。因此不要在 2.0.6 上假设 <code>REQUEST_TIMEOUT=6000</code> 已生效；先升级并验证到 2.0.8，再单独配置请求超时。</p></div><h3 id="5-发布后的验收">5. 发布后的验收</h3><div class="tabs"><div class="nav-tabs"><button type="button" class="tab active"><i class="fas fa-vial"></i>接口正确性</button><button type="button" class="tab"><i class="fas fa-cloud"></i>平台状态</button><button type="button" class="tab"><i class="fas fa-shield-halved"></i>浏览器 CORS</button></div><div class="tab-contents"><div class="tab-item-content active"><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">curl --fail https://your-project.vercel.app/all</span><br><span class="line">curl --fail <span class="string">&quot;https://your-project.vercel.app/bilibili?limit=3&quot;</span></span><br><span class="line">curl -I <span class="string">&quot;https://your-project.vercel.app/zhihu?rss=true&quot;</span></span><br></pre></td></tr></table></figure><p>确认 <code>/all</code> 的 <code>code</code> 为 200、<code>routes</code> 非空；榜单 <code>data</code> 是数组；RSS 的 <code>Content-Type</code> 是 XML。</p></div><div class="tab-item-content"><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">npx vercel inspect https://your-project.vercel.app</span><br><span class="line">npx vercel logs https://your-project.vercel.app --since 1h</span><br></pre></td></tr></table></figure><p>确认目标是 production、状态是 Ready、Function 构建成功，并检查是否存在连续超时、上游 403 或模块缺失。</p></div><div class="tab-item-content"><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">curl -i -X OPTIONS https://your-project.vercel.app/bilibili \</span><br><span class="line">  -H <span class="string">&quot;Origin: https://your-frontend.example&quot;</span> \</span><br><span class="line">  -H <span class="string">&quot;Access-Control-Request-Method: GET&quot;</span></span><br></pre></td></tr></table></figure><p>确认只返回预期 Origin 和方法；若使用 <code>*</code>，就不应同时允许 credentials。</p></div></div><div class="tab-to-top"><button type="button" aria-label="scroll to top"><i class="fas fa-arrow-up"></i></button></div></div><h3 id="6-绑定域名与回滚">6. 绑定域名与回滚</h3><p>在 Vercel 的 Domains 中添加 API 子域名，再按控制台给出的 DNS 目标配置。不要凭记忆硬写 CNAME；同一个域名若已经被其他项目占用，应先确认归属，避免为了一个热榜接口把主站 DNS 拔成盆栽。</p><p>每次更新前保留上一个 Ready 部署。新版本出现系统性 500 时，优先在 Vercel 控制台把别名切回上一份通过验收的生产部署，再分析依赖或数据源变化；不要在事故现场边改 <code>package-lock.json</code> 边许愿。</p><h2 id="依赖升级：把“每天检查”变成真正的门禁">依赖升级：把“每天检查”变成真正的门禁</h2><p>仓库已启用 Dependabot，每天检查 npm 依赖。但自动开 PR 不等于自动安全升级，尤其 <code>dailyhot-api</code> 同时包含路由、爬取器、缓存和页面解析逻辑。建议每个升级 PR 至少执行：</p><ol><li>锁文件差异审查，确认没有意外主版本跳跃；</li><li><code>npx vercel dev</code> 本地启动；</li><li><code>/all</code>、一个 JSON 路由、一个 RSS 路由和一个错误方法测试；</li><li>Preview 部署检查 Function 体积和冷启动；</li><li>CORS、缓存参数与 robots 行为回归；</li><li>只在 Preview 全绿后合并到 <code>main</code>。</li></ol><p>从 2.0.6 升到 2.0.8 时，建议把依赖改为精确版本，避免以后无审查漂移：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">npm install --save-exact dailyhot-api@2.0.8</span><br><span class="line">npx vercel dev</span><br></pre></td></tr></table></figure><p>验证完再提交 <code>package.json</code> 与 <code>package-lock.json</code>。若希望继续自动接收补丁版本，也可以保留 caret，但生产部署仍必须使用锁文件安装。</p><h2 id="公开-API-还缺哪些生产护栏">公开 API 还缺哪些生产护栏</h2><p>当前部署适合个人项目、开发测试和轻量聚合，但不能因为 Vercel 自动扩容就假装它无限抗打。</p><table><thead><tr><th>风险</th><th>表现</th><th>建议</th></tr></thead><tbody><tr><td>数据源变更</td><td>单路由 403、结构解析失败</td><td>独立监控重点路由，不把单源故障升级成全站宕机</td></tr><tr><td>无全局限流</td><td>热门接口被脚本集中调用</td><td>在边缘层增加限流、Bot 防护或调用配额</td></tr><tr><td>实例缓存不共享</td><td>并发实例重复回源</td><td>需要时接 Redis/CDN，并设置合理 TTL</td></tr><tr><td>宽松 CORS</td><td>任意站点借用接口、配置互相冲突</td><td>只读与凭据场景分开配置</td></tr><tr><td>抓取合规</td><td>数据源条款或结构变化</td><td>尊重站点规则、降低频率、保留停用单路由能力</td></tr><tr><td>依赖漂移</td><td>重部署行为突然变化</td><td><code>npm ci</code>、Preview、锁文件审查与可回滚部署</td></tr></tbody></table><details class="toggle" style="border: 1px solid #425b89"><summary class="toggle-button" style="background-color: #425b89;color: #ffffff">点开：接口异常时按这个顺序排查</summary><div class="toggle-content"><ol><li><code>/all</code> 也失败：检查 Vercel Function 构建、Node 版本、ESM 配置和入口是否被正确打包。</li><li><code>/all</code> 正常、某个榜单失败：大概率是该数据源接口、反爬或页面结构变化；查看对应上游路由的运行日志。</li><li>JSON 正常、RSS 失败：检查 <code>rss=true</code> 是否正确传递、响应是否包含无法生成 Feed 的异常字段。</li><li>首次请求慢、随后恢复：区分冷启动与上游超时；不要先把 TTL 调成一年假装世界和平。</li><li>浏览器报 CORS、命令行正常：检查响应里是否同时出现多套 <code>Access-Control-*</code>，并修复 wildcard + credentials 冲突。</li><li>更新依赖后全部 500：回滚上一份 Ready 部署，再比较 Node 要求、导出方式、锁文件与 <code>vercel.json</code>。</li></ol></div></details><h2 id="最后的复盘">最后的复盘</h2><p>HotAPI 最值得复用的不是那句 <code>serveHotApi()</code>，而是“核心能力做成包，平台差异留在薄适配层”的思路。业务路由不必知道自己跑在 Vercel，部署仓也不复制 40 份抓取实现；两边通过版本、入口和 HTTP 契约连接。</p><p>但薄并不等于随便。真正决定它能否长期运行的是锁文件、Serverless 缓存认知、CORS 边界、Preview 门禁、日志和回滚。把这些补齐，三行代码可以是一座稳稳的桥；忽略它们，三行代码也能是一块写着“此路不通”的电子牌。</p><a class="btn-beautify blue center larger" href="https://github.com/luomo66ccff/hotapi" title="从部署仓开始复现"><i class="fab fa-github"></i><span>从部署仓开始复现</span></a>]]>
    </content>
    <id>https://blog.luomo.moe/posts/9f7e31ac.html</id>
    <link href="https://blog.luomo.moe/posts/9f7e31ac.html"/>
    <published>2026-08-04T14:10:00.000Z</published>
    <summary>从三行入口、ESM 依赖、动态路由与进程内缓存，到 Vercel Node.js Function、GitHub 持续部署、CORS 加固和版本升级，完整拆解 HotAPI 的部署适配路径。</summary>
    <title>把一个 npm 包变成热榜 API：HotAPI 的 Vercel Serverless 技术路径与部署实战</title>
    <updated>2026-08-04T14:10:00.000Z</updated>
  </entry>
  <entry>
    <author>
      <name>洛墨-天染-依然</name>
    </author>
    <category term="技术实践" scheme="https://blog.luomo.moe/categories/%E6%8A%80%E6%9C%AF%E5%AE%9E%E8%B7%B5/"/>
    <category term="Next.js" scheme="https://blog.luomo.moe/tags/Next-js/"/>
    <category term="React" scheme="https://blog.luomo.moe/tags/React/"/>
    <category term="TypeScript" scheme="https://blog.luomo.moe/tags/TypeScript/"/>
    <category term="Zustand" scheme="https://blog.luomo.moe/tags/Zustand/"/>
    <category term="Playwright" scheme="https://blog.luomo.moe/tags/Playwright/"/>
    <category term="Docker" scheme="https://blog.luomo.moe/tags/Docker/"/>
    <category term="Cloudflare" scheme="https://blog.luomo.moe/tags/Cloudflare/"/>
    <content>
      <![CDATA[<link rel="stylesheet" class="aplayer-secondary-style-marker" href="/assets/css/APlayer.min.css"><script src="/assets/js/APlayer.min.js" class="aplayer-secondary-script-marker"></script><script class="meting-secondary-script-marker" src="/assets/js/Meting.min.js"></script><link rel="stylesheet" type="text&#x2F;css" href="/pluginsSrc/hexo-tag-hint/dist/hexo-tag-hint.min.css"><p>我把自己做过的公开项目重新过了一遍，最后选择详细拆解 <ruby>雾港档案<rt>Fog Harbor Archive</rt></ruby>。理由并不是它的技术名词最多，而是它把一个原创题材从“能点的网页”做成了完整产品：有 11 个调查模块、23 份证物、4 组谜题、3 个结局、二周目变化、跨设备布局、自动化测试和可重复部署链路。它最能说明一件事：沉浸感不是靠全屏特效糊出来的，而是叙事、状态、交互和工程约束一起咬合的结果。</p><mark class="hl-label purple">原创叙事</mark> <mark class="hl-label blue">本地存档</mark> <mark class="hl-label green">完整E2E</mark> <mark class="hl-label orange">已上线</mark><a class="btn-beautify blue larger" href="https://fog-harbor-archive.luomo.moe" title="进入雾港调查室"><i class="fas fa-play"></i><span>进入雾港调查室</span></a><a class="btn-beautify purple larger" href="https://github.com/luomo66ccff/fog-harbor-archive" title="查看 GitHub 源码"><i class="fab fa-github"></i><span>查看 GitHub 源码</span></a><div class="note info flat"><p>本文只谈工程，不剧透案件答案。核心真相会继续躺在雾里，绝不被教程一铲子挖出来。</p></div><span id="more"></span><h2 id="先看成品：同一案件，两套交互密度">先看成品：同一案件，两套交互密度</h2><div class="gallery-container" data-type="data" data-button="true" data-limit="10" data-first="10">    <div class="gallery-items">[{"url":"/images/fog-harbor/desktop-investigation.webp","alt":"桌面端调查工作区：多窗口、任务栏与案件材料并存","title":"桌面端调查工作区"},{"url":"/images/fog-harbor/mobile-investigation.webp","alt":"移动端调查界面：内容重排为单列阅读与操作","title":"移动端调查界面"},{"url":"/images/fog-harbor/investigator-index.webp","alt":"调查员索引：二周目和隐藏线索不会硬塞进第一次流程","title":"调查员索引"}]</div>  </div><p>桌面端不是普通网页套一层“窗口皮肤”：窗口有打开、最小化、聚焦、层级和位置；移动端也不是把桌面缩成邮票，而是换成更适合触控的导航与阅读顺序。两端共享案件状态，但各自拥有符合设备的交互密度。</p><h2 id="为什么这类项目真正难做">为什么这类项目真正难做</h2><p>一个线性故事只需要回答“下一段是什么”。调查游戏却同时要回答：玩家看过什么、证物是否被关联、谜题是否解开、当前任务能否推进、二周目是否开启、窗口该出现在什么位置，以及异常存档是否会把进度机炸成烟花。</p><p>因此我没有先堆页面，而是把项目拆成四种不同寿命的状态：</p><table><thead><tr><th>状态</th><th>例子</th><th>保存位置</th><th>设计目的</th></tr></thead><tbody><tr><td>案件事实</td><td>证物、人物、文档、消息、时间线</td><td>TypeScript 静态数据</td><td>可审查、可测试、不会被 UI 改写</td></tr><tr><td>长期进度</td><td>已解锁证物、谜题、结局、二周目</td><td><code>localStorage</code></td><td>刷新或下次访问后继续调查</td></tr><tr><td>会话状态</td><td>彩蛋触发、临时提示</td><td><code>sessionStorage</code></td><td>关闭标签页后自然重置</td></tr><tr><td>界面状态</td><td>窗口位置、层级、最小化、待处理意图</td><td>Zustand 内存状态</td><td>快速响应，不污染案件数据</td></tr></tbody></table><div class="note warning flat"><p>最重要的边界是：窗口开没开，不等于证据解没解锁；动画播没播，也不等于剧情已经推进。把 UI 状态和领域状态混在一起，后面一定会出现“关个窗口把结局关没了”的赛博灵异事件。</p></div><h2 id="技术路线：先建立规则，再让界面长出来">技术路线：先建立规则，再让界面长出来</h2><div class="timeline blue"><div class='timeline-item headline'>        <div class='timeline-item-title'>          <div class='item-circle'><p>从想法到可部署产品</p></div>        </div>      </div><div class='timeline-item'>        <div class='timeline-item-title'>          <div class='item-circle'><ol><li>把叙事写成可查询的数据</li></ol></div>        </div>        <div class='timeline-item-content'><p>人物、证物、文档、消息、音频转写、谜题、任务、时间线和结局先获得稳定 ID。组件只消费数据，不在 JSX 里偷偷决定案件真相。</p></div>      </div><div class='timeline-item'>        <div class='timeline-item-title'>          <div class='item-circle'><ol start="2"><li>把推进规则抽成纯逻辑</li></ol></div>        </div>        <div class='timeline-item-content'><p>证据解锁、谜题判定、任务推进、结局条件、二周目变化和调查日志分别进入 <code>lib/</code>。它们可以脱离浏览器做单元测试。</p></div>      </div><div class='timeline-item'>        <div class='timeline-item-title'>          <div class='item-circle'><ol start="3"><li>用两个 Store 管理两种世界</li></ol></div>        </div>        <div class='timeline-item-content'><p><code>case-store</code> 负责案件进度、迁移与持久化；<code>window-store</code> 负责窗口系统、层级和会话彩蛋。两者通过明确动作协作，不互相偷改内部字段。</p></div>      </div><div class='timeline-item'>        <div class='timeline-item-title'>          <div class='item-circle'><ol start="4"><li>再实现桌面与移动交互</li></ol></div>        </div>        <div class='timeline-item-content'><p>桌面端组合窗口管理器、任务栏和叙事层；移动端重排内容和入口。动效遵守 reduced-motion，音频不可用时仍提供文本转写。</p></div>      </div><div class='timeline-item'>        <div class='timeline-item-title'>          <div class='item-circle'><ol start="5"><li>用自动化测试钉住关键旅程</li></ol></div>        </div>        <div class='timeline-item-content'><p>CI 依次执行 lint、TypeScript 检查、构建、Node 单元测试、依赖审计和 Playwright；桌面、移动、二周目与彩蛋流程都进入浏览器测试。</p></div>      </div><div class='timeline-item'>        <div class='timeline-item-title'>          <div class='item-circle'><ol start="6"><li>用不可公开暴露的容器端口上线</li></ol></div>        </div>        <div class='timeline-item-content'><p>生产构建输出 Next.js standalone，由非 root 容器运行；Cloudflare Tunnel 在同一 Compose 网络中接入，宿主机只监听 <code>127.0.0.1:8797</code>。</p></div>      </div></div><h2 id="架构全景">架构全景</h2><div class="mermaid-wrap"><pre class="mermaid-src" data-config="{}" hidden>    flowchart TB  U[&quot;调查员 &#x2F; 浏览器&quot;] --&gt; C[&quot;GameClient&quot;]  C --&gt; P[&quot;Providers&quot;]  P --&gt; CS[&quot;case-store&quot;]  P --&gt; WS[&quot;window-store&quot;]  CS --&gt; E[&quot;推进与谜题引擎&quot;]  WS --&gt; W[&quot;桌面窗口系统&quot;]  E --&gt; D[&quot;案件静态数据&quot;]  CS --&gt; LS[&quot;localStorage\nfog-harbor-save-v1&quot;]  WS --&gt; SS[&quot;sessionStorage\nfog-harbor-easter-session-v1&quot;]  C --&gt; N[&quot;叙事层 &#x2F; 过场 &#x2F; 环境事件&quot;]  C --&gt; M[&quot;桌面组件 &#x2F; 移动组件 &#x2F; 谜题组件&quot;]  </pre></div><p>代码按 Next.js App Router 组织，React 与 TypeScript 承担视图和类型边界，Tailwind CSS 负责视觉系统，Framer Motion 处理过场与微动效，Zustand 管状态。默认开发/构建命令由 Vinext 驱动；服务器部署则在 Docker 构建阶段显式执行 <code>next build</code>，并通过环境变量启用 Next.js 的 <code>standalone</code> 输出。这样，日常开发目标和自托管运行时互不冒充。</p><h2 id="路径一：叙事数据要“可计算”">路径一：叙事数据要“可计算”</h2><p>调查游戏里的文案并不只是字符串。每份证物至少需要稳定 ID、来源、显示条件和关联关系；任务需要前置条件；结局需要一组可以解释的判定。稳定 ID 是这套系统的地基，因为存档、测试和 UI 都只应该引用 ID，而不是复制整份对象。</p><p>时间谜题也应先成为规则。例如比较实体钟与系统记录时，可以把核心偏差写成：</p><p><span class="katex-display"><span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML" display="block"><semantics><mrow><mi mathvariant="normal">Δ</mi><mi>t</mi><mo>=</mo><msub><mi>t</mi><mtext>physical</mtext></msub><mo>−</mo><msub><mi>t</mi><mtext>system</mtext></msub></mrow><annotation encoding="application/x-tex">\Delta t = t_{\text{physical}} - t_{\text{system}}</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="katex-base"><span class="katex-strut" style="height:0.6833em;"></span><span class="mord">Δ</span><span class="mord mathnormal">t</span><span class="mspace" style="margin-right:0.2778em;"></span><span class="mrel">=</span><span class="mspace" style="margin-right:0.2778em;"></span></span><span class="katex-base"><span class="katex-strut" style="height:0.9012em;vertical-align:-0.2861em;"></span><span class="mord"><span class="mord mathnormal">t</span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.3361em;"><span style="top:-2.55em;margin-left:0em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="katex-sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord text mtight"><span class="mord mtight">physical</span></span></span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.2861em;"><span></span></span></span></span></span></span><span class="mspace" style="margin-right:0.2222em;"></span><span class="mbin">−</span><span class="mspace" style="margin-right:0.2222em;"></span></span><span class="katex-base"><span class="katex-strut" style="height:0.9012em;vertical-align:-0.2861em;"></span><span class="mord"><span class="mord mathnormal">t</span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.2806em;"><span style="top:-2.55em;margin-left:0em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="katex-sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord text mtight"><span class="mord mtight">system</span></span></span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.2861em;"><span></span></span></span></span></span></span></span></span></span></span></p><p>界面负责收集输入和展示反馈，谜题引擎负责标准化输入、计算 <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi mathvariant="normal">Δ</mi><mi>t</mi></mrow><annotation encoding="application/x-tex">\Delta t</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="katex-base"><span class="katex-strut" style="height:0.6833em;"></span><span class="mord">Δ</span><span class="mord mathnormal">t</span></span></span></span>、判断容差并返回结果。这样同一条规则可以被桌面窗口、移动页面和单元测试复用。</p><!-- spoiler-1ffd9:black --><span class="spoiler" onclick="this.classList.toggle('spoiler')"><span class="spoiler-blur spoiler-1ffd9">案件答案当然不会写在公式下面。想套答案的侦探请收起你那伸向 F12 的小手。</span></span><h2 id="路径二：存档不是-JSON-parse-完就下班">路径二：存档不是 <code>JSON.parse</code> 完就下班</h2><p>长期状态保存在 <code>fog-harbor-save-v1</code>。读取时不直接相信浏览器里的 JSON，而是执行迁移和清洗：旧字段补默认值，集合按合法 ID 白名单过滤，枚举落到允许范围，计数和布尔值做类型归一化。损坏或被手工篡改的存档最多丢失异常字段，不能让整个应用在启动阶段白屏。</p><p>推荐把持久化层看成一个版本化协议：</p><figure class="highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">type</span> <span class="title class_">PersistedCaseState</span> = &#123;</span><br><span class="line">  <span class="attr">version</span>: <span class="built_in">number</span>;</span><br><span class="line">  <span class="attr">unlockedEvidenceIds</span>: <span class="built_in">string</span>[];</span><br><span class="line">  <span class="attr">solvedPuzzleIds</span>: <span class="built_in">string</span>[];</span><br><span class="line">  <span class="attr">unlockedEndingIds</span>: <span class="built_in">string</span>[];</span><br><span class="line">  <span class="attr">secondRun</span>: <span class="built_in">boolean</span>;</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><p>每次升级只从“磁盘里的未知数据”迁移到“当前内存模型”，不要让 UI 组件各自兼容历史版本。迁移完成后再由 Store 暴露稳定动作，组件只调用 <code>unlockEvidence()</code>、<code>solvePuzzle()</code> 一类的领域操作。</p><p>调查日志导出也遵循最小化原则：只包含玩家主动形成的调查记录，不夹带身份、网络或设备信息。一个离线单机叙事项目，没必要突然 cosplay 数据经纪人。</p><h2 id="路径三：桌面窗口系统的关键不是拖拽">路径三：桌面窗口系统的关键不是拖拽</h2><p>窗口管理真正麻烦的是一致性：打开已存在窗口时要聚焦而非复制；最小化后任务栏状态要同步；点击窗口要提升 z-index；视口变化后位置不能漂到屏幕外；剧情发出的“打开某文件”意图要等目标窗口准备好后消费。</p><p><code>window-store</code> 因此维护窗口实体、焦点、层级、位置和 pending intent。组件发动作，Store 完成原子更新，最终由视图渲染。移动端则绕开自由窗口布局，直接把相同内容投影到单列导航中。共享的是领域信息，不是桌面坐标。</p><p>动效还要有降级路径。系统开启 <code>prefers-reduced-motion</code> 时，九段短过场切换到静态表达；音频失败时保留转写；环境彩蛋可以增强氛围，但绝不能成为推进主线的唯一入口。</p><h2 id="路径四：测试一条“玩家真的会走”的路">路径四：测试一条“玩家真的会走”的路</h2><p>这个项目的 CI 不是只跑一次构建。GitHub Actions 使用 Node.js 22，并按下面的顺序验证：</p><ol><li><code>npm ci</code> 保证锁文件安装可复现；</li><li>ESLint 与 <code>tsc --noEmit</code> 守住静态边界；</li><li>Vinext 构建确认默认目标可产出；</li><li>Node 原生测试覆盖推进、谜题、结局和状态清洗等纯逻辑；</li><li><code>npm audit</code> 对生产依赖的 high 和全依赖的 critical 风险设门禁；</li><li>Playwright 安装 Chromium，跑桌面、移动端、二周目和彩蛋旅程；</li><li>失败时上传截图、视频、trace 和报告，避免只留一句“元素不存在”让人对着空气破案。</li></ol><div class="note success flat"><p>测试分层的收益是定位速度：纯规则错了看单元测试，交互旅程断了看 Playwright，部署运行时错了看容器健康检查。三类问题不会在同一个红灯里抱团取暖。</p></div><h2 id="部署教程：Docker-Cloudflare-Tunnel">部署教程：Docker + Cloudflare Tunnel</h2><p>下面是仓库当前真实使用的生产路径。它不要求在公网开放应用端口，适合已有 Linux 服务器和 Cloudflare 托管域名的场景。</p><h3 id="1-准备环境">1. 准备环境</h3><ul><li>一台安装 Docker Engine 与 Docker Compose v2 的 Linux 主机；</li><li>一个 Cloudflare Zero Trust 账户与已创建的 remotely-managed Tunnel；</li><li>域名已接入 Cloudflare；</li><li>Git，用于拉取和更新代码。</li></ul><div class="tabs"><div class="nav-tabs"><button type="button" class="tab active"><i class="fas fa-laptop-code"></i>本地开发</button><button type="button" class="tab"><i class="fas fa-server"></i>生产服务器</button></div><div class="tab-contents"><div class="tab-item-content active"><p>本地开发需要 Node.js 22.13 或更高版本：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">git <span class="built_in">clone</span> https://github.com/luomo66ccff/fog-harbor-archive.git</span><br><span class="line"><span class="built_in">cd</span> fog-harbor-archive</span><br><span class="line">npm ci</span><br><span class="line">npm run dev</span><br></pre></td></tr></table></figure><p>提交前建议执行完整验证：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">npm run <span class="built_in">test</span>:all</span><br></pre></td></tr></table></figure></div><div class="tab-item-content"><p>服务器只需要 Git、Docker 与 Compose：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">git <span class="built_in">clone</span> https://github.com/luomo66ccff/fog-harbor-archive.git</span><br><span class="line"><span class="built_in">cd</span> fog-harbor-archive</span><br></pre></td></tr></table></figure><p>不要在服务器全局安装 Node。依赖安装、构建和运行都在版本固定的 <code>node:22-alpine</code> 镜像里完成。</p></div></div><div class="tab-to-top"><button type="button" aria-label="scroll to top"><i class="fas fa-arrow-up"></i></button></div></div><h3 id="2-配置-Tunnel-Token">2. 配置 Tunnel Token</h3><p>在 Cloudflare Zero Trust 后台创建 Tunnel，把公开主机名指向 <code>http://app:3000</code>。<code>app</code> 是 Compose 服务名，cloudflared 与它处于同一私有网络，因此无需绕到宿主机公网端口。</p><p>在项目根目录创建 <code>.env.server</code>：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">TUNNEL_TOKEN=粘贴你的_Remotely_Managed_Tunnel_Token</span><br></pre></td></tr></table></figure><p>这个文件已经被 Git 忽略。不要把 Token 写进 <code>compose.server.yaml</code>，更不要截图发群里表演“一键共享基础设施”。</p><p>如果你使用自己的域名，还要把 <code>compose.server.yaml</code> 中的构建参数改为实际地址：</p><figure class="highlight yaml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="attr">build:</span></span><br><span class="line">  <span class="attr">args:</span></span><br><span class="line">    <span class="attr">NEXT_PUBLIC_SITE_URL:</span> <span class="string">https://your-domain.example</span></span><br></pre></td></tr></table></figure><h3 id="3-构建并启动">3. 构建并启动</h3><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">docker compose -f compose.server.yaml --env-file .env.server up -d --build</span><br></pre></td></tr></table></figure><p>构建采用三阶段镜像：<code>dependencies</code> 通过 <code>npm ci</code> 安装锁定依赖；<code>builder</code> 设置 <code>FOG_HARBOR_SERVER_BUILD=1</code> 并执行 <code>npx next build</code>；<code>runner</code> 只复制 <code>.next/standalone</code>、静态文件和 <code>public</code>，最后以非 root 的 <code>nextjs</code> 用户运行 <code>server.js</code>。</p><h3 id="4-验证健康状态">4. 验证健康状态</h3><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">docker compose -f compose.server.yaml --env-file .env.server ps</span><br><span class="line">curl --fail http://127.0.0.1:8797/</span><br><span class="line">docker compose -f compose.server.yaml --env-file .env.server logs --<span class="built_in">tail</span>=100 app tunnel</span><br></pre></td></tr></table></figure><p><code>app</code> 的健康检查每 15 秒请求容器内的 <code>127.0.0.1:3000</code>。只有它进入 healthy，<code>tunnel</code> 才会启动。宿主机映射为 <code>127.0.0.1:8797:3000</code>，所以 8797 只允许本机访问；公网流量必须经过 Tunnel。</p><h3 id="5-安全与运维检查">5. 安全与运维检查</h3><p>两个服务都启用了 <code>no-new-privileges</code> 并丢弃全部 Linux capabilities。应用容器不以 root 运行，Tunnel Token 位于未提交的环境文件，服务端也没有数据库和玩家账号。上线前仍应确认服务器防火墙没有额外放行 3000 或 8797。</p><p>更新代码时使用：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">git pull --ff-only</span><br><span class="line">docker compose -f compose.server.yaml --env-file .env.server up -d --build</span><br><span class="line">docker image prune -f</span><br></pre></td></tr></table></figure><p>生产回滚最好依赖已验证 Git 标签。切到上一个稳定标签后重新构建，而不是手改正在运行的容器：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">git switch --detach v2.2.0</span><br><span class="line">docker compose -f compose.server.yaml --env-file .env.server up -d --build</span><br></pre></td></tr></table></figure><details class="toggle" style="border: 1px solid #425b89"><summary class="toggle-button" style="background-color: #425b89;color: #ffffff">点开：部署失败时按这个顺序排查</summary><div class="toggle-content"><ol><li><code>app</code> 构建失败：先看 Docker 构建输出，确认锁文件未漂移、Node 版本仍为 22、<code>next build</code> 没有类型或静态生成错误。</li><li><code>app</code> 一直 unhealthy：进入日志检查 <code>server.js</code> 是否启动，确认容器端口仍是 3000，健康检查 URL 没被重定向到不可达地址。</li><li><code>tunnel</code> 退出：检查 <code>.env.server</code> 是否存在、Token 是否来自当前 Tunnel、容器时间与服务器 DNS 是否正常。</li><li>域名显示 502：确认 Zero Trust 的 Service URL 是 <code>http://app:3000</code>，不是宿主机的 <code>localhost:8797</code>；容器里的 localhost 只指向它自己。</li><li>页面能开但旧资源未更新：检查浏览器缓存和 Cloudflare 缓存，再确认构建参数里的站点 URL 是否还是旧域名。</li></ol></div></details><h2 id="这条路线为什么适合它">这条路线为什么适合它</h2><p>这个项目的核心状态全部在浏览器本地，服务器只负责交付应用。Docker standalone 控制运行时体积和版本，Cloudflare Tunnel 解决入口、TLS 与隐藏源站端口，Zustand 持久化让故事不依赖数据库。每层只解决自己的问题，部署路径就不会为了“看起来像大厂”凭空养出一套用户系统、消息队列和三台吃灰的数据库。</p><p>它也保留了继续扩展的空间：若未来需要云存档，可以在现有持久化协议外增加同步适配层；若需要更多案件，可以复用引擎与窗口系统，仅替换数据包和主题组件；如果要把客户端交付到其他运行时，领域规则和测试仍然可以保留。</p><h2 id="最后的复盘">最后的复盘</h2><p>《雾港档案》最值得复用的并不是某个组件，而是这条顺序：先把事实做成数据，把推进做成纯规则，把长期状态和界面状态拆开，再让桌面、移动端、动效和部署围绕同一组边界生长。顺序对了，特效是放大器；顺序错了，特效就是案发现场的烟雾弹。</p><p>如果你准备做自己的互动叙事项目，可以先只实现一个人物、三份证物、一个谜题和一个结局，但从第一天就给它们稳定 ID、迁移策略和一条自动化玩家旅程。小规模的正确结构，比大规模的临时奇迹更容易走到上线。</p><a class="btn-beautify blue center larger" href="https://fog-harbor-archive.luomo.moe" title="现在开始调查"><i class="fas fa-magnifying-glass"></i><span>现在开始调查</span></a>]]>
    </content>
    <id>https://blog.luomo.moe/posts/7c2a4f61.html</id>
    <link href="https://blog.luomo.moe/posts/7c2a4f61.html"/>
    <published>2026-08-04T10:30:00.000Z</published>
    <summary>从叙事数据建模、桌面式交互、持久化迁移和自动化测试，到 Docker 与 Cloudflare Tunnel 上线，完整拆解《雾港档案：失踪的第七码头》的工程路径。</summary>
    <title>把网页做成一间会记住你的调查室：雾港档案 2.2 技术路径与部署实战</title>
    <updated>2026-08-04T10:30:00.000Z</updated>
  </entry>
</feed>
