<?xml version="1.0" encoding="UTF-8"?><rss version="2.0" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>赵培州</title><description>AI 应用研发 · Agent 工作流 · 全栈实践</description><link>https://heaven-1314.github.io/</link><templateTheme>Firefly</templateTheme><templateThemeVersion>6.15.6</templateThemeVersion><templateThemeUrl>https://github.com/CuteLeaf/Firefly</templateThemeUrl><lastBuildDate>2026年8月17日 14:37:03</lastBuildDate><item><title>先别急着上 RAG：四根轴算清适用边界</title><link>https://heaven-1314.github.io/posts/rag-applicability-boundary/</link><guid isPermaLink="true">https://heaven-1314.github.io/posts/rag-applicability-boundary/</guid><description>长上下文时代 RAG 被反复宣判死刑，但检索并没有死。四根轴加四笔账，算清什么时候关键词检索就够、什么时候才真正需要语义检索。</description><pubDate>Mon, 17 Aug 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;大模型的上下文窗口冲到百万 token 之后，“RAG 已死”的论调隔三差五出来一次；另一边，简历里”智能客服 + RAG”仍是标配，好像不做检索增强就跟不上时代。两边都把问题想简单了。RAG 不是默认件，也不是遗产，它是一个有明确适用边界的工程选项——这篇文章把这条边界画出来。&lt;/p&gt;
&lt;section&gt;&lt;h2&gt;同一个问题，两种问法，两种命运&lt;a href=&quot;#同一个问题两种问法两种命运&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;先把模式抽象出来。设用户想问的问题是 a，文档库里对应的答案是 A，问法有两种：&lt;/p&gt;&lt;ul&gt;
&lt;li&gt;a1（行话版）——用户嘴里的词和文档里的词高度重合&lt;/li&gt;
&lt;li&gt;a2（口语版）——用户用自己的生活语言描述同一个意图，文档原词一个没出现&lt;/li&gt;
&lt;/ul&gt;&lt;p&gt;a1 靠关键词检索就能命中；a2 是关键词检索的天敌。举个具体例子，某公司报销制度里写的是”探亲路费凭票报销，硬座全报、卧铺报七成”：&lt;/p&gt;&lt;ul&gt;
&lt;li&gt;问法 a1：“探亲假火车票的报销标准是什么？”——“探亲&quot;&quot;火车票&quot;&quot;报销”全是文档原词，关键词检索秒中。制度全文不到一千字，命中后直接整篇读进上下文，什么都不用再建。&lt;/li&gt;
&lt;li&gt;问法 a2：“我老家在外地，过年回去的高铁票能报多少？“——文档里没有”过年&quot;&quot;回去&quot;&quot;高铁”，关键词检索零命中。但语义上这就是探亲路费，向量检索能接住。&lt;/li&gt;
&lt;/ul&gt;&lt;p&gt;同一个意图，两种问法，命运完全不同。这就是检索选型的第一性原理——分界线不在数据量，不在公司规模，在&lt;strong&gt;用户嘴里的词和文档里的词的重合度&lt;/strong&gt;。&lt;/p&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;四根轴，一条口诀&lt;a href=&quot;#四根轴一条口诀&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;





























&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;轴&lt;/th&gt;&lt;th&gt;问什么&lt;/th&gt;&lt;th&gt;判断&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;规模&lt;/td&gt;&lt;td&gt;语料塞不塞得进窗口&lt;/td&gt;&lt;td&gt;百 K 以内可以全塞；超窗口没得选，检索是唯一路径&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;频次&lt;/td&gt;&lt;td&gt;查询量多大&lt;/td&gt;&lt;td&gt;全塞的成本是乘法——每次查询都付全量 token，乘上次数就是真金白银&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;词汇重合度&lt;/td&gt;&lt;td&gt;用户说不说行话&lt;/td&gt;&lt;td&gt;说行话关键词够；不说行话才需要语义检索&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;语料规范度&lt;/td&gt;&lt;td&gt;文件名和内容规不规范&lt;/td&gt;&lt;td&gt;法条、判例、规章制度这类规范语料，命中即读全文，语义检索无增益&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;blockquote&gt;&lt;p&gt;口诀：塞得下加低频，全塞；塞得下加高频，检索省钱省延迟；塞不下，没得选；要精度，检索是降噪器。&lt;/p&gt;&lt;/blockquote&gt;&lt;p&gt;把后两根轴交叉，能得到一个反直觉的结论——语义检索的真实主战场是&lt;strong&gt;非规范语料乘口语化提问&lt;/strong&gt;（工单、聊天记录、会议纪要），而不是规范文档。给一万条法条建向量库，不如一个文件名匹配加全文阅读。&lt;/p&gt;&lt;p&gt;顺带拆掉两个常见误解：&lt;/p&gt;&lt;p&gt;误解一，“做个路由判断，简单问题走关键词、复杂问题走向量”。不需要，也做不好——用户下一句话会怎么措辞，事前不可知。工业界的答案是并联：两路检索同时发出，用 RRF（倒数排名融合）把两份榜单合成一份。不赌用户的嘴，让两路投票。这也不是两套系统，关键词索引可以内存直跑，向量库是同一批文档的另一个视图，同源同更新。&lt;/p&gt;&lt;p&gt;误解二，“检索策略要动态选”。其实产品形态在立项时就把策略定死了——用户要原文（工程师查报错），做成搜索产品，返回列表加高亮，不用接大模型；用户要人话（新人问制度），做成问答产品，检索是内部管道，用户只看到答案加引用角标。运行时零判断。&lt;/p&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;“我都 1M 上下文了，全部塞进去不行吗？”&lt;a href=&quot;#我都-1m-上下文了全部塞进去不行吗&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;小语料、低频场景——行，而且这就是最优解，主流 AI 编码工具就是这么干的（不用向量库，代理自己搜索加读文件）。但账要算完四笔：&lt;/p&gt;&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;成本是乘法&lt;/strong&gt;。十万 token 语料，全塞与检索只差九万五，单次看着免费；乘上一千次查询就是每天一个亿 token 级的重复读入。且超长输入通常分档加价，缓存会在文档一改时全部失效。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;延迟买不掉&lt;/strong&gt;。十万 token 的读入阶段首字延迟是秒级，五千是亚秒级。这是吞吐问题，加钱只能缓解不能消除。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;精度随长度衰减&lt;/strong&gt;。大海捞针测试（一根针藏进百万 token 找出来）强模型近乎满分，但真实任务是多针加干扰项加交叉引用——塞十万 token 其中九成与本题无关，注意力被稀释，实测答案质量反而低于只塞五千精准内容。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;规模上限&lt;/strong&gt;。十万篇文档是数亿 token，百万窗口塞不进百分之一，“全塞”根本不在选项里。&lt;/li&gt;
&lt;/ol&gt;&lt;p&gt;还有个反转认知的事实。很多人说”我用关键词把相关内容全找出来，一起塞进上下文”——这句话里的”找出来”本身就是检索。关键词匹配、按相关度排序返回前若干条，这就是检索器的召回端，只是没叫 RAG 的名字。所以真实的格局不是”RAG 对抗全塞”，而是分层共存。&lt;/p&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;检索没死，但换了角色&lt;a href=&quot;#检索没死但换了角色&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;强模型时代，检索的三个新定位：&lt;/p&gt;&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;供料阀门&lt;/strong&gt;。再强的模型也读不见窗口里不存在的内容。检索的角色从”补模型知识短板”变成”省钱的供料阀门”——用百分之一的 token，买到九成的效果。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;降噪器&lt;/strong&gt;。这是长上下文给不了的能力——把无关内容挡在窗口外面。前排噪音挤占 token 预算，正确文档排太后还会被截断、被”中间遗忘”，所以精排才有价值：给上下文窗口里那几个宝贵座位挑对的乘客。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;幻觉的缰绳&lt;/strong&gt;。反直觉但重要——模型越强，幻觉越自信。召回不足时，强模型更敢用内部知识补漏。供料质量与模型能力是乘法关系，不是替代关系。&lt;/li&gt;
&lt;/ul&gt;&lt;p&gt;同时有一份贬值清单。查询改写、多轮语义扩充这类”帮模型理解差句”的优化正在贬值——现代大模型在输入时自己就会理解、联想、改写，它本身就是查询理解器，在它前面再包一层人工查询工程是浪费时间。升值的是召回准确性、精排、去重。&lt;/p&gt;&lt;blockquote&gt;&lt;p&gt;口诀：优化”喂什么”的永远有效，优化”帮模型理解差句”的正在贬值。&lt;/p&gt;&lt;/blockquote&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;真要上，默认配方能到九十分&lt;a href=&quot;#真要上默认配方能到九十分&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;基本向量库加 embedding 切块加一个精排模型，就是 90% 可用的系统，接入大模型答题能到七八十分。剩下那二十分，没有规模和评测集就不要追。&lt;/p&gt;&lt;p&gt;顺带把精排说透，因为它是被误解最深的一环。召回阶段用的是双塔结构——查询和文档各自独立编码成一个向量再比对距离，两者从未”见过面”，快，可以离线预计算，毫秒级扫全库。精排是另一种模型——把查询和文档拼接成一个序列一起读，词级交互，逐对打分，准但慢，只能伺候前二十到五十个候选。一个是海选简历，一个是面试当面聊。另外注意，把向量从 768 维换成 2048 维不是精排——那是换更强的召回模型，两件事正交。&lt;/p&gt;&lt;p&gt;那二十分怎么追？靠评测闭环，不是靠感觉。核心是先有一小份金标准评测集——二十条起步，每条是”问题加应命中的文档加标准答案”。来源三选：让大模型从文档反向生成问题（半天出几百条，但问法一股文档腔，只能冷启动用）；从真实查询日志里抽题人工标注（二十条约一小时，质量最高）；上线后收集用户点”答案不对”的反馈滚动入库（每周一两个小时，评测集随产品长大）。有了它，每次改配置都有分数可看，错误可归因——该召回的没召回改切块和混合检索，召回了排太后上精排，召回全对答案胡编改接地指令和引用标注，答案跑题改提示词或换模型。&lt;/p&gt;&lt;blockquote&gt;&lt;p&gt;归因口诀：用户骂”答非所问、胡编”，先查检索；用户骂”讲得绕、听不懂”，再换模型。检索决定有没有答案，模型决定答案讲得好不好。&lt;/p&gt;&lt;/blockquote&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;写在最后&lt;a href=&quot;#写在最后&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;回到开头的两派。“RAG 已死”派只看到了窗口变宽，没看到注意力会稀释、延迟要付钱；“无脑上 RAG”派只看到了简历潮流，没问过用户说不说行话。两者共享同一个错误——把工程选项当信仰。&lt;/p&gt;&lt;p&gt;下次有人问”要不要上 RAG”，先反问四个问题：语料多大、查得多不多、用户说不说行话、文档规不规范。四个答案出来，要不要上、上到哪一层、优化停在哪一分，自己就出来了。&lt;/p&gt;&lt;/section&gt;</content:encoded></item><item><title>ASR 模型选型实战：从一段全是噪音的录音说起</title><link>https://heaven-1314.github.io/posts/asr-model-selection-field-notes/</link><guid isPermaLink="true">https://heaven-1314.github.io/posts/asr-model-selection-field-notes/</guid><description>四款开源/闭源语音识别模型横向评测实录——音频编码踩坑、无标注评测方法论、数字后处理配方与显存实测。</description><pubDate>Fri, 14 Aug 2026 00:00:00 GMT</pubDate><content:encoded>&lt;section&gt;&lt;h2&gt;事情的起点&lt;a href=&quot;#事情的起点&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;一批 8kHz 的无线通话录音要做转写。这个选型前后做了两轮：第一轮把市面上叫得上号的开源模型扫了一遍（七个本地模型 + 四家云端 API），第二轮圈定四款做批量集中验证。&lt;/p&gt;&lt;p&gt;第一天就撞上了第一个坑，而且是我自己挖的。&lt;/p&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;第一坑：PCM 根本不是一种格式&lt;a href=&quot;#第一坑pcm-根本不是一种格式&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;拿到的是一批无扩展名的裸音频文件，按”8kHz × 8bit = 64kbps”的直觉，我用无符号 8bit 线性 PCM 去解：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;Terminal window&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;ffmpeg&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;-f&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;u8&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;-ar&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;8000&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;-ac&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;1&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;-i&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;input.pcm&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;output.wav&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;解出来一听——噪音巨大，人声若有若无。我的第一反应是”源文件音质就这么差”。&lt;/p&gt;&lt;p&gt;直到有人拿配套的专用转换工具转了一版，同样源文件，听感明显干净。一个 14MB 的小工具能做到 ffmpeg 做不到的事？我不信。&lt;/p&gt;&lt;p&gt;&lt;code&gt;strings&lt;/code&gt; 扫了一遍那个工具的核心 DLL，导出函数列表里躺着一行答案：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;G729ToALaw&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;PcmtoWaveNew&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;G729 转 A-law——这批录音是电信系统的 &lt;strong&gt;A-law 对数编码&lt;/strong&gt;，根本不是线性 PCM。&lt;/p&gt;&lt;p&gt;对数编码和线性编码的区别可以抽象成一个通用模式：同一个字节值 b，在线性语义下表示”幅度就是 b”，在对数语义下表示”幅度是 exp(b) 的映射”。两套语义对小信号的处理完全不同——对数编码正是为了在 8bit 里塞下电话级动态范围设计的。你拿线性去解对数，等于拿对数表当加法表用，信噪比全乱，出来的”噪音”其实是被错误还原的语音。&lt;/p&gt;&lt;p&gt;换正确的解码方式：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;Terminal window&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;ffmpeg&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;-f&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;alaw&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;-ar&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;8000&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;-ac&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;1&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;-i&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;input.pcm&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;-c:a&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;pcm_s16le&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;output.wav&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;听感和专用工具完全一致。顺带搞清了大小差异：它输出 8bit 我输出 16bit，同一段音频正好差一倍，内容零差别。&lt;/p&gt;&lt;p&gt;&lt;strong&gt;教训：遇到陌生的裸数据，先逆向它的配套工具，别凭参数直觉猜格式。&lt;/strong&gt; A-law（中国/欧洲电信）还是 μ-law（美日），这一步猜错，后面所有评测全部作废——我第一轮全量推理就是这么报废的。&lt;/p&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;第二坑：升采样不会提升质量&lt;a href=&quot;#第二坑升采样不会提升质量&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;纠错过程中我还犯过另一个方向的错误：把 8kHz 升到 16kHz 再喂模型，理由是”主流模型都要 16kHz”。&lt;/p&gt;&lt;p&gt;错。奈奎斯特定理决定了 8kHz 录音里最高只有 4kHz 的频率信息，升采样不会凭空造出新信息，插值算法反而会把 8bit 的量化误差扩散到更多采样点上，信噪比更低。正确姿势是保持原生采样率，让模型内部的重采样逻辑自己处理——各家模型都带这一层。&lt;/p&gt;&lt;p&gt;当然有例外：个别开源模型（比如 FireRedASR2）要求外部喂 16kHz，那就单独转一份给它，但这是”喂给特定模型的适配层”，不是”提升音质”。&lt;/p&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;没有标注数据，怎么做评测？&lt;a href=&quot;#没有标注数据怎么做评测&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;这批录音没有人工转写的标准答案，算不了字错率（CER）。实际用的是两条腿走路：&lt;/p&gt;&lt;p&gt;&lt;strong&gt;交叉对比&lt;/strong&gt;：多个模型转同一段音频，互为参照。所有模型都识别出的内容，大概率是对的；只有一个模型独有的内容，人工抽听裁决。&lt;/p&gt;&lt;p&gt;&lt;strong&gt;维度量化&lt;/strong&gt;：把”识别质量”拆成可数的指标——&lt;/p&gt;&lt;ul&gt;
&lt;li&gt;行业术语命中数（维护一张领域术语表，统计每家的命中次数）&lt;/li&gt;
&lt;li&gt;地名/呼号识别（词表 + 正则）&lt;/li&gt;
&lt;li&gt;文本输出量（平均/中位字数，过短=漏内容，过长=可能有幻觉）&lt;/li&gt;
&lt;li&gt;成功率（失败、空输出单独计数）&lt;/li&gt;
&lt;li&gt;速度（平均耗时、RTF=处理时间/音频时长）&lt;/li&gt;
&lt;/ul&gt;&lt;p&gt;&lt;strong&gt;测速还有个铁律：必须标注并发数和 batch size。&lt;/strong&gt; 云端 API 开 5 个并发和串行，吞吐天差地别；本地模型 batch=1 和 batch=8 也不是一回事。我第一版报告只写了”平均 X 秒”，被自己人一句”你到底并发了几个”问穿——没有并发标注的速度数据，等于没测。&lt;/p&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;第一轮：基准排名会骗人&lt;a href=&quot;#第一轮基准排名会骗人&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;第一轮扫描覆盖了七个本地开源模型：FireRedASR2-LLM、FireRedASR2-AED、Qwen3-ASR-1.7B、FunASR-Nano、SenseVoice-L、Paraformer-Large、Whisper-Large-v3，外加豆包/讯飞/腾讯云/阿里云四家云端。先看各论文在标准 16kHz 测试集上的普通话 CER：&lt;/p&gt;








































&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;模型&lt;/th&gt;&lt;th&gt;基准CER%&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;FireRedASR2-LLM&lt;/td&gt;&lt;td&gt;2.89&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;FireRedASR2-AED&lt;/td&gt;&lt;td&gt;3.05&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;豆包ASR&lt;/td&gt;&lt;td&gt;3.69&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Qwen3-ASR-1.7B&lt;/td&gt;&lt;td&gt;3.76&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;FunASR-Nano&lt;/td&gt;&lt;td&gt;4.16&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;SenseVoice-L&lt;/td&gt;&lt;td&gt;4.47&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Paraformer-Large&lt;/td&gt;&lt;td&gt;4.56&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Whisper-Large-v3&lt;/td&gt;&lt;td&gt;9.86&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;p&gt;拿同一段真实的 8kHz 低质量通话音频喂进去，&lt;strong&gt;排名反转了&lt;/strong&gt;：基准第四的 Qwen3-ASR 实测最优——专业词汇最准、转写最完整通顺；基准第一的 FireRedASR2 鲁棒性反而略逊，出现了把”夜航资质”听成”银行资质”这类领域词错误。&lt;/p&gt;&lt;p&gt;这一轮最大的教训就一句话：&lt;strong&gt;基准测试集是 16kHz 干净音频，你的业务音频是另一回事，选型必须拿真实数据实测。&lt;/strong&gt;&lt;/p&gt;&lt;p&gt;初筛还抓到几种有意思的失效模式，值得记录：&lt;/p&gt;&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;重复循环&lt;/strong&gt;：Qwen3-ASR 和 Whisper Turbo 在噪声段陷入复读，同一个词组输出几百次（Whisper 还顺手切成了繁体中文）&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;整体乱码&lt;/strong&gt;：Paraformer 在这批音频上输出完全不可读&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;同门几乎一致&lt;/strong&gt;：FireRedASR2 标准版和 Lite 版输出几乎逐字相同，买大不买小没有意义&lt;/li&gt;
&lt;/ul&gt;&lt;p&gt;另外做了个底座核查，结论对选型很有参考价值：&lt;strong&gt;榜单上的高分”新”模型，大量是开源底座的衍生品&lt;/strong&gt;。核查方法很简单——抓 HuggingFace 模型仓库的 raw README（详情页是 SPA，curl HTML 拿不到内容）。核查结果：MOSS-Transcribe 是 Qwen3-1.7B 底座 + Qwen3-Omni 音频编码器；Higgs Audio v3 是 Whisper-Large-v3 编码器 + Qwen3 解码器；某排行榜第 10 名的模型点开详情一看，“基于千问 1.7B 微调”。所以追赶榜单之前，先看看它爹是谁。&lt;/p&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;第二轮：四款集中验证&lt;a href=&quot;#第二轮四款集中验证&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;第二轮圈定四款：Qwen3-ASR 与 FireRedASR2 本地部署、讯飞与豆包云端 API，用批量的真实数据（100 个常规文件 + 两批各 10 个长音频）集中验证。维度量化结果：&lt;/p&gt;





















































&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;维度&lt;/th&gt;&lt;th&gt;Qwen3-ASR&lt;/th&gt;&lt;th&gt;FireRedASR2&lt;/th&gt;&lt;th&gt;讯飞&lt;/th&gt;&lt;th&gt;豆包 Seed&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;部署&lt;/td&gt;&lt;td&gt;本地 GPU&lt;/td&gt;&lt;td&gt;本地 GPU&lt;/td&gt;&lt;td&gt;云端 API&lt;/td&gt;&lt;td&gt;云端 API&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;成功率&lt;/td&gt;&lt;td&gt;99%&lt;/td&gt;&lt;td&gt;81%~70%&lt;/td&gt;&lt;td&gt;99%&lt;/td&gt;&lt;td&gt;86%（限流所致）&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;行业术语&lt;/td&gt;&lt;td&gt;多&lt;/td&gt;&lt;td&gt;少&lt;/td&gt;&lt;td&gt;&lt;strong&gt;最多&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;多&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;处理速度（RTF，越小越快）&lt;/td&gt;&lt;td&gt;0.20&lt;/td&gt;&lt;td&gt;&lt;strong&gt;0.12&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;0.70&lt;/td&gt;&lt;td&gt;0.40&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;词级时间戳&lt;/td&gt;&lt;td&gt;需额外对齐模型&lt;/td&gt;&lt;td&gt;&lt;strong&gt;原生支持&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;—&lt;/td&gt;&lt;td&gt;—&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;数字输出&lt;/td&gt;&lt;td&gt;汉字（需后处理）&lt;/td&gt;&lt;td&gt;汉字&lt;/td&gt;&lt;td&gt;汉字+阿拉伯混合&lt;/td&gt;&lt;td&gt;&lt;strong&gt;自带阿拉伯数字&lt;/strong&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;p&gt;几个值得展开的点：&lt;/p&gt;&lt;p&gt;&lt;strong&gt;FireRedASR2 被稳定性淘汰。&lt;/strong&gt; 它推理最快、原生带词级时间戳和置信度评分，设计上很优雅。但常规批 19% 的文件输出空文本，换一批长音频又随机挂掉三个——置信度掉到 0.07，其他三家都识别得完整。测试结束后我把它从服务器上删了，理由就一个：ASR 是个流水线零件，零件可以慢，不能随机坏。&lt;/p&gt;&lt;p&gt;&lt;strong&gt;豆包的限流很严。&lt;/strong&gt; 并发 3 就开始批量 429，1140 个文件的单线程补跑花了好几个小时。它的 API 是两步式（提交任务+轮询结果），认证信息全放 HTTP Header，网上能搜到的旧版端点基本都 404 或 403，照着官方文档的 v3 路径走才通。&lt;/p&gt;&lt;p&gt;&lt;strong&gt;讯飞的语种参数是 &lt;code&gt;cn&lt;/code&gt; 不是 &lt;code&gt;zh&lt;/code&gt;。&lt;/strong&gt; 这个坑值得单独写一行：报”转写语种未授权”先检查语种代码，中国区普通话的代码是 &lt;code&gt;cn&lt;/code&gt;。另外它的签名放在请求头而不是 URL 参数，音频要发原始二进制——base64 会触发文件大小校验不一致。&lt;/p&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;LLM 系 ASR 的数字问题&lt;a href=&quot;#llm-系-asr-的数字问题&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;Qwen3-ASR 识别准确率很高，但所有数字都输出汉字：“三四三四”而不是”3434”。这对通话类场景是硬伤——呼号、编号、高度值都需要阿拉伯数字。&lt;/p&gt;&lt;p&gt;第一反应是加提示词：“所有数字用阿拉伯数字表示”。实测两种提示词的输出&lt;strong&gt;逐字节相同&lt;/strong&gt;。原因在于 LLM 系 ASR 是音频驱动的转写器，说话人怎么读它就怎么写，文字提示控制不了输出格式。云端 API 没这个问题是因为服务端内置了 ITN（逆文本规范化）。&lt;/p&gt;&lt;p&gt;解法是本地后处理，配方三件套：&lt;/p&gt;&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;cn2an 的 smart 模式&lt;/strong&gt;处理量词式读法（“三百”→300，“十二点”→12点）&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;逐位映射表&lt;/strong&gt;处理逐字读法（幺=1、两=2、拐=7、洞=0——行业读法里 0 读”洞”）&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;贪心分段&lt;/strong&gt;处理混合读法：“六百三零幺六”整体解析会失败，拆成 cn2an 能吃的最长前缀（六百三→630）加逐位尾巴（零幺六→016），拼出 630016&lt;/li&gt;
&lt;/ol&gt;&lt;p&gt;还有两个防御性细节：末尾的”点”是钟点语义（“十二点”→“12点”），要防着被当小数点吞掉；“一起”、“统一”这类含数字字的常用词不能误伤。&lt;/p&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;显存与内存：实测数字&lt;a href=&quot;#显存与内存实测数字&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;部署前总得回答”多大的卡能跑”。用 120 秒的长音频把流程压满实测（bf16 精度）：&lt;/p&gt;



















&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;场景&lt;/th&gt;&lt;th&gt;显存峰值&lt;/th&gt;&lt;th&gt;内存峰值&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;纯转写&lt;/td&gt;&lt;td&gt;4.68 GB&lt;/td&gt;&lt;td&gt;4.76 GB&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;转写+词级时间戳&lt;/td&gt;&lt;td&gt;6.40 GB&lt;/td&gt;&lt;td&gt;4.99 GB&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;p&gt;结论：满血（转写+时间戳）8G 显存起步，内存 8G 稳妥；纯转写 6G 显存够用；4G 显存或 4G 内存的机器跑不动——权重本身 bf16 就 3.4GB，加上 CUDA 上下文和激活值必然爆。&lt;/p&gt;&lt;p&gt;顺带修了一个自己的惯性错误：一开始用 fp32 跑（显存直接翻倍），其实 bf16 加一行输入特征类型转换就完全正常，输出质量一致、速度更快。&lt;/p&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;最后的选型答案&lt;a href=&quot;#最后的选型答案&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;两轮跑完，答案收敛得很干净：&lt;/p&gt;&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;数据不能出域 / 无网络环境&lt;/strong&gt;：Qwen3-ASR 本地部署，配上数字后处理和时间戳对齐模型，功能闭环&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;要省事、要准确率&lt;/strong&gt;：讯飞云端，术语识别最多、稳定 99%&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;要阿拉伯数字原生输出&lt;/strong&gt;：豆包，但要接受单线程限流&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;没有 GPU、纯 CPU 跑&lt;/strong&gt;：SenseVoice-Small 或 Paraformer（第一轮结论，轻量场景够用）&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;不想部署也不想挑模型&lt;/strong&gt;：直接走大厂托管 API&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;FireRedASR2&lt;/strong&gt;：速度快，但随机空输出这个性质决定了它不适合做生产流水线零件&lt;/li&gt;
&lt;/ul&gt;&lt;p&gt;这次评测最大的收获反而不是选型结论，而是三条方法论：&lt;strong&gt;遇到陌生数据格式先逆向配套工具&lt;/strong&gt;；&lt;strong&gt;基准排名会骗人，选型必须拿真实业务音频实测&lt;/strong&gt;（基准第一的模型在我们的数据上被反超，最后还因稳定性出局）；以及&lt;strong&gt;没有标注数据时，多维交叉对比照样能收敛出可信结论&lt;/strong&gt;。至于那个让我第一轮全量报废的 A-law——下次再见到 8kHz 的”PCM”，我会先 &lt;code&gt;strings&lt;/code&gt; 一眼再动手。&lt;/p&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;关联&lt;a href=&quot;#关联&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;ul&gt;
&lt;li&gt;检阅报告（内网）：四模型横向对比页，含音频播放与分段可点时间轴&lt;/li&gt;
&lt;/ul&gt;&lt;/section&gt;</content:encoded></item><item><title>从 OpenClash 到 dae：一个刷机老手的两天折腾记</title><link>https://heaven-1314.github.io/posts/openclash-to-dae/</link><guid isPermaLink="true">https://heaven-1314.github.io/posts/openclash-to-dae/</guid><description>从 SSR 到 Passwall 到 OpenClash，再到基于 eBPF 的 dae——为什么换、换了验证了哪些技术判断、以及&quot;变砖&quot;到底算什么。</description><pubDate>Sun, 09 Aug 2026 00:00:00 GMT</pubDate><content:encoded>&lt;blockquote&gt;&lt;p&gt;2026 年 8 月，星期天。女朋友不在家，一个人，有大把时间。于是我想起一件盘算了很久的事：把路由器的代理方案，从 OpenClash 换到 dae。&lt;/p&gt;&lt;/blockquote&gt;
&lt;section&gt;&lt;h2&gt;事情是怎么开始的&lt;a href=&quot;#事情是怎么开始的&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;这件事的起因，一半是技术判断，一半是”手痒”。&lt;/p&gt;&lt;p&gt;先说判断。我从 2017 年开始折腾路由器，那会儿还念高中，在闲鱼淘了台斐讯 K2。一开始用的甚至不是 OpenWrt——是 hanwckf 的 Padavan，华硕固件底子改的，又稳又省心。后来刷了潘多拉（PandoraBox），才算真正弄懂 OpenWrt 那套东西：内核、feeds、opkg，还有 Breed——刷不死鸟引导器，只要 Breed 还在，刷机刷得再烂都死不了。&lt;/p&gt;&lt;p&gt;快十年，手里的机器从 K2 换到 K2 Pro，再到现在的 360T7。软路由只在 N1 盒子上短暂玩过，主力一直是硬路由。插件则换了一茬又一茬：SSR、Passwall、OpenClash。&lt;/p&gt;&lt;p&gt;这套组合用着一直还行，但有两个问题慢慢积攒起来。一是架构越来越重——OpenClash 当代理，AdGuard Home 当广告过滤，两层各管一摊，总觉得哪里别扭。二是性能一直没打满——千兆宽带，日常上网只能跑到 700M。&lt;/p&gt;&lt;p&gt;所以当我在网上看到 dae 这个方案时，第一反应不是”要不要换”，而是”这值得认真评估一下”。它宣称的东西——eBPF、内核层分流、真直连——恰好命中了我那两个痛点。&lt;/p&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;为什么要翻墙&lt;a href=&quot;#为什么要翻墙&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;先把这事儿说透。为什么要翻墙？&lt;/p&gt;&lt;p&gt;不是为了什么不可告人的东西。是信息、学术、工具、内容——一个搞技术的，GitHub 打不开、Google 搜不了、一堆开发文档在墙外，这日子没法过。我高中那会儿想看的教程、想下的软件、想进的学习网站，一堆在墙外。这不只是我的需求，是一代人的需求。&lt;/p&gt;&lt;p&gt;从 2003 年金盾工程立项起，这堵墙和绕墙的人就一直在玩猫鼠游戏：&lt;/p&gt;&lt;ul&gt;
&lt;li&gt;早期是免费代理、Tor——反复被墙；&lt;/li&gt;
&lt;li&gt;后来是 PPTP/L2TP/OpenVPN——2012 到 2015 年被 DPI（深度包检测）识别封杀；&lt;/li&gt;
&lt;li&gt;2012 年，Shadowsocks 出现。它把流量加密成随机字节流，看起来就是普通流量，优雅地躲过特征检测，一夜爆红；&lt;/li&gt;
&lt;li&gt;2015 年大清洗，ss 的握手特征被识别，于是 SSR（ShadowsocksR）加上了混淆协议，猫鼠游戏升级；&lt;/li&gt;
&lt;li&gt;2015 年 V2Ray/VMess 带着时间校验、动态端口杀进来；&lt;/li&gt;
&lt;li&gt;2018 年 Trojan 更进一步——直接伪装成一个正常的 HTTPS 网站，你连过去它给你看网页，你带上钥匙它就是隧道，正面刚”443 非 TLS 就封”的策略；&lt;/li&gt;
&lt;li&gt;2018 年 Clash 带着 YAML 规则和订阅管理来了，机场生态爆发，使用门槛骤降；&lt;/li&gt;
&lt;li&gt;2022 年 REALITY 借真实网站的 TLS 指纹，几乎无法检测；&lt;/li&gt;
&lt;li&gt;再到 2023 年的 sing-box、dae……&lt;/li&gt;
&lt;/ul&gt;&lt;p&gt;但所有这些，到了路由器这一层，都只是”内核”。你看到的插件——SSR Plus、Passwall、OpenClash——都是壳。壳决定体验，内核决定能力。换插件不叫升级，换内核才是。&lt;/p&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;换壳记：从 SSR 到 OpenClash&lt;a href=&quot;#换壳记从-ssr-到-openclash&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;我第一次用 SSR 的时候，还在高中。那时候的插件就是 luci-app-ssr-plus 那一类，简单、够用。后来为什么一路换？&lt;/p&gt;&lt;p&gt;两个原因。&lt;/p&gt;&lt;p&gt;第一个：&lt;strong&gt;节点支持的协议不一样&lt;/strong&gt;。SSR 毕竟年代久远，它的安全性和抗检测能力都偏低了。机场的节点协议在升级，老插件接不住新协议，你不换插件，节点都没法用。&lt;/p&gt;&lt;p&gt;第二个，也是更本质的：&lt;strong&gt;不同插件的规则可定义范围不一样&lt;/strong&gt;。核心功能——翻墙上网——大家都能做，区别在于你能对流量做多精细的控制。SSR Plus 的规则就那么几板斧，Passwall 灵活一些、能挂多个核心（v2ray/xray），而 OpenClash 的自定义程度最高：YAML 规则、规则集、节点策略组、订阅管理，几乎什么都能配。&lt;/p&gt;&lt;p&gt;所以我的路线图是：SSR → Passwall → OpenClash。一路往上，本质是在换”壳”，换更自由的控制面板。&lt;/p&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;700M 的税：OpenClash 的代价&lt;a href=&quot;#700m-的税openclash-的代价&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;OpenClash 好用，但那个”别扭”感，我后来想明白了。&lt;/p&gt;&lt;p&gt;我原来的架构是分层：OpenClash 当代理层，AdGuard Home 当广告过滤层。但两层太复杂，后来我把广告过滤直接塞进了 OpenClash 的代理规则里——图一个少一层。&lt;/p&gt;&lt;p&gt;代价很快就显现。千兆宽带，日常上网只能跑到 700M。而且 MT7981 这颗 CPU 开始发热：待机 60 度，WiFi 模块 55 度；满速跑代理，CPU 能冲到 70 度，负载飙到 2 甚至接近 3。&lt;/p&gt;&lt;p&gt;问题出在哪？&lt;/p&gt;&lt;p&gt;OpenClash 是 Clash/Mihomo 内核的用户态方案。TUN 模式下，流量路径是这样的：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;设备 → 网卡驱动 → 内核协议栈 → TUN 虚拟网卡 → Clash 进程（用户态）&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;     &lt;/span&gt;&lt;/span&gt;&lt;span&gt;→ 分流判定 + 代理/直连处理 → 内核协议栈 → 网线出去&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;注意最后那步：&lt;strong&gt;即使是”直连”的国内流量，也要从内核态切到用户态（Clash 进程），处理完再切回内核态发出去&lt;/strong&gt;。每个包两次上下文切换。我把广告过滤塞进规则后，国内流量也得过 Clash 这一层——那 300M 的差距，就是用户态处理的税。&lt;/p&gt;&lt;p&gt;这不是 Clash 的错，是用户态方案的宿命：你的分流逻辑跑在用户态，流量就得进用户态。&lt;/p&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;遇见 dae：eBPF 和”真直连”&lt;a href=&quot;#遇见-daeebpf-和真直连&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;dae 是 daeuniverse 的作品。它的作者 mzz2017 之前做了 v2rayA——v2ray-core 的 Web 界面，用 iptables 分流。做久了，他看到了一个天花板：任何用户态代理，连”直连”流量都要经过用户态处理一遍。这是架构性的浪费，不是调优能救的。&lt;/p&gt;&lt;p&gt;他的解法是换个赛道：用 eBPF 把分流程序写进 Linux 内核的 tc（traffic control）挂载点，在流量进入 TCP/IP 协议栈&lt;strong&gt;之前&lt;/strong&gt;就完成分流。eBPF 程序跑在内核态，没有上下文切换。&lt;/p&gt;&lt;p&gt;直连流量是这样走的：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;设备 → 网卡驱动 → tc 挂载点（eBPF 分流判定）&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;├─ 直连 → 内核 L3 路由直接转发 → 网线出去    ← 零用户态开销&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;3&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;└─ 代理 → 重定向到 dae 的 tproxy 端口 → dae 用户态加密 → 网线出去&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;&lt;strong&gt;关键在”Real Direct”（真直连）&lt;/strong&gt;：直连流量在内核层就拐弯了，Linux 变成一台纯交换机/路由器。mzz2017 自己在文档里写：“以 benchmark 来看，dae 的直连性能和其他代理程序相比就像个怪物。“隔着屏幕都能看见那点得意。&lt;/p&gt;&lt;p&gt;除了分流，还有两个设计细节值得玩味：&lt;/p&gt;&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;进程匹配&lt;/strong&gt;：Clash 要扫 /proc 文件系统才能知道一个连接属于哪个进程，可能要几十毫秒；dae 用 eBPF 在 cgroupv2 挂载点监听系统调用，直接读进程控制块，快一个量级。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;域名匹配&lt;/strong&gt;：dae 劫持 DNS 请求，建立”域名 → IP”的映射表。所以它的 DNS 处理必须自己来——这也是为什么配置里 dnsmasq 的流量要 must_direct（强制直连），否则 DNS 不过 dae，域名分流就失效。&lt;/li&gt;
&lt;/ul&gt;&lt;p&gt;但架构没有白来的好处。eBPF 的性能来自内核，代价也是内核：要内核 5.17+，要 BTF 调试信息，要 veth 虚拟网卡，要一堆 CONFIG 选项。桌面 Linux 默认开着，&lt;strong&gt;嵌入式固件（OpenWRT/Armbian）默认关掉&lt;/strong&gt;。&lt;/p&gt;&lt;p&gt;换句话说：dae 把性能做进了内核，把税留给了固件。而我的 360T7 恰好装着 Yuzhii 的 fixed-parts U-Boot——这是整个迁移里唯一真正需要认真对待的约束。&lt;/p&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;迁移：两天，15 个坑，一次关键判断&lt;a href=&quot;#迁移两天15-个坑一次关键判断&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;先说清楚我的方法论：这件事我没打算”试试看”，而是先列约束，再逐条验证。迁移的第一条约束就很硬——&lt;strong&gt;Yuzhii 的 U-Boot 只认 FIT 格式&lt;/strong&gt;。&lt;/p&gt;&lt;section&gt;&lt;h3&gt;约束之一：分区表是硬门槛&lt;a href=&quot;#约束之一分区表是硬门槛&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;Yuzhii 的 U-Boot 从闪存固定偏移读原始 FIT 镜像（魔数 d00dfeed），不解析 UBI 卷：&lt;/p&gt;
























&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;格式&lt;/th&gt;&lt;th&gt;特征&lt;/th&gt;&lt;th&gt;能不能启动&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;FIT (.itb)&lt;/td&gt;&lt;td&gt;魔数在偏移 0&lt;/td&gt;&lt;td&gt;✅&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;sysupgrade (.bin)&lt;/td&gt;&lt;td&gt;魔数在偏移 0x800&lt;/td&gt;&lt;td&gt;❌&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;UBI factory (.bin)&lt;/td&gt;&lt;td&gt;魔数 UBI#&lt;/td&gt;&lt;td&gt;❌&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;p&gt;官方 ImmortalWrt 固件全按 UBI 分区布局组织，分区表和 Yuzhii 的 U-Boot 对不上——跟手机刷机一个道理，分区表不兼容，镜像再新也刷不进去。&lt;/p&gt;&lt;p&gt;这条约束我在动手前就确认过，所以固件选择从一开始就没走偏。但强刷不兼容固件这件事，我确实验证过一次——不是为了试错，是为了确证”哪些固件真的不能用”。结果卡红灯，SSH 全断。对很多人这是噩梦，对我来说只是流程里的一步：进 U-Boot TFTP 模式，推 initramfs-recovery.itb 进去，十分钟救回来。&lt;/p&gt;&lt;p&gt;这里要纠正一个说法：&lt;strong&gt;这不叫变砖&lt;/strong&gt;。&lt;/p&gt;&lt;p&gt;什么叫真正的砖？我从安卓 2.3.2 刷到 4.0.4、再刷到 4.4.4 那一代人，见过真正的砖：连引导器都进不去，没有任何恢复模式。手机没有 fastboot 就是黑砖，只能靠高通 9008 厂商模式串口救；路由器连 Breed/U-Boot 都进不去，就得拆壳上 TTL 串口线，看 bootlog，手搓救砖。&lt;/p&gt;&lt;p&gt;卡开机？U-Boot 还在、TFTP 还能进？那只是小场面，跟换轮胎差不多。&lt;/p&gt;&lt;/section&gt;&lt;section&gt;&lt;h3&gt;约束之二：kmod-veth 必须编译期解决&lt;a href=&quot;#约束之二kmod-veth-必须编译期解决&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;dae 启动时要创建 veth 虚拟网卡对，内核缺 CONFIG_VETH 直接报 fatal。这条约束有个隐蔽的点：&lt;strong&gt;kaslana（我此前维护的自编译固件）用的 SNAPSHOT 内核，vermagic 和官方仓库的 kmod 包对不上&lt;/strong&gt;，&lt;code&gt;apk add kmod-veth&lt;/code&gt; 直接”No such package”。&lt;/p&gt;&lt;p&gt;根本原因：内核和 kmod 包不是一起编译的，ABI 签名不一致。所以这条只能靠”编译时把 kmod-veth 编进固件”解决，事后装不了——这也是我后来去跑自编译的动机之一：既然要动内核，干脆验证一遍构建链路。&lt;/p&gt;&lt;/section&gt;&lt;section&gt;&lt;h3&gt;约束之三：BTF 与 eBPF 工具链&lt;a href=&quot;#约束之三btf-与-ebpf-工具链&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;自编译那两天，GitHub Actions 跑了 11 次、12 小时，坑确实多，但复盘下来最值得写的有两个，都指向构建系统本身的隐蔽性：&lt;/p&gt;&lt;p&gt;一个是&lt;strong&gt;通配符陷阱&lt;/strong&gt;。我用 &lt;code&gt;for f in target/linux/generic/config-*&lt;/code&gt; 循环改内核配置，结果 &lt;code&gt;config-*&lt;/code&gt; 把 &lt;code&gt;config-filter&lt;/code&gt; 也匹配进去了，排序后版本号解析成 “filter”，BTF 选项全写进了错误的文件。编译了两遍都”成功”，但 BTF 没生成，daed 照样崩。这类 bug 不写进文章没人信，但它就是真实存在的。&lt;/p&gt;&lt;p&gt;另一个是&lt;strong&gt;静默剔除&lt;/strong&gt;。&lt;code&gt;@HAS_BPF_TOOLCHAIN&lt;/code&gt; 是 Kconfig 条件，不满足时 &lt;code&gt;make defconfig&lt;/code&gt; 会&lt;strong&gt;静默移除&lt;/strong&gt; daed 包，一个警告都没有。得开 &lt;code&gt;CONFIG_DEVEL=y&lt;/code&gt; + &lt;code&gt;CONFIG_BPF_TOOLCHAIN_HOST=y&lt;/code&gt; 才看得到。&lt;/p&gt;&lt;/section&gt;&lt;section&gt;&lt;h3&gt;关键判断：为什么自编译本身是个伪需求&lt;a href=&quot;#关键判断为什么自编译本身是个伪需求&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;两天泥潭的尽头，不是”终于编译成功了”，而是一个更值钱的发现——&lt;strong&gt;自编译这个前提，本身站不住&lt;/strong&gt;。&lt;/p&gt;&lt;p&gt;我在 6.12 内核上跑 daed，崩在：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;program of this type cannot use helper bpf_get_current_task#35&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;根因是内核 6.12 的 sockops 程序不支持 &lt;code&gt;bpf_get_current_task&lt;/code&gt; helper。我当时的第一反应是”那我编译一个带正确配置的内核”。这是惯性思维。&lt;/p&gt;&lt;p&gt;停下来问了一句：&lt;strong&gt;官方 SNAPSHOT 用什么内核？&lt;/strong&gt; 答案是 master = 6.18。再验证一句：6.18 支持这个 helper 吗？支持。那结论就清楚了——官方固件已经满足 dae 的全部运行条件，缺的只是 BTF 数据和几个 kmod 包。而 vmlinux-btf 走 CO-RE 机制跨版本兼容，6.12 的 BTF 数据在 6.18 上也能用。&lt;/p&gt;&lt;p&gt;于是最终的迁移路径短得反常识：刷官方 SNAPSHOT → 加 cooluc 第三方源 → &lt;code&gt;apk add vmlinux-btf daed luci-app-daed&lt;/code&gt; → 5 分钟装完。&lt;/p&gt;&lt;p&gt;&lt;strong&gt;重新审视最初的判断&lt;/strong&gt;：我为什么想自编译？因为想要完全可控的内核配置。但当官方固件已经满足全部硬性约束时，这个前提就塌了。结论不是”我白编了两天”，而是”我用了最硬核的方式，验证了一条其实更短的路径”。这种验证本身是有价值的——它排除了”官方固件不行，必须自编译”这个假设。&lt;/p&gt;&lt;/section&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;现在是真舒服&lt;a href=&quot;#现在是真舒服&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;迁移完成，一切正常。逐项对照最初的痛点：&lt;/p&gt;&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;国内直连&lt;/strong&gt;：恢复千兆满速。eBPF 的内核分流，国内流量不进用户态，那 300M 的税没了。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;代理流量&lt;/strong&gt;：说实话，国外网速没有提升，还是 150M 左右。原因很实在：代理速度的瓶颈在加密和线路，不在分流机制——不管 eBPF 还是 TUN，代理流量最终都要进用户态做加密，CPU 算力到头了就是那么多。这点得说清楚，不吹。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;广告过滤&lt;/strong&gt;：拆出来单独跑了 AdBlock Fast（adblock 的轻量版），不再塞进代理规则。分层又回来了，但这次没有性能税，因为广告过滤在代理之外。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;温度&lt;/strong&gt;：待机 60 度，满速代理 70 度，比 OpenClash 时代舒服。&lt;/li&gt;
&lt;/ul&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;开发者视角：mzz2017 的取舍&lt;a href=&quot;#开发者视角mzz2017-的取舍&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;写到最后，想说点站在开发者角度的话。&lt;/p&gt;&lt;p&gt;mzz2017 先是做了 v2rayA，然后抛弃了它。不是不好，是看到了天花板。他审视市面上的方案：&lt;/p&gt;&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Clash&lt;/strong&gt;：用户态做一切，直连流量也交税；&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Fake IP&lt;/strong&gt;：有 DNS 缓存污染的老问题；&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;iptables/nftables&lt;/strong&gt;：挂在 netfilter 上，位置太靠后，分流已经晚了；&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;域名嗅探&lt;/strong&gt;：只能嗅 TLS/HTTP。&lt;/li&gt;
&lt;/ul&gt;&lt;p&gt;他的想法很直接：为什么不能在&lt;strong&gt;最早的路径&lt;/strong&gt;上就把流量分了？eBPF 给了他答案。代价是内核要求高，用户侧适配成本大——也就是我这两天验证的全部内容。&lt;/p&gt;&lt;p&gt;dae 适合谁？适合软路由玩家、愿意折腾内核配置的人、在乎直连性能和资源占用的人。它不适合纯小白——底层约束太多，一个 kmod-veth 就能劝退一片。&lt;/p&gt;&lt;p&gt;但换个角度想：&lt;strong&gt;dae 把性能做进内核，是把”性能税”从运行时转移到了部署时。&lt;/strong&gt; 运行的时候快得飞起，部署的时候要过五关斩六将。这个交易划不划算，看你是哪种用户。&lt;/p&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;附录：给想迁移的人&lt;a href=&quot;#附录给想迁移的人&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;ol&gt;
&lt;li&gt;固件：官方 SNAPSHOT（内核 6.18）即可，或 Wei.G 上 T7 非 UBI 编译的固件&lt;/li&gt;
&lt;li&gt;kmod-veth：必须编译期包含，事后装不上（vermagic 不匹配）&lt;/li&gt;
&lt;li&gt;BTF：确认 CONFIG_DEBUG_INFO_BTF=y，否则 daed 无法运行；缺的话装 vmlinux-btf&lt;/li&gt;
&lt;li&gt;分区变体：Yuzhii U-Boot 只认非 UBI，永远选非 UBI 固件&lt;/li&gt;
&lt;li&gt;恢复：卡开机就 U-Boot TFTP 推 initramfs-recovery.itb，十分钟的事&lt;/li&gt;
&lt;li&gt;包管理：25.12 用 apk，不是 opkg&lt;/li&gt;
&lt;li&gt;配置：全在 /etc/daed/wing.db，SQLite 可以直接操作&lt;/li&gt;
&lt;li&gt;订阅转换：Clash YAML 需转 URI，用 luci-app-daede 或 Python 脚本&lt;/li&gt;
&lt;li&gt;接口：必须设 br-lan，不是 wan&lt;/li&gt;
&lt;li&gt;广告过滤：daed 不带，用 AdBlock Fast 之类单独跑&lt;/li&gt;
&lt;/ol&gt;&lt;/section&gt;</content:encoded></item><item><title>各行各业都在做仪表盘,但它们根本不是一种东西</title><link>https://heaven-1314.github.io/posts/dashboards-across-industries/</link><guid isPermaLink="true">https://heaven-1314.github.io/posts/dashboards-across-industries/</guid><description>研发、电商、医院、机房、智慧城市、公安——长得像,其实是好几种不同的生物。</description><pubDate>Thu, 06 Aug 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;最近因为做一个研发效能看板,我顺着这条线研究了一圈别的行业的仪表盘——电商的、医院的、数据中心的、智慧城市的、公安的。&lt;/p&gt;
&lt;p&gt;越看越觉得一件事:&lt;strong&gt;大家都在做”仪表盘”,但做出来的根本不是一种东西。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;表面看都是图表、卡片、排行榜,技术栈也差不多。但只要往里走一步就会发现,它们的核心使命完全不同,踩的坑完全不同,甚至连”什么算成功”都不一样。&lt;/p&gt;
&lt;p&gt;很多人做看板翻车,不是技术不行,是一开始就把一种仪表盘当成了另一种来做。&lt;/p&gt;
&lt;p&gt;这篇文章,我把见过的几类仪表盘摊开,讲清楚它们各自的侧重点、难在哪、要注意什么。&lt;/p&gt;
&lt;section&gt;&lt;h2&gt;先找到一个底层视角&lt;a href=&quot;#先找到一个底层视角&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;要看懂不同行业的仪表盘,最快的方法不是看行业,而是看一个更底层的东西——&lt;strong&gt;这张看板做出来,是为了让人干什么?&lt;/strong&gt;&lt;/p&gt;&lt;p&gt;我把它叫”核心动作”。核心动作不一样,整张看板的骨头就不一样。我见过的大概有五种。&lt;/p&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;一、监控告警型：出事了,得立刻让人知道&lt;a href=&quot;#一监控告警型出事了得立刻让人知道&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;代表是数据中心机房监控、服务器监控、价格异动监控。&lt;/p&gt;&lt;p&gt;看板上长什么样：一堆实时数字、几条折线、某个值红了就弹报警。&lt;/p&gt;&lt;p&gt;&lt;strong&gt;侧重点就三个字：快、准、不漏。&lt;/strong&gt;&lt;/p&gt;&lt;p&gt;做这种看板要注意:&lt;/p&gt;&lt;ul&gt;
&lt;li&gt;数据得是秒级新鲜的,昨天的数据放上来没人看。&lt;/li&gt;
&lt;li&gt;告警要有阈值,但不能太敏感。天天报警,几天后人就麻了,真出事反而看不到——这叫”告警疲劳”,是这类系统最常见的死法。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;不能漏报&lt;/strong&gt;。机房温度爆了你没报出来,是要出事、要赔钱的。这条比”画得好看”重要一万倍。&lt;/li&gt;
&lt;li&gt;图表反而不用花,关键是数据链路够不够快、够不够稳。&lt;/li&gt;
&lt;/ul&gt;&lt;p&gt;这类看板,价值全在”出事那一秒能抓住”,平时没人看它,反而是它最成功的时候。&lt;/p&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;二、度量考核型：事后算账,分蛋糕&lt;a href=&quot;#二度量考核型事后算账分蛋糕&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;代表是研发绩效、销售排行、各种 KPI 仪表盘。&lt;/p&gt;&lt;p&gt;看板上长什么样：完成率、排名、达标/未达标。&lt;/p&gt;&lt;p&gt;&lt;strong&gt;侧重点：口径权威、处处对得上账。&lt;/strong&gt;&lt;/p&gt;&lt;p&gt;注意:&lt;/p&gt;&lt;ul&gt;
&lt;li&gt;每一个数字背后都有一套”怎么算”的规则,行话叫”口径”。这套规则是开会吵出来的,不是技术定的。你说技术定不了,对不起,业务方也定不清楚,得一群人坐一起磨。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;口径一定会变。&lt;/strong&gt; 业务在跑,人在用,用着用着发现原来的定义不公平、不全、不对,就得改。&lt;/li&gt;
&lt;li&gt;同一个数字出现在看板的好几个位置,必须保证处处一致。顶部大数和下面分组列表对不上,老板第一个找你。&lt;/li&gt;
&lt;li&gt;这类看板,难的根本不是画图,是让每一个数字”可信”。&lt;/li&gt;
&lt;/ul&gt;&lt;p&gt;这块是我亲历的。我那张研发看板,图表三天画完了,口径改了半年。改到后面我都怀疑人生,但确实每一版改完,数字才真的”对得上账”。&lt;/p&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;三、调度协调型：当下怎么动,资源怎么分&lt;a href=&quot;#三调度协调型当下怎么动资源怎么分&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;代表是医院床位、仓储、运力调度。&lt;/p&gt;&lt;p&gt;看板上长什么样：每个科室还剩几张床、哪些车在跑、谁闲着。&lt;/p&gt;&lt;p&gt;&lt;strong&gt;侧重点：资源状态实时准确,能支持当下决策。&lt;/strong&gt;&lt;/p&gt;&lt;p&gt;注意:&lt;/p&gt;&lt;ul&gt;
&lt;li&gt;资源是有限的,有人占了别人就不能占。两个人同时抢一张床,系统不能把一张床分给两个人。&lt;/li&gt;
&lt;li&gt;状态要绝对一致。看板上显示有空床,护士跑过去发现没有,这种事多发生几次,这套看板就没人信了。&lt;/li&gt;
&lt;li&gt;往往还带预测：明天大概会有多少床空出来,后天呢?&lt;/li&gt;
&lt;li&gt;这类看板,背后是一个状态机。图表只是状态机的一张脸。&lt;/li&gt;
&lt;/ul&gt;&lt;p&gt;它的核心是”现在”,不是”过去”(那是考核型),也不是”未来”(那是预测)。每一秒的数字都得是真的。&lt;/p&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;四、分析洞察型：找规律,做决策&lt;a href=&quot;#四分析洞察型找规律做决策&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;代表是电商品类分析、市场趋势、用户分群。&lt;/p&gt;&lt;p&gt;看板上长什么样：多维交叉表、能钻取、能对比。&lt;/p&gt;&lt;p&gt;&lt;strong&gt;侧重点：能自由探索,查询够快,数据够干净。&lt;/strong&gt;&lt;/p&gt;&lt;p&gt;注意:&lt;/p&gt;&lt;ul&gt;
&lt;li&gt;用户会疯狂钻取。从这个品类点到那个地区,再点到那个时间段,一层层往下问。查询性能扛不住,体验直接崩。&lt;/li&gt;
&lt;li&gt;维度一多,口径更多。钻着钻着发现两个数对不上,是常态。&lt;/li&gt;
&lt;li&gt;这类看板,本质不是给人”看”的,是给人”玩”的。它是个自助分析工具,用户想问什么就能问什么。&lt;/li&gt;
&lt;/ul&gt;&lt;p&gt;做这种看板,最大的错觉是以为把图画出来就完了。不是,你交付的是”用户能问到多深”,画图只是最外面那层。&lt;/p&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;五、流程驱动型：推着业务一步步往前走&lt;a href=&quot;#五流程驱动型推着业务一步步往前走&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;代表是电商订单全流程(咨询→下单→付款→发货→物流→签收)、工单系统。&lt;/p&gt;&lt;p&gt;看板上长什么样：每个订单现在卡在哪一步、有多少积压、哪个环节是瓶颈。&lt;/p&gt;&lt;p&gt;&lt;strong&gt;侧重点：全生命周期状态机,一步都不能丢。&lt;/strong&gt;&lt;/p&gt;&lt;p&gt;注意:&lt;/p&gt;&lt;ul&gt;
&lt;li&gt;状态流转是核心。从”待付款”到”已签收”,中间每一步都不能跳、不能丢、不能回不去。&lt;/li&gt;
&lt;li&gt;异常补偿特别难。付了款没发货怎么办?发了货要退款怎么办?每一个异常分支都得有交代。&lt;/li&gt;
&lt;li&gt;幂等性：同一个动作来两次,不能造出两个订单。涉钱的事,这点是天条。&lt;/li&gt;
&lt;li&gt;这类看板,看的是流程的脉搏。图表展示的是分布和瓶颈,帮人看出”哪一步堵了”。&lt;/li&gt;
&lt;/ul&gt;&lt;p&gt;它跟调度型容易混。区别是：调度型管的是”资源此刻在哪”,流程型管的是”一件事走到了哪一步”。&lt;/p&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;六、指挥展示型：它根本不是给人用的&lt;a href=&quot;#六指挥展示型它根本不是给人用的&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;这一类我犹豫了很久要不要单列,因为它的逻辑跟前面五种完全不一样。&lt;/p&gt;&lt;p&gt;代表是&lt;strong&gt;智慧城市大脑、应急指挥中心、领导驾驶舱&lt;/strong&gt;。&lt;/p&gt;&lt;p&gt;看板上长什么样：整面墙的大屏、3D 数字孪生(一个能转的虚拟城市)、满屏滚动的数字和轨迹,科幻感拉满。&lt;/p&gt;&lt;p&gt;它的核心动作是什么?说实话,&lt;strong&gt;很多时候它的核心动作是”被看”&lt;/strong&gt;——被领导看、被视察组看、被新闻镜头看。&lt;/p&gt;&lt;p&gt;这话有点刻薄,但反映了一个真实情况：这类大屏,展示价值常常大于实用价值。设计的时候,&lt;strong&gt;“好不好看、像不像科幻片”的优先级,可能比”数据准不准”还高&lt;/strong&gt;。我也见过一些城市的车流量、人口热力图,数据是有美化成分的——这是行业里公开的秘密,toG 项目自带政治属性。&lt;/p&gt;&lt;p&gt;但你要说它纯花瓶,也不公平。它真正的技术难度在这两点:&lt;/p&gt;&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;数据打通&lt;/strong&gt;：一个城市大脑要接交警、消防、卫健委、环保、城管几十个委办局的系统。每个系统一个厂家,一套标准,谁也不愿意把数据共享出来。把这群数据捏到一块儿,才是这个项目最吃人的地方。技术不难,难在协调。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;渲染&lt;/strong&gt;：一个能流畅转动的 3D 城市,加载整个城市的建筑模型,这背后是真功夫(不是图表库能搞定的,得用游戏引擎那套)。&lt;/li&gt;
&lt;/ul&gt;&lt;p&gt;造价常常是千万级。但这钱大部分花在硬件(摄像头、大屏、指挥中心装修)和集成上,&lt;strong&gt;软件本身占比很小&lt;/strong&gt;。&lt;/p&gt;&lt;p&gt;如果你接到这类活,记住一条:&lt;strong&gt;别被那个转来转去的 3D 城市唬住,真正的功夫在数据治理和系统打通,不在那个壳。&lt;/strong&gt;&lt;/p&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;七、强权限和保密型：公安这类&lt;a href=&quot;#七强权限和保密型公安这类&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;公安系统(案件管理、治安态势、结案率大屏)骨子里是度量考核 + 流程驱动,前五种就覆盖了。我单拎出来,是因为它有几个独有属性,你做的时候不注意会出大事。&lt;/p&gt;&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;保密是红线&lt;/strong&gt;。数据脱敏、传输加密、不落明文,这些不是”可选优化”,是及格线。涉案信息泄露,不是 bug,是事故。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;权限分级极严&lt;/strong&gt;。一个民警能看到的数据范围,和他警种、级别、是否主办人强绑定。你不能让一个基层民警一打开系统看到全国数据。权限模型设计错一步,就是泄密。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;跨系统协查&lt;/strong&gt;：一个案子可能要查户籍、车辆、住宿、通讯,这些系统分散在不同部门。查询链路的复杂度远超普通看板。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;结案率、破案率这种口径,也会朝令夕改&lt;/strong&gt;——政策导向一变,考核口径跟着变。这点和我做的研发绩效看板一模一样,谁也别笑谁。&lt;/li&gt;
&lt;/ul&gt;&lt;p&gt;做这类看板,&lt;strong&gt;数据安全和权限模型,优先级在所有东西之上&lt;/strong&gt;,包括图表好不好看。&lt;/p&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;还有一根轴,解释了很多误会&lt;a href=&quot;#还有一根轴解释了很多误会&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;讲完这几种,还有一个维度特别重要,它能解释一件事——&lt;strong&gt;为什么有的看板数据量很大但不难,有的数据量很小却要命。&lt;/strong&gt;&lt;/p&gt;&lt;p&gt;这根轴是：数据是硬的,还是软的。&lt;/p&gt;&lt;p&gt;&lt;strong&gt;硬数据&lt;/strong&gt;：传感器读数、金额、数量。机房温度就是温度,订单金额就是金额,全世界只有一个值。&lt;/p&gt;&lt;p&gt;&lt;strong&gt;软数据&lt;/strong&gt;：人填的、口径模糊的。任务完成度、满意度、工作质量。&lt;/p&gt;&lt;p&gt;硬数据看板(机房、电商交易),难点在&lt;strong&gt;规模和实时&lt;/strong&gt;——几万个传感器每秒一个点,怎么存、怎么算、怎么秒级刷出来,这是技术硬骨头。&lt;/p&gt;&lt;p&gt;软数据看板(研发绩效、考核),难点在&lt;strong&gt;口径治理&lt;/strong&gt;——数据可能就那么点,但”怎么算”这件事,能吵半年、改半年。&lt;/p&gt;&lt;p&gt;很多人会觉得”我的看板简单,因为数据量小”。但你做的如果是软数据看板,数据量再小,口径这条线上的难度,可能比一个硬数据看板还高。&lt;/p&gt;&lt;p&gt;我那张研发看板就是典型：数据量小到单文件数据库就够装,但口径改了半年。反过来看一个机房监控,数据量是我这的几万倍,但每个数字是死的、是客观的,反而不用扯皮。&lt;/p&gt;&lt;p&gt;&lt;strong&gt;复杂度不是一根轴,是好几根。数据量只是其中一根。&lt;/strong&gt;&lt;/p&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;仪表盘还藏在哪些行业&lt;a href=&quot;#仪表盘还藏在哪些行业&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;上面七种只是”主力”。仪表盘其实无处不在,每个行业都有自己的变种,我快速点几个:&lt;/p&gt;&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;金融交易&lt;/strong&gt;：股票看盘、交易风控。把”监控告警”和”分析洞察”推到极致——毫秒级实时,错了直接赔钱,是实时和准确的天花板。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;工业制造和能源&lt;/strong&gt;：工厂车间 MES、电网调度。和机房监控一类,但更狠:&lt;strong&gt;看了数据,系统还要自动去调设备&lt;/strong&gt;(加大电流、关阀门)。这不只是看,是闭环控制,出错了会炸设备、会停电。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;交通&lt;/strong&gt;：红绿灯调控、地铁客流、航班调度。调度协调 + 城市级规模,一个城市几万个路口。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;物流&lt;/strong&gt;：双十一物流大屏、快递轨迹。流程驱动的代表,一个包裹从揽收到签收的全生命周期。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;互联网运营&lt;/strong&gt;：DAU、留存、实时在线人数。分析洞察的高频版,全靠埋点喂出来的。&lt;/li&gt;
&lt;/ul&gt;&lt;p&gt;你看,每一种都能对回到前面那几个核心动作上,只是每个行业把它推到了不同的极致。&lt;/p&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;回到开头&lt;a href=&quot;#回到开头&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;所以你明白我为什么说”它们根本不是一种东西”了。&lt;/p&gt;&lt;p&gt;下次做仪表盘之前,先问自己几件事:&lt;/p&gt;&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;这张看板的核心动作是什么?&lt;/strong&gt; 是出事要报警,还是事后要考核,还是当下要调度,还是给人探索,还是推流程走,还是只是给人看?核心动作不同,整个架构的骨头就不同。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;我的数据是硬的还是软的?&lt;/strong&gt; 硬的,把力气花在规模和实时上;软的,把力气花在口径和一致性上。别使错劲。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;我最怕的失败是什么?&lt;/strong&gt; 是漏报?对不上数?状态错乱?查询超时?丢步?泄密?怕的不一样,防守的重点就不一样。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;这是不是个 toG / 强展示场景?&lt;/strong&gt; 如果是,把力气分一份给”数据打通”和”看起来像回事”;如果涉及保密,权限和安全是地基,不是装饰。&lt;/li&gt;
&lt;/ol&gt;&lt;p&gt;这几件事想清楚再动手,能省掉后来很多”为什么这么简单的东西,改了半年还没完”的痛苦。&lt;/p&gt;&lt;p&gt;各行各业的仪表盘,长得像,是因为图表那层皮长得像。皮下面,它们是好几种完全不同的生物。&lt;/p&gt;&lt;/section&gt;</content:encoded></item><item><title>一份342页PDF转Word的活，逼我把6.3GB大模型塞进了8GB笔记本</title><link>https://heaven-1314.github.io/posts/unlimited-ocr-local-deploy-8gb/</link><guid isPermaLink="true">https://heaven-1314.github.io/posts/unlimited-ocr-local-deploy-8gb/</guid><description>百度Unlimited-OCR本地部署全纪录：8GB显存、Blackwell无声失败、Windows反超WSL2、大PDF逐页架构——两天踩坑记。</description><pubDate>Thu, 06 Aug 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;事情的起点很朴素：有人递给我一份 &lt;strong&gt;342 页的招标 PDF&lt;/strong&gt;,要转成 Word。&lt;/p&gt;
&lt;p&gt;第一步绕不开 OCR。但这份文件里全是表格、多栏、图文混排,还有跨页的章节——传统逐页切图的 OCR 一上,版面碎成渣,语境全丢。云 OCR 呢,342 页要钱,而且招标内容含商务条款,不适合往云上传。&lt;/p&gt;
&lt;p&gt;我盯上了百度刚开源的 &lt;strong&gt;Unlimited-OCR&lt;/strong&gt;。它不是飞桨 PaddleOCR 那条传统检测+识别的线,而是一个 3B MoE 的&lt;strong&gt;视觉语言大模型&lt;/strong&gt;(VLM),6.3GB,官方标语很狂:“&lt;strong&gt;One-shot Long-horizon Parsing&lt;/strong&gt;”——一次性长文档解析,无限上下文,整篇文档一起理解。&lt;/p&gt;
&lt;p&gt;听起来正合适。但问题是：我这台笔记本是 &lt;strong&gt;RTX 5060 Laptop,只有 8GB 显存&lt;/strong&gt;。6.3GB 的模型塞进 8GB 的卡,听起来就像把一头大象塞进冰箱。&lt;/p&gt;
&lt;p&gt;后来的两天,就是在反复验证”到底塞不塞得进去”。这篇文章记的就是这个过程。&lt;/p&gt;
&lt;section&gt;&lt;h2&gt;选型：为什么是 VLM 路线&lt;a href=&quot;#选型为什么是-vlm-路线&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;OCR 方案我大致分三类:&lt;/p&gt;&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;传统 OCR(PaddleOCR、Tesseract)&lt;/strong&gt;：检测+识别管线,快、轻,但跨页语境丢光——表格被分页切断、标题和正文分家、图文混排还原差。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;云端 OCR&lt;/strong&gt;：效果好,但贵 + 招标内容不宜外传。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;VLM 大模型 OCR(Unlimited-OCR、DeepSeek-OCR、MinerU)&lt;/strong&gt;：整页当图像喂给大模型,它自己理解版面、保持上下文,对复杂版式还原最强。&lt;/li&gt;
&lt;/ul&gt;&lt;p&gt;Unlimited-OCR 的卖点正好打在痛点上：无限上下文、VLM 理解版面、开源可本地。零成本、完全可控,代价是得和 8GB 显存死磕。&lt;/p&gt;&lt;p&gt;&lt;strong&gt;先说清楚一件事&lt;/strong&gt;：本次是&lt;strong&gt;部署 + 推理工程优化&lt;/strong&gt;,不是微调。没改一行模型权重,纯粹是把官方模型在消费级硬件上跑稳、跑快、跑通批量。这两件事千万别混。&lt;/p&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;第一关：环境,一堆无声的坑&lt;a href=&quot;#第一关环境一堆无声的坑&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;section&gt;&lt;h3&gt;下载源——别盲目走代理&lt;a href=&quot;#下载源别盲目走代理&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;2.5GB 的 torch wheel,我一开始用阿里云镜像,下了 20 分钟才三分之一。后来换交大镜像(mirror.sjtu.edu.cn),&lt;strong&gt;1 分钟搞定&lt;/strong&gt;,42MB/s。&lt;/p&gt;&lt;p&gt;更坑的是官方 CDN(download.pytorch.org)走 Clash 代理必报 SSL EOF——TLS 指纹问题。清华 TUNA 当时只有不到 5KB/s。&lt;/p&gt;&lt;p&gt;&lt;strong&gt;教训：大文件下载先 curl 测 6 秒速,再选源。国内直连镜像常比走代理快得多。&lt;/strong&gt;&lt;/p&gt;





























&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;源&lt;/th&gt;&lt;th&gt;速度&lt;/th&gt;&lt;th&gt;备注&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;官方 CDN 走 Clash&lt;/td&gt;&lt;td&gt;SSL EOF&lt;/td&gt;&lt;td&gt;TLS 指纹问题&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;mirrors.aliyun.com&lt;/td&gt;&lt;td&gt;1.5 MB/s&lt;/td&gt;&lt;td&gt;慢&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;strong&gt;mirror.sjtu.edu.cn&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;&lt;strong&gt;42 MB/s&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;torch wheel 首选&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;strong&gt;hf-mirror.com&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;&lt;strong&gt;61 MB/s&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;HuggingFace 模型首选&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/section&gt;&lt;section&gt;&lt;h3&gt;Blackwell 显卡必须用 cu128&lt;a href=&quot;#blackwell-显卡必须用-cu128&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;RTX 5060 是 50 系,compute capability 是 &lt;strong&gt;sm_120(Blackwell)&lt;/strong&gt;。我装了 torch cu126,&lt;code&gt;torch.cuda.is_available()&lt;/code&gt; 返回 &lt;code&gt;True&lt;/code&gt;,以为成了。结果一跑推理,报 “not compatible”。&lt;/p&gt;&lt;p&gt;这是最阴的一类坑——&lt;strong&gt;它不直接报错,而是装的时候正常、用的时候无声失败&lt;/strong&gt;。cu126 构建只支持到 sm_90,50 系是 sm_120,对不上。cu129 又没有 Windows 构建,只有 &lt;strong&gt;cu128&lt;/strong&gt; 有。&lt;/p&gt;&lt;p&gt;&lt;strong&gt;教训：装 torch 前先 &lt;code&gt;torch.cuda.get_device_capability()&lt;/code&gt; 确认 GPU 算力,选对应 CUDA 版本。50 系必上 cu128。&lt;/strong&gt;&lt;/p&gt;&lt;/section&gt;&lt;section&gt;&lt;h3&gt;Windows 上的段错误&lt;a href=&quot;#windows-上的段错误&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;模型用了 &lt;code&gt;trust_remote_code&lt;/code&gt;,我照惯例加了 &lt;code&gt;low_cpu_mem_usage=True&lt;/code&gt; 想省内存。结果 &lt;code&gt;from_pretrained&lt;/code&gt; 时直接 &lt;strong&gt;Segmentation fault&lt;/strong&gt;(段错误)崩溃。&lt;/p&gt;&lt;p&gt;诡异的是纯 &lt;code&gt;import&lt;/code&gt; 没事,一到加载就崩。排查走了弯路。最后发现:&lt;strong&gt;去掉这个参数就正常了&lt;/strong&gt;。&lt;/p&gt;&lt;p&gt;trust_remote_code 模型在 Windows 上慎用 &lt;code&gt;low_cpu_mem_usage&lt;/code&gt;,有段错误风险。这是个没法预报、只能靠踩的坑。&lt;/p&gt;&lt;/section&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;第二关:“无限上下文”的真相&lt;a href=&quot;#第二关无限上下文的真相&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;这是最核心的一个坑,也是我觉得最值得写的一坑。&lt;/p&gt;&lt;p&gt;Unlimited-OCR 不是号称”无限上下文、一次性长文档解析”吗?那我第一反应是：342 页一股脑塞给它,&lt;code&gt;model.infer_multi()&lt;/code&gt; 一次搞定,多爽。&lt;/p&gt;&lt;p&gt;结果&lt;strong&gt;全废&lt;/strong&gt;。342 页长文档,token 截断、OOM,什么都跑不出来。&lt;/p&gt;&lt;p&gt;翻官方架构才发现:&lt;strong&gt;它自己也是 PyMuPDF 拆页 → 逐页单独 &lt;code&gt;infer()&lt;/code&gt; → 流式聚合&lt;/strong&gt;。&lt;/p&gt;&lt;p&gt;这里有个反讽,必须说清楚:&lt;/p&gt;&lt;blockquote&gt;&lt;p&gt;官方标语”无限上下文 One-shot 长文档解析”是&lt;strong&gt;模型能力层面&lt;/strong&gt;的——单页内能理解任意长内容、不截断。但工程上跑 342 页 PDF,&lt;strong&gt;仍然必须逐页拆分喂&lt;/strong&gt;,否则显存和 token 都顶不住。&lt;/p&gt;&lt;/blockquote&gt;&lt;p&gt;&lt;strong&gt;“模型能力” ≠ “工程实现”。&lt;/strong&gt; 标语说的是模型单次推理内不截断,不代表你能把整本 PDF 一次塞进推理函数。分不清这个,架构就会判错。&lt;/p&gt;&lt;p&gt;正确做法:&lt;code&gt;infer_multi()&lt;/code&gt; 是给”几张图一起识别”用的,不是给”几百页文档一起理解”用的。大 PDF 必须逐页推理,靠前端聚合结果。这是架构红线。&lt;/p&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;第三关：8GB 显存,怎么塞 6.3GB 模型&lt;a href=&quot;#第三关8gb-显存怎么塞-63gb-模型&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;这是整件事最反直觉的部分。&lt;/p&gt;&lt;p&gt;起初我太保守,设 &lt;code&gt;max_memory={0:&quot;4.8GiB&quot;}&lt;/code&gt;,把三分之一层 offload 到 CPU。结果 PCIe 来回搬数据,GPU 利用率一直上不去,我当时还以为是”batch=1 的天花板”——这是个刻板印象,很多人会这么归因。&lt;/p&gt;&lt;p&gt;后来放宽到 &lt;code&gt;{0:&quot;6.8GiB&quot;}&lt;/code&gt;,打印 &lt;code&gt;model.hf_device_map&lt;/code&gt; 的 Counter一看,&lt;strong&gt;所有层都在 cuda 上了&lt;/strong&gt;(&lt;code&gt;{&apos;0&apos;: 1}&lt;/code&gt;),GPU 利用率直接拉到 &lt;strong&gt;99%&lt;/strong&gt;。&lt;/p&gt;&lt;p&gt;超额的部分怎么办?靠 &lt;strong&gt;Windows 共享 GPU 内存&lt;/strong&gt;自动兜底。这是 8GB 卡能跑起来的关键。&lt;/p&gt;&lt;p&gt;&lt;strong&gt;诊断方法&lt;/strong&gt;：加载后打印 &lt;code&gt;model.hf_device_map&lt;/code&gt; 的 Counter,看层在 cuda vs cpu 的分布。GPU 利用率低,第一件事不是怀疑 batch,是确认层分布。&lt;/p&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;反直觉高潮：Windows 原生居然赢了 WSL2&lt;a href=&quot;#反直觉高潮windows-原生居然赢了-wsl2&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;我想更快,试了 WSL2 + SGLang(一个推理框架)。结果这条路彻底走不通,而且原因很反直觉:&lt;/p&gt;&lt;ul&gt;
&lt;li&gt;WSL2 GPU 直通只有 &lt;strong&gt;6.84GB&lt;/strong&gt; 可用(CUDA context 占了点)&lt;/li&gt;
&lt;li&gt;模型加载完(6.49GB)只剩 0.28GB,&lt;strong&gt;SGLang 的 KV cache 池无处安放&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;关键:&lt;strong&gt;WSL2 用不了 Windows 的共享 GPU 内存兜底&lt;/strong&gt;——这是它和 Windows 原生 WDDM 架构的根本差异&lt;/li&gt;
&lt;/ul&gt;&lt;p&gt;也就是说,在 Windows 原生下,8GB 是”8GB + 共享内存兜底”;在 WSL2 下,8GB 是&lt;strong&gt;硬墙&lt;/strong&gt;。&lt;/p&gt;&lt;p&gt;&lt;strong&gt;结论：8GB 卡只能 Windows 原生 Transformers;SGLang 至少要 12GB 显存才有意义。&lt;/strong&gt;&lt;/p&gt;&lt;p&gt;很多人下意识觉得”Linux/WSL2 跑 AI 更专业”,在小显存这个场景里,这个直觉是错的。&lt;/p&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;最终架构&lt;a href=&quot;#最终架构&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;抛弃了 Gradio(版本冲突太烦),换 &lt;strong&gt;FastAPI + SSE + 自定义前端&lt;/strong&gt;,更可控:&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;C:\Users\Legion\Unlimited-OCR\&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;├── run_web.bat          Web 服务启动(双击)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;3&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;├── app.py               FastAPI 后端(推理 + SSE 流式)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;4&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;├── index.html           深色双栏前端(复刻官方 UI)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;5&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;├── run_ocr.bat          单图命令行(拖拽即用)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;6&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;├── test_infer.py        命令行推理入口&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;7&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;├── models/Unlimited-OCR/  权重 + 5 个 trust_remote_code .py&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;8&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;└── .venv/               torch 2.10+cu128 + transformers 4.57.1&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;几个关键优化:&lt;/p&gt;&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;stdout 劫持实现 token 级流式&lt;/strong&gt;：复刻官方 &lt;code&gt;ThreadTargetedStdout&lt;/code&gt;,劫持推理线程的 stdout,逐 token 推到前端 SSE,边识别边出字。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;save_results=False&lt;/code&gt;&lt;/strong&gt;：去掉每页用 matplotlib 画带框图的开销(前端用不到),GPU 不用空等。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;双层文本清理&lt;/strong&gt;：模型原始输出是 &lt;code&gt;&amp;lt;|det|&amp;gt;title [46,81,521,134]&amp;lt;|/det|&amp;gt;实际文本&lt;/code&gt; 这种带检测框坐标的——流式时用 &lt;code&gt;clean_stream&lt;/code&gt; 缓冲隐藏未闭合标记,整页完成后用官方 &lt;code&gt;remove_det&lt;/code&gt; 规整。&lt;/li&gt;
&lt;/ol&gt;&lt;p&gt;实测结果：33MB / 342 页招标文件,&lt;strong&gt;逐页流式解析成功,中文识别精准,GPU 利用率 ~99%&lt;/strong&gt;。&lt;/p&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;一些可以复用的认知&lt;a href=&quot;#一些可以复用的认知&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;踩完这堆坑,我总结了下面这些,下次本地部署大模型应该能少走弯路:&lt;/p&gt;&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;8GB 显存是大模型本地部署的生死线&lt;/strong&gt;,6.3GB 刚好挤进去,再大就难。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Windows 共享 GPU 内存是 8GB 卡的救命稻草&lt;/strong&gt;,WSL2 没有这个机制——所以小显存反而 Windows 原生更合适。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;GPU 利用率低,先排查 offload&lt;/strong&gt;,别急着甩锅给 batch=1。先看 &lt;code&gt;hf_device_map&lt;/code&gt; 层分布是不是全在 GPU。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;大 PDF 必须逐页处理&lt;/strong&gt;,即便模型号称”无限上下文”——那是模型能力,不是工程实现。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;trust_remote_code 模型在 Windows 慎用 &lt;code&gt;low_cpu_mem_usage&lt;/code&gt;&lt;/strong&gt;,有段错误风险。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Blackwell(50 系)必须 cu128+&lt;/strong&gt;,cu126 会无声失败。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;“模型能力” ≠ “工程实现”&lt;/strong&gt;,这是这次最大的认知更新。标语、benchmark、demo 展示的都是模型能力上限,工程落地要打很多折扣。&lt;/li&gt;
&lt;/ol&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;一份检查清单&lt;a href=&quot;#一份检查清单&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;下次小显存本地部署大模型,按这个走:&lt;/p&gt;&lt;ul&gt;
&lt;li&gt; &lt;code&gt;torch.cuda.get_device_capability()&lt;/code&gt; 确认算力,选对应 CUDA 版本 torch&lt;/li&gt;
&lt;li&gt; 大文件下载先 curl 测速选镜像(hf-mirror / SJTU / 阿里云)&lt;/li&gt;
&lt;li&gt; transformers / gradio / sglang 的 &lt;code&gt;huggingface-hub&lt;/code&gt; 版本对齐&lt;/li&gt;
&lt;li&gt; trust_remote_code 模型：避免 &lt;code&gt;low_cpu_mem_usage=True&lt;/code&gt;&lt;/li&gt;
&lt;li&gt; 小显存:&lt;code&gt;device_map=&quot;auto&quot;&lt;/code&gt; + &lt;code&gt;max_memory&lt;/code&gt; 宽松,依赖 Windows 共享内存&lt;/li&gt;
&lt;li&gt; 加载后打印 &lt;code&gt;hf_device_map&lt;/code&gt; 确认层分布全在 GPU&lt;/li&gt;
&lt;li&gt; 大 PDF：PyMuPDF 拆页 → 逐页推理,&lt;strong&gt;不要&lt;/strong&gt; infer_multi&lt;/li&gt;
&lt;li&gt; 流式：stdout 劫持方案&lt;/li&gt;
&lt;li&gt; 文本后处理：清理 det 等版面标记&lt;/li&gt;
&lt;li&gt; Windows bat 脚本用 GBK 编码(cmd.exe 用 GBK 解析 .bat)&lt;/li&gt;
&lt;li&gt; WSL2：venv 建在 ext4 &lt;code&gt;~/&lt;/code&gt;,别放 &lt;code&gt;/mnt/c&lt;/code&gt;(drvfs 慢且易出权限问题)&lt;/li&gt;
&lt;/ul&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;最后&lt;a href=&quot;#最后&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;342 页 PDF 最终是转出来了,中文识别很准,表格和版面还原也比我预期好。整个过程最值钱的不是 OCR 结果本身,是踩出来的一套”消费级硬件跑大模型”的工程直觉——什么能省、什么不能省、哪些直觉是错的。&lt;/p&gt;&lt;p&gt;Unlimited-OCR 仓库：&lt;a href=&quot;https://github.com/baidu/Unlimited-OCR&quot; target=&quot;_blank&quot;&gt;https://github.com/baidu/Unlimited-OCR&lt;/a&gt;&lt;/p&gt;&lt;p&gt;如果你也在小显存卡上折腾大模型,希望这篇能帮你少踩两个坑。&lt;/p&gt;&lt;/section&gt;</content:encoded></item><item><title>Agent = Model + Harness：为什么你的 AI 编码工具总是不稳定</title><link>https://heaven-1314.github.io/posts/agent-model-plus-harness/</link><guid isPermaLink="true">https://heaven-1314.github.io/posts/agent-model-plus-harness/</guid><description>真正决定 AI 编码工具能不能在项目里持续稳定产出的，不是模型，而是模型之外那一层工程规范（Harness）。</description><pubDate>Wed, 05 Aug 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;很多人以为 AI 编码工具好不好用，取决于底层模型强不强。但用久了会发现一个现象：&lt;strong&gt;同一个模型，有人用起来很稳，有人用几天就崩&lt;/strong&gt;。差别不在模型，而在模型之外的那一层——Harness。&lt;/p&gt;
&lt;section&gt;&lt;h2&gt;拆开看：Model 和 Harness&lt;a href=&quot;#拆开看model-和-harness&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;把任何一个 AI 编码工具拆开，其实是两个正交的部分：&lt;/p&gt;&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Model（模型）&lt;/strong&gt;：负责理解、推理、生成代码。这是你最容易感知到的部分。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Harness（工程规范）&lt;/strong&gt;：让模型能稳定、按流程干活的&lt;strong&gt;整套工程约束系统&lt;/strong&gt;。包括——
&lt;ul&gt;
&lt;li&gt;项目规范的存放位置（该听谁的、不该听谁的）&lt;/li&gt;
&lt;li&gt;任务状态管理（做到哪一步了、下一步是什么）&lt;/li&gt;
&lt;li&gt;上下文投喂策略（该喂什么、不喂什么、喂多少）&lt;/li&gt;
&lt;li&gt;经验沉淀机制（踩过的坑下次怎么避免）&lt;/li&gt;
&lt;li&gt;工具调用规程（什么时候该跑命令、什么时候该问人）&lt;/li&gt;
&lt;li&gt;行为边界设定（哪些事绝不能让它自作主张）&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;三个关键认知&lt;a href=&quot;#三个关键认知&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;&lt;strong&gt;1. 解耦思维&lt;/strong&gt;
把”模型能 coding”和”模型能在项目中持续稳定 coding”彻底分开。前者是能力，后者是系统。&lt;/p&gt;&lt;p&gt;&lt;strong&gt;2. Harness 才是稳定性的来源&lt;/strong&gt;
单一模型的提升，替代不了工程规范的缺失。模型再强，没有好的 Harness，照样在真实项目里翻车——因为它不知道你的项目规范、不知道上下文怎么组织、不知道边界在哪。&lt;/p&gt;&lt;p&gt;&lt;strong&gt;3. 表现归因&lt;/strong&gt;
当 AI 编码工具表现不好时，&lt;strong&gt;先查 Harness 的完备性，而不是归咎于模型&lt;/strong&gt;。这是最容易犯的错误：一不顺就换更强的模型，结果问题依旧。&lt;/p&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;我的实践&lt;a href=&quot;#我的实践&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;我日常用 Claude Code 做主力交付，一开始也遇到过”越用越崩”的阶段。后来我把稳定性问题归因到 Harness 层，给自己搭了一套”永不遗忘系统”：&lt;/p&gt;&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;对话记忆&lt;/strong&gt;：跨会话记住上下文，不用每次从零讲&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;代码知识图谱&lt;/strong&gt;：全项目调用链、影响半径存进图数据库，改代码先定位影响&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;wiki 知识库&lt;/strong&gt;：修过的 bug、做过的架构决策，以稳定引用的方式沉淀，下次不重踩&lt;/li&gt;
&lt;/ul&gt;&lt;p&gt;这就是我的 Harness 层。效果很直接：一个人能同时维护多个系统不掉线，新会话 5 秒内能检索到历史经验。&lt;strong&gt;这套系统让我真正理解了 Agent 好用的根源——不是快，是让知识跨会话积累。&lt;/strong&gt;&lt;/p&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;结论&lt;a href=&quot;#结论&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;评价一个 AI 编码工具（或你自己的 Agent 工作流），&lt;strong&gt;先看它的 Harness，再谈模型&lt;/strong&gt;。从 vibe coding（无约束的乱写）到 spec coding（有工程规范的落地），本质就是给模型补上 Harness 这一层。模型会持续升级，但真正让你稳定产出的是那套看不见的工程系统。&lt;/p&gt;&lt;/section&gt;</content:encoded></item><item><title>Agent 工作流生态的三层解剖：方法论、运行时与实践</title><link>https://heaven-1314.github.io/posts/agent-workflow-three-layers/</link><guid isPermaLink="true">https://heaven-1314.github.io/posts/agent-workflow-three-layers/</guid><description>把不同层次的 Agent 项目放在同一平面比功能，是最常见的调研错误。方法论层教 Agent 怎么干活，运行时层让它长期存活，实践层把两者拼成解决方案。</description><pubDate>Wed, 05 Aug 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;调研 Agent 工作流生态时，我犯过一个典型错误：&lt;strong&gt;把不同层次的项目拉到同一平面比 feature&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;结果得到一张”功能对照表”，看似全面，实则什么也说明不了——因为它把三类完全正交的东西混在一起了。后来重做调研，按&lt;strong&gt;三层架构&lt;/strong&gt;解剖，一切都清晰了。&lt;/p&gt;
&lt;section&gt;&lt;h2&gt;三层架构&lt;a href=&quot;#三层架构&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;┌─────────────────────────────────────────────┐&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;│  实践层（pattern）                           │&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;3&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;│  把方法论层的技能组合起来，解决真实的大问题     │&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;4&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;│  ↓ 依赖                                      │&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;5&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;│  方法论层（skill 集）                         │&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;6&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;│  教 Agent 干活时遵循什么纪律                  │&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;7&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;│  ↓ 运行于                                    │&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;8&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;│  运行时层（framework）                        │&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;9&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;│  Agent 怎么部署、持久化、多渠道               │&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;10&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;└─────────────────────────────────────────────┘&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;方法论层&lt;/strong&gt;回答”Agent 干活时遵循什么纪律”。代表作是各种”技能集”——一套教 Agent 怎么写测试、怎么写计划、怎么追问的技能集合。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;运行时层&lt;/strong&gt;回答”Agent 怎么长期存活”。代表作是 Agent 运行时框架——checkpoint 持久化、沙箱、多渠道接入。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;实践层&lt;/strong&gt;回答”具体怎么把技能拼起来解决一个大问题”。代表作是”让弱模型一夜写完上万行代码”这类实践案例。&lt;/li&gt;
&lt;/ul&gt;&lt;p&gt;&lt;strong&gt;方法论层和运行时层不是竞争关系，是宿主关系&lt;/strong&gt;——一套技能集既可以跑在这个运行时上，也可以跑在那个运行时上。&lt;/p&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;方法论层的两种风格：强约束 vs 轻启发&lt;a href=&quot;#方法论层的两种风格强约束-vs-轻启发&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;同属方法论层，两套主流技能集分化成两种风格：&lt;/p&gt;





























&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;维度&lt;/th&gt;&lt;th&gt;强约束派&lt;/th&gt;&lt;th&gt;轻启发派&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;&lt;strong&gt;约束机制&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;铁律、硬门、红线表（大写、不可协商）&lt;/td&gt;&lt;td&gt;决策树 + 推荐答案&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;strong&gt;典型句式&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;”没有根因调查，就不许修&quot;&lt;/td&gt;&lt;td&gt;&quot;逐分支走下去，给出你的推荐答案”&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;strong&gt;追问机制&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;一次一问，终点是产出规范文档&lt;/td&gt;&lt;td&gt;一次一问 + 每题附推荐答案&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;strong&gt;目标用户&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;初级工程师、弱模型&lt;/td&gt;&lt;td&gt;有经验的工程师、强模型&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;p&gt;两者的理论血统同源（TDD、领域驱动设计、深模块、缝等经典软件理论），但风格分化。&lt;/p&gt;&lt;p&gt;&lt;strong&gt;关键洞察&lt;/strong&gt;：强约束和轻启发不是对错，是适配——&lt;/p&gt;&lt;blockquote&gt;&lt;p&gt;&lt;strong&gt;弱模型干活用强约束，强模型干活用轻启发。&lt;/strong&gt;&lt;/p&gt;&lt;/blockquote&gt;&lt;p&gt;弱模型没有”品味”，需要铁律和红线把它钉死；强模型有判断力，规则反而束缚它。如果你有多模型路由能力，正好可以按任务复杂度分发：简单任务给轻启发技能，关键任务给强约束技能。&lt;/p&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;运行时层的真正创新&lt;a href=&quot;#运行时层的真正创新&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;以一个”filesystem-first 的持久化 Agent 框架”为例，它的目录结构本身没什么，真正值得借鉴的是两个设计决策：&lt;/p&gt;&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;文档随包分发&lt;/strong&gt;：Agent 能在本地读到自己框架的文档，不依赖模型参数里记住的东西。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;durable = checkpointed workflow&lt;/strong&gt;：每一步存证，崩溃从断点续跑，等审批时不消耗算力。&lt;/li&gt;
&lt;/ol&gt;&lt;p&gt;它的安全哲学也值得记：&lt;strong&gt;安全边界不是绝对隔离，而是靠窄工具设计 + 权限控制 + 审批 + 幂等设计一起完成&lt;/strong&gt;。AI 生成的不可信代码进沙箱，开发者的工具在运行时里调沙箱，一行配置即可加工具级审批。&lt;/p&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;实践层最有价值的结论&lt;a href=&quot;#实践层最有价值的结论&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;实践层案例里信息密度最高的一个洞察是：&lt;/p&gt;&lt;blockquote&gt;&lt;p&gt;&lt;strong&gt;代码仓库里最有价值的不是实现代码，是那套验证机制。工程师从质检员，变成质检闸门的设计师。&lt;/strong&gt;&lt;/p&gt;&lt;/blockquote&gt;&lt;p&gt;具体到工程：TDD 红绿循环是基础设施，但真正稀缺的是&lt;strong&gt;对抗性验证&lt;/strong&gt;——比如让一个独立的子 Agent 监控测试有没有被”偷改”、用对比脚本证明新旧实现行为等价。这些机制防止的不是写错代码，是防止”弱模型绕过验证”。&lt;/p&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;结论&lt;a href=&quot;#结论&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;看 Agent 生态，先分层再比较：&lt;/p&gt;&lt;ul&gt;
&lt;li&gt;比较技能集 → 方法论层内比较（强约束 vs 轻启发）&lt;/li&gt;
&lt;li&gt;比较框架 → 运行时层内比较（持久化、沙箱、渠道）&lt;/li&gt;
&lt;li&gt;学习方法 → 去实践层找最高密度的案例&lt;/li&gt;
&lt;/ul&gt;&lt;p&gt;&lt;strong&gt;把不同层混比，是调研里最贵的错误。&lt;/strong&gt; 分层之后，每层都有清晰的评判维度，照方抓药就行。&lt;/p&gt;&lt;/section&gt;</content:encoded></item><item><title>AI 应用的成本优化：从选型到缓存的全链路</title><link>https://heaven-1314.github.io/posts/ai-cost-optimization/</link><guid isPermaLink="true">https://heaven-1314.github.io/posts/ai-cost-optimization/</guid><description>Agent 项目的成本不是&quot;用便宜的模型&quot;那么简单。从模型路由、提示压缩、缓存架构到任务编排，是一条全链路。</description><pubDate>Wed, 05 Aug 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;做 AI 应用，尤其是 Agent 系统，“你的项目怎么节约成本”是面试必问、上线必踩的题。成本不是”换便宜模型”一个开关，而是&lt;strong&gt;贯穿从模型选型到任务编排的全链路方法论&lt;/strong&gt;。&lt;/p&gt;
&lt;section&gt;&lt;h2&gt;成本从哪来&lt;a href=&quot;#成本从哪来&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;先拆清楚 AI 应用的成本结构：&lt;/p&gt;&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Token 消耗&lt;/strong&gt;：每次调用的输入 + 输出 token&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;调用次数&lt;/strong&gt;：一个用户请求触发多少次模型调用（Agent 系统常是 N 次）&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;重复推理&lt;/strong&gt;：重试、中断、幂等没做好导致的白跑&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;上下文膨胀&lt;/strong&gt;：对话历史越攒越长，每次都重传一遍&lt;/li&gt;
&lt;/ul&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;六个优化方向&lt;a href=&quot;#六个优化方向&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;section&gt;&lt;h3&gt;1. 模型选择与路由&lt;a href=&quot;#1-模型选择与路由&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;&lt;strong&gt;不是所有任务都需要最强模型。&lt;/strong&gt; 按任务复杂度动态路由：&lt;/p&gt;&lt;ul&gt;
&lt;li&gt;简单分类 / 提取 → 轻量模型（便宜、快）&lt;/li&gt;
&lt;li&gt;复杂推理 / 代码生成 → 强模型（贵但准）&lt;/li&gt;
&lt;li&gt;结构化输出 → 带 JSON mode 的模型，省 token&lt;/li&gt;
&lt;/ul&gt;&lt;p&gt;实际工程里用 new-api、LiteLLM 这类网关做路由，按任务类型分发到不同模型。一次 Agent 流程里，规划用强模型、执行用便宜的，能砍掉一大块成本。&lt;/p&gt;&lt;/section&gt;&lt;section&gt;&lt;h3&gt;2. 提示压缩与上下文治理&lt;a href=&quot;#2-提示压缩与上下文治理&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;每次调用传的上下文越短越省钱。具体手段：&lt;/p&gt;&lt;ul&gt;
&lt;li&gt;历史对话&lt;strong&gt;摘要&lt;/strong&gt;而不是全量重传&lt;/li&gt;
&lt;li&gt;只带相关文档（RAG 检索 top-k，不是把整个知识库塞进去）&lt;/li&gt;
&lt;li&gt;系统提示用 &lt;strong&gt;prompt caching&lt;/strong&gt;（Anthropic / OpenAI 都支持，缓存命中后输入 token 大幅打折）&lt;/li&gt;
&lt;/ul&gt;&lt;/section&gt;&lt;section&gt;&lt;h3&gt;3. 缓存与复用&lt;a href=&quot;#3-缓存与复用&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;相同或相似的请求，结果缓存复用，避免重复推理：&lt;/p&gt;&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;确定性请求&lt;/strong&gt;（如”总结这篇文章”）：结果落库，下次直接返回&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;半确定性请求&lt;/strong&gt;：语义缓存（embedding 相似度匹配）&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;prompt caching&lt;/strong&gt;：把固定上下文（系统提示、知识库）缓存到模型侧，命中后输入成本降 90%&lt;/li&gt;
&lt;/ul&gt;&lt;/section&gt;&lt;section&gt;&lt;h3&gt;4. 推理效率&lt;a href=&quot;#4-推理效率&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;批处理&lt;/strong&gt;：多个请求合并一次推理&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;流式输出&lt;/strong&gt;：早返回早结束，减少超时重试&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;早停 / 结构化输出&lt;/strong&gt;：让模型只输出必要内容，别啰嗦&lt;/li&gt;
&lt;/ul&gt;&lt;/section&gt;&lt;section&gt;&lt;h3&gt;5. 重复推理避免&lt;a href=&quot;#5-重复推理避免&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;Agent 系统最容易浪费的地方：&lt;strong&gt;任务失败重试，从头再跑一遍&lt;/strong&gt;。&lt;/p&gt;&lt;p&gt;解法：&lt;/p&gt;&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;任务状态检查点&lt;/strong&gt;：每步做完落库，失败从断点续跑，不从头&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;幂等设计&lt;/strong&gt;：同一个任务执行多次结果一致，重试不产生副作用&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;结果缓存按任务 ID&lt;/strong&gt;：重试时先查缓存&lt;/li&gt;
&lt;/ul&gt;&lt;/section&gt;&lt;section&gt;&lt;h3&gt;6. 成本监控与归因&lt;a href=&quot;#6-成本监控与归因&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;不能笼统知道”这个月花了 500 块”，要能拆到&lt;strong&gt;具体功能 / 链式调用 / 用户&lt;/strong&gt;。建立精细化追踪：&lt;/p&gt;&lt;ul&gt;
&lt;li&gt;每次调用记录：哪个功能触发、走了哪个模型、消耗多少 token&lt;/li&gt;
&lt;li&gt;按功能 / 用户 / 链路聚合，找出成本大头&lt;/li&gt;
&lt;li&gt;大头先优化（二八定律——20% 的调用常消耗 80% 的成本）&lt;/li&gt;
&lt;/ul&gt;&lt;/section&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;一个常被忽略的点：上下文增长&lt;a href=&quot;#一个常被忽略的点上下文增长&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;长对话 Agent 最隐蔽的成本是&lt;strong&gt;上下文雪球&lt;/strong&gt;。第 10 轮对话时，前 9 轮历史每轮都要重新传一遍，token 消耗线性增长。&lt;/p&gt;&lt;p&gt;解法：&lt;/p&gt;&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;记忆分层&lt;/strong&gt;：近期对话保原文，远期对话压缩成摘要&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;记忆系统&lt;/strong&gt;：把事实沉淀到外部存储（向量库 / 知识图谱），用时检索，不靠对话历史承载&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;会话窗口管理&lt;/strong&gt;：超过阈值自动截断 / 摘要&lt;/li&gt;
&lt;/ul&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;成本和质量的平衡&lt;a href=&quot;#成本和质量的平衡&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;成本优化的红线是&lt;strong&gt;不显著损害性能&lt;/strong&gt;。判断方法：&lt;/p&gt;&lt;ul&gt;
&lt;li&gt;核心链路（影响用户体验的关键步骤）→ 用强模型，不省&lt;/li&gt;
&lt;li&gt;辅助链路（路由判断、简单提取、格式转换）→ 用便宜模型&lt;/li&gt;
&lt;li&gt;可缓存链路 → 缓存优先，模型兜底&lt;/li&gt;
&lt;/ul&gt;&lt;p&gt;一刀切换便宜模型是最差的做法——核心链路质量下降直接毁产品。&lt;/p&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;结论&lt;a href=&quot;#结论&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;AI 成本优化是个系统工程：&lt;strong&gt;选型（路由）+ 输入（提示压缩 / 缓存）+ 执行（幂等 / 续跑）+ 输出（结构化 / 监控）&lt;/strong&gt;。每一步都有优化空间，组合起来能把成本压一个数量级。&lt;/p&gt;&lt;p&gt;面试时被问”做了哪些成本优化”，&lt;strong&gt;回答全链路 + 量化结果&lt;/strong&gt;（降了百分之多少）才是工程师视角；只答”换便宜模型”是用户视角。&lt;/p&gt;&lt;/section&gt;</content:encoded></item><item><title>从 20 多个 bug 里归纳出的 6 类调试模式</title><link>https://heaven-1314.github.io/posts/bug-patterns-catalog/</link><guid isPermaLink="true">https://heaven-1314.github.io/posts/bug-patterns-catalog/</guid><description>大部分 bug 不是孤立的，它们聚成几类模式。识别模式，比逐个记住 bug 高效得多。</description><pubDate>Wed, 05 Aug 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;写代码踩的 bug 成千上万，但归起类来，&lt;strong&gt;大部分 bug 都是同一类模式的重复&lt;/strong&gt;。记录过 20 多个 bug 后，我发现它们收敛成 6 类模式。识别模式，比记住每个 bug 的细节高效得多。&lt;/p&gt;
&lt;section&gt;&lt;h2&gt;模式一：编码 / 字符集问题&lt;a href=&quot;#模式一编码--字符集问题&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;典型症状：中文文件名乱码、特殊字符导致接口 500、自定义名称不被库识别。&lt;/p&gt;&lt;p&gt;常见根因：&lt;/p&gt;&lt;ul&gt;
&lt;li&gt;默认编码不对（比如归档工具用旧编码处理中文名）&lt;/li&gt;
&lt;li&gt;自定义名称和库内置字典对不上（tiktoken 不认模型名）&lt;/li&gt;
&lt;li&gt;文件名含特殊字符，URL 编码 / 解码不对称&lt;/li&gt;
&lt;/ul&gt;&lt;p&gt;&lt;strong&gt;通用修复思路&lt;/strong&gt;：任何处理文件名、编码、名称的地方，都要验证字符串&lt;strong&gt;全链路&lt;/strong&gt;传递正确——编码 → 传输 → 解码，任一段错就乱。&lt;/p&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;模式二：数据计算精度&lt;a href=&quot;#模式二数据计算精度&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;典型症状：统计值反复出错、数据重复写入、单日工时异常巨大。&lt;/p&gt;&lt;p&gt;常见根因：&lt;/p&gt;&lt;ul&gt;
&lt;li&gt;复杂计算逻辑在重构中&lt;strong&gt;丢失&lt;/strong&gt;了某一段&lt;/li&gt;
&lt;li&gt;缺少防重复写入机制（幂等）&lt;/li&gt;
&lt;li&gt;总账没按基准摊分（单日 120h 这种不合理值没人挡）&lt;/li&gt;
&lt;/ul&gt;&lt;p&gt;&lt;strong&gt;通用修复思路&lt;/strong&gt;：数据计算加&lt;strong&gt;防御层&lt;/strong&gt;——输入验证 → 中间校验 → 输出合理性检查。不要只在末端验证。&lt;/p&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;模式三：配置 / 环境问题&lt;a href=&quot;#模式三配置--环境问题&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;典型症状：服务连不上、构建撑爆磁盘、参数边界报错。&lt;/p&gt;&lt;p&gt;常见根因：&lt;/p&gt;&lt;ul&gt;
&lt;li&gt;服务绑定 &lt;code&gt;127.0.0.1&lt;/code&gt; 而不是 &lt;code&gt;0.0.0.0&lt;/code&gt;，外部连不上&lt;/li&gt;
&lt;li&gt;构建前不检查磁盘空间，直接打满&lt;/li&gt;
&lt;li&gt;参数校验边界两端不一致&lt;/li&gt;
&lt;/ul&gt;&lt;p&gt;&lt;strong&gt;通用修复思路&lt;/strong&gt;：环境问题先查四件事——&lt;strong&gt;端口绑定、磁盘空间、权限、启动顺序&lt;/strong&gt;。四个都不对再深挖。&lt;/p&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;模式四：前端 / API 契约&lt;a href=&quot;#模式四前端--api-契约&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;典型症状：API 重定向导致功能瘫痪、页面渲染异常、字段对不上。&lt;/p&gt;&lt;p&gt;常见根因：&lt;/p&gt;&lt;ul&gt;
&lt;li&gt;API 尾部斜杠 / 大小写不宽容，重定向把请求带飞&lt;/li&gt;
&lt;li&gt;单体 HTML 里函数重复定义互相覆盖&lt;/li&gt;
&lt;li&gt;前端改了字段名，后端没同步&lt;/li&gt;
&lt;/ul&gt;&lt;p&gt;&lt;strong&gt;通用修复思路&lt;/strong&gt;：API 对输入&lt;strong&gt;宽容&lt;/strong&gt;（尾部斜杠、大小写）；前端改完跑&lt;strong&gt;全功能回归&lt;/strong&gt;，不只测改的那一页。&lt;/p&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;模式五：数据管道&lt;a href=&quot;#模式五数据管道&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;典型症状：数据在链路里”变了样”，源头没问题、结果不对。&lt;/p&gt;&lt;p&gt;核心认知：数据从源头到页面经过多个阶段，&lt;strong&gt;每个阶段都可能引入偏差&lt;/strong&gt;——拉取不全、解析断字段、清洗边界错、计算重复、渲染不匹配。&lt;/p&gt;&lt;p&gt;&lt;strong&gt;通用修复思路&lt;/strong&gt;：管道逐阶段加检查点（详见&lt;a href=&quot;/posts/data-pipeline-stage-validation&quot;&gt;数据管道的六阶段验证&lt;/a&gt;），让问题当场暴露在哪一段，而不是最后才知道。&lt;/p&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;模式六：对象存在 ≠ 功能可用&lt;a href=&quot;#模式六对象存在--功能可用&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;典型症状：配置”看起来都在”，服务”看着活着”，但用户路径就是不通。&lt;/p&gt;&lt;p&gt;常见根因：&lt;/p&gt;&lt;ul&gt;
&lt;li&gt;路由对象存在但实际不可达&lt;/li&gt;
&lt;li&gt;进程存活但功能已挂&lt;/li&gt;
&lt;li&gt;映射条目在但读不了数据&lt;/li&gt;
&lt;/ul&gt;&lt;p&gt;&lt;strong&gt;通用修复思路&lt;/strong&gt;：任何状态判断都验证&lt;strong&gt;最终用户路径&lt;/strong&gt;（真实读取、真实连接），而不是只看对象 / 进程 / 配置存在。&lt;/p&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;调试通用流程&lt;a href=&quot;#调试通用流程&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;识别出模式后，调试本身也有固定流程：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;复现 → 确认不是环境/配置问题 → 二分定位到最少代码行 → 加日志看中间状态&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;→ 查历史（有没有同类 bug 记录过）→ 最小改动修复 → 写测试防回归 → 记录沉淀&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;其中两条最容易被跳过：&lt;strong&gt;二分定位&lt;/strong&gt;（一次改一堆再试是浪费）和&lt;strong&gt;查历史&lt;/strong&gt;（大概率有人踩过，先搜再动手）。&lt;/p&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;结论&lt;a href=&quot;#结论&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;blockquote&gt;&lt;p&gt;&lt;strong&gt;bug 是模式，不是事件。&lt;/strong&gt;&lt;/p&gt;&lt;/blockquote&gt;&lt;p&gt;遇到 bug 先归类——是编码、计算、环境、契约、管道、还是状态判断？归到类，修复思路就出来了。再对照通用调试流程走一遍，基本都能落。&lt;/p&gt;&lt;p&gt;把 bug 沉淀成模式库，比记住”这个 bug 怎么修”值钱得多——因为模式能迁移到下一个项目。&lt;/p&gt;&lt;/section&gt;</content:encoded></item><item><title>口径变更的三层陷阱：改代码 ≠ 改数据 ≠ 改缓存</title><link>https://heaven-1314.github.io/posts/caliber-change-three-layers/</link><guid isPermaLink="true">https://heaven-1314.github.io/posts/caliber-change-three-layers/</guid><description>业务统计口径一变，代码、历史数据、展示缓存三层都要动。漏掉任何一层，线上数字就是&quot;改了但没变&quot;。</description><pubDate>Wed, 05 Aug 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;业务系统里，“统计口径”（某个数字到底怎么算）是&lt;strong&gt;朝令夕改&lt;/strong&gt;的高频对象。而口径变更最容易踩的坑不是”改代码”，是&lt;strong&gt;只改了代码&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;一个统计口径的变更，至少涉及三层：&lt;strong&gt;代码逻辑、历史数据、展示缓存&lt;/strong&gt;。漏掉任何一层，你看到的都是”改了但没变”。&lt;/p&gt;
&lt;section&gt;&lt;h2&gt;陷阱一：改代码 ≠ 改数据&lt;a href=&quot;#陷阱一改代码--改数据&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;场景：某个统计从”排除 X”翻转为”纳入 X”。&lt;/p&gt;&lt;p&gt;你把代码改了：查询逻辑、聚合逻辑、前端显示全改了，单测也过了。但线上的数字没变。&lt;/p&gt;&lt;p&gt;为什么？因为&lt;strong&gt;过去的历史数据在”排除 X”阶段就已经被清理掉了&lt;/strong&gt;。X 现在根本不在数据库里，代码再对，也无米下锅。&lt;/p&gt;&lt;blockquote&gt;&lt;p&gt;&lt;strong&gt;口径从”排除 X”翻转为”纳入 X”时，多问一句：X 现在在数据库里吗？&lt;/strong&gt;
如果之前是”同步即删”的设计，光改代码不够，必须触发一次全量重同步，让新逻辑把 X 重新拉回来。&lt;/p&gt;&lt;/blockquote&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;陷阱二：改生成端 ≠ 清展示端缓存&lt;a href=&quot;#陷阱二改生成端--清展示端缓存&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;场景：数字是 AI 生成的结论。你改了生成规则（比如禁用一个指标），提示词对了、喂的数据对了、服务重启了、单测过了。用户看到的结论&lt;strong&gt;还是旧的&lt;/strong&gt;。&lt;/p&gt;&lt;p&gt;为什么？因为展示端是&lt;strong&gt;读结论缓存表&lt;/strong&gt;的，旧记录还在表里，页面读缓存直接显示，根本不会触发重新生成。你改了”生产端”，但”展示端”的历史结论没清。&lt;/p&gt;&lt;p&gt;正确的闭环有四段，缺一不可：&lt;/p&gt;&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;改生成端&lt;/strong&gt;：提示词 + 喂给模型的数据&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;清展示端缓存&lt;/strong&gt;：删掉结论缓存里的旧记录&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;触发一次重算&lt;/strong&gt;：让新规则落进缓存&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;验证新输出&lt;/strong&gt;：拉一条结论，grep 旧关键词确认 0 命中&lt;/li&gt;
&lt;/ol&gt;&lt;p&gt;&lt;strong&gt;光改生成端 + 重启 ≠ 用户看到新结论。&lt;/strong&gt; 这条是”改了但没效”的典型：代码层完全正确，但展示链路里的旧状态还在。&lt;/p&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;陷阱三：改 AI 结论，剥字段比改提示词可靠&lt;a href=&quot;#陷阱三改-ai-结论剥字段比改提示词可靠&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;接上面的场景：你想让 AI 结论里不再出现某个指标（比如加班时长）。光在提示词里加一句”禁止使用 X”够吗？&lt;strong&gt;不够。&lt;/strong&gt; 模型还是会从喂给它的数据字段里自己推出来——字段里有 &lt;code&gt;hours_over_8h&lt;/code&gt;、&lt;code&gt;total_overtime_h&lt;/code&gt;，它自然会用。&lt;/p&gt;&lt;blockquote&gt;&lt;p&gt;&lt;strong&gt;模型的结论口径 = 喂的数据 + 提示词约束，共同决定。&lt;/strong&gt;
提示词禁令是”口头劝阻”，剥字段是”物理约束”。改 AI 口径先想：这些字段还要不要喂？能不喂就不喂。&lt;/p&gt;&lt;/blockquote&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;陷阱四：用户口述自相矛盾 → 结构化锁定&lt;a href=&quot;#陷阱四用户口述自相矛盾--结构化锁定&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;口径变更里最隐蔽的坑：&lt;strong&gt;用户自己说的话前后矛盾&lt;/strong&gt;。&lt;/p&gt;&lt;p&gt;比如需求文档里写”指标 A 改为指标 B”，用户口述却是”指标 A 改为指标 C”。两个词长得很像，语义完全不同。你按其中一句猜着改了，必然返工。&lt;/p&gt;&lt;p&gt;解法不是猜，是&lt;strong&gt;给结构化选项让用户一次拍死&lt;/strong&gt;：&lt;/p&gt;&lt;blockquote&gt;&lt;p&gt;用 4 个选项把口径问清楚（保持 A / 统一为 B / 统一为 C / 其他），附一个推荐项。代价是 1 个问题，收益是不返工。&lt;/p&gt;&lt;/blockquote&gt;&lt;p&gt;口述越模糊、术语越相近，越要用选择题，而不是靠上下文猜。&lt;/p&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;一个共性&lt;a href=&quot;#一个共性&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;这四个陷阱背后是同一个认知：&lt;strong&gt;“改了源头” ≠ “改了运行时” ≠ “改了展示链路的旧状态”&lt;/strong&gt;。代码正确只是第一层，历史数据要重新同步，展示缓存要显式失效，AI 结论要物理约束喂的数据。&lt;/p&gt;&lt;blockquote&gt;&lt;p&gt;&lt;strong&gt;口径变更不是一次代码修改，是代码、数据、缓存、验证四个动作的一次组合拳。&lt;/strong&gt;&lt;/p&gt;&lt;/blockquote&gt;&lt;p&gt;下一次改任何”数字怎么算”的需求，先列这张清单再动手：改代码 → 重同步数据 → 失效缓存 → 触发重算 → 验证新输出。一步都不能省。&lt;/p&gt;&lt;/section&gt;</content:encoded></item><item><title>代码知识图谱怎么选：纯代码结构 vs 全模态知识图谱</title><link>https://heaven-1314.github.io/posts/code-knowledge-graph-selection/</link><guid isPermaLink="true">https://heaven-1314.github.io/posts/code-knowledge-graph-selection/</guid><description>想让 AI 理解代码库，&quot;函数被谁调了&quot;和&quot;系统为什么这么设计&quot;是两个层次的问题，对应两类不同的图谱工具。</description><pubDate>Wed, 05 Aug 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;想让 AI 助手”读懂”你的代码库，常见方案是建代码知识图谱。但”读代码库”其实有两种完全不同的需求，对应两类不同的工具：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;代码结构图谱&lt;/strong&gt;：回答”这个函数被谁调用了？”&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;全模态知识图谱&lt;/strong&gt;：回答”这个系统的设计动机是什么？”&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;把这两类混为一谈，是选型时最常犯的错。&lt;/p&gt;
&lt;section&gt;&lt;h2&gt;两类工具的本质差异&lt;a href=&quot;#两类工具的本质差异&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;&lt;strong&gt;纯代码结构图谱&lt;/strong&gt;：源码 → 语法分析（AST）→ 存进数据库 → 一个查询工具。只含代码结构：函数、类、调用关系、导入关系。&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;源码 → AST → 数据库 → &quot;authMiddleware 被谁调用了？&quot;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;&lt;strong&gt;全模态知识图谱&lt;/strong&gt;：代码 + 文档 + PDF + 图片全部索引 → 代码走语法分析，文档/图片走 LLM 提取概念 → 存进图结构 → 交互式可视化 + 多种查询。&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;代码 + 文档 + PDF + 图片 → AST(代码) + LLM(文档) → 图谱 → 可视化 + 查询&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;对比表&lt;a href=&quot;#对比表&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;

















































&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;维度&lt;/th&gt;&lt;th&gt;纯代码结构&lt;/th&gt;&lt;th&gt;全模态知识图谱&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;索引内容&lt;/td&gt;&lt;td&gt;只有代码&lt;/td&gt;&lt;td&gt;代码 + 文档 + 图片 + PDF&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;代码提取&lt;/td&gt;&lt;td&gt;语法分析 AST&lt;/td&gt;&lt;td&gt;语法分析 AST（相同）&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;文档 / 图片&lt;/td&gt;&lt;td&gt;❌ 不支持&lt;/td&gt;&lt;td&gt;✅ LLM 提取概念和关系&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;索引成本&lt;/td&gt;&lt;td&gt;零（纯本地解析）&lt;/td&gt;&lt;td&gt;文档部分走 LLM（有 token 费）&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;输出&lt;/td&gt;&lt;td&gt;文本查询结果&lt;/td&gt;&lt;td&gt;HTML 交互图 + JSON + 多种格式&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;”为什么”&lt;/td&gt;&lt;td&gt;❌ 只有结构&lt;/td&gt;&lt;td&gt;✅ 从注释/docstring 提取设计动机&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;社区发现&lt;/td&gt;&lt;td&gt;❌&lt;/td&gt;&lt;td&gt;✅ 自动聚类相似模式&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;置信度&lt;/td&gt;&lt;td&gt;无&lt;/td&gt;&lt;td&gt;✅ 区分事实（抽取）与推测（推断）&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;四个典型场景怎么选&lt;a href=&quot;#四个典型场景怎么选&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;&lt;strong&gt;场景一：日常代码导航&lt;/strong&gt;——“上传接口在哪个文件？调用链是什么？”
两个都能做，但纯代码结构更轻更快。&lt;/p&gt;&lt;p&gt;&lt;strong&gt;场景二：跨项目 Bug 模式&lt;/strong&gt;——“ZIP 编码问题在 A 和 B 两个项目都出现过，它们有共同点吗？”
纯代码结构看不到跨项目概念；全模态图谱的社区发现会把两个 bug 自动聚类到一起。&lt;/p&gt;&lt;p&gt;&lt;strong&gt;场景三：新人接手项目&lt;/strong&gt;——“系统整体架构什么样？关键设计决策是什么？”
纯代码结构能给结构，但没有”为什么”；全模态图谱能提取设计动机、输出架构报告。&lt;/p&gt;&lt;p&gt;&lt;strong&gt;场景四：文档与代码联动&lt;/strong&gt;——“这份文档说的修复，对应代码里哪个函数？”
纯代码结构不索引文档；全模态图谱把文档和代码放进同一个图谱，直接关联。&lt;/p&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;选型建议&lt;a href=&quot;#选型建议&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;&lt;strong&gt;如果你的资产只有代码&lt;/strong&gt; → 纯代码结构图谱（零成本、极简、快）。&lt;/p&gt;&lt;p&gt;&lt;strong&gt;如果代码 + 文档（wiki / bug 记录 / 设计文档）都要&lt;/strong&gt; → 全模态知识图谱。代码部分走语法分析（零 token），文档部分走 LLM（有缓存，只处理一次），成本可控。&lt;/p&gt;&lt;p&gt;&lt;strong&gt;如果两个都要，先装轻的。&lt;/strong&gt; 纯代码结构解决 80% 的日常导航需求；等文档资产多起来，再补全模态的做跨项目分析。&lt;/p&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;一个务实的组合&lt;a href=&quot;#一个务实的组合&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;很多人最终会走到”分层”的路线：&lt;/p&gt;&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;代码导航&lt;/strong&gt;用轻量的结构图谱（毫秒级响应、零成本）&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;知识沉淀&lt;/strong&gt;用文档型知识库（人机共用、版本可控）&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;跨项目洞察&lt;/strong&gt;用全模态图谱（按需建，处理文档资产）&lt;/li&gt;
&lt;/ul&gt;&lt;p&gt;三个各司其职，而不是一个工具试图解决所有问题。&lt;/p&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;结论&lt;a href=&quot;#结论&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;选型先分清问题：&lt;/p&gt;&lt;blockquote&gt;&lt;p&gt;&lt;strong&gt;“函数被谁调用”是结构问题，用代码结构图谱；“系统为什么这么设计”是知识问题，用全模态图谱。&lt;/strong&gt;&lt;/p&gt;&lt;/blockquote&gt;&lt;p&gt;先回答你要解决哪个问题，再选工具。两个都答不出”哪个适合你”，那就先上轻的那个。&lt;/p&gt;&lt;/section&gt;</content:encoded></item><item><title>定时任务凌晨配置白天跑？先查时区</title><link>https://heaven-1314.github.io/posts/cron-timezone-trap/</link><guid isPermaLink="true">https://heaven-1314.github.io/posts/cron-timezone-trap/</guid><description>守护进程内置的定时任务如果用 UTC 解析 cron，配置的&quot;凌晨低峰&quot;实际会在北京时间上午 10 点跑——正好撞高峰。</description><pubDate>Wed, 05 Aug 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;线上服务每天上午 10 点准时出现几百条超时/500，持续十几分钟，然后”自己好了”。这种幽灵故障，90% 的根因不是业务代码——&lt;strong&gt;是定时任务的时区错位&lt;/strong&gt;。&lt;/p&gt;
&lt;section&gt;&lt;h2&gt;典型症状&lt;a href=&quot;#典型症状&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;ul&gt;
&lt;li&gt;服务每天固定时刻出现几百条超时/500，持续十几分钟，然后自愈&lt;/li&gt;
&lt;li&gt;配置/文档里写的是凌晨低峰执行维护，实际运行时间却是白天工作时间&lt;/li&gt;
&lt;li&gt;维护任务耗时随数据量增长&lt;strong&gt;逐日恶化&lt;/strong&gt;（今天 2 分钟，下周 20 分钟）&lt;/li&gt;
&lt;/ul&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;排查路径（按序，命中率高）&lt;a href=&quot;#排查路径按序命中率高&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;&lt;strong&gt;1. 按分钟聚合 ERROR/WARN，找爆发窗口&lt;/strong&gt;
不是看单条错误，而是看&lt;strong&gt;分布&lt;/strong&gt;。&lt;code&gt;grep &apos;&quot;level&quot;:&quot;ERROR&quot;&apos; log | 按 time 分桶统计&lt;/code&gt;，错误会聚成一个明显的尖峰。&lt;/p&gt;&lt;p&gt;&lt;strong&gt;2. 与维护任务起止时间对齐&lt;/strong&gt;
找 VACUUM / GC / backup / checkpoint 这类任务的 Start/Complete 日志，比对是否与错误窗口严丝合缝。能对上 = 找到元凶。&lt;/p&gt;&lt;p&gt;&lt;strong&gt;3. 看维护任务耗时趋势&lt;/strong&gt;
VACUUM 之类每天都会记录 duration，统计 7 天趋势——持续恶化 = 数据增长型问题。&lt;/p&gt;&lt;p&gt;&lt;strong&gt;4. 源码确认 Timezone（关键一步）&lt;/strong&gt;
&lt;code&gt;grep Timezone&lt;/code&gt; 调度注册代码。&lt;strong&gt;凡是 &lt;code&gt;Timezone: &quot;UTC&quot;&lt;/code&gt; + 服务器在 UTC+8，直接把 UTC 时间 +8 才是真实执行时刻&lt;/strong&gt;。&lt;/p&gt;&lt;p&gt;举例：cron 写 &lt;code&gt;0 2 * * *&lt;/code&gt;（凌晨 2 点），如果调度用 UTC，那它在 UTC+8 就是&lt;strong&gt;上午 10 点&lt;/strong&gt; 执行——工作时间黄金时段。&lt;/p&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;两条铁律&lt;a href=&quot;#两条铁律&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;&lt;strong&gt;铁律一&lt;/strong&gt;：配置”低峰时间”前，先确认调度时区。&lt;strong&gt;凌晨配置白天执行，先查时区，别查业务逻辑。&lt;/strong&gt;&lt;/p&gt;&lt;p&gt;&lt;strong&gt;铁律二&lt;/strong&gt;：排查”固定时刻故障”时，&lt;strong&gt;不要一开始就查业务代码&lt;/strong&gt;，先看有没有每日维护任务在这个时刻跑（VACUUM / 备份 / 清理 / 缓存重建都是候选）。&lt;/p&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;放大因素：数据只进不出&lt;a href=&quot;#放大因素数据只进不出&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;这类故障常被一个隐藏问题放大：&lt;strong&gt;存储策略默认关闭清理&lt;/strong&gt;。&lt;/p&gt;&lt;p&gt;请求日志只进不出，DB 7 天涨到 8GB，所有全表操作（VACUUM / 备份 / 查询）逐日变慢，维护窗口越拖越长，最终撞进工作时间。&lt;/p&gt;&lt;p&gt;教训：&lt;strong&gt;“默认不清理”对日志型表是定时炸弹&lt;/strong&gt;。启用保留策略（如请求日志保留 7 天），让库停在固定水位。&lt;/p&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;守护进程的”假死”特性&lt;a href=&quot;#守护进程的假死特性&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;守护进程维护类操作（VACUUM / 全量备份）对在线服务的杀伤方式很特别：&lt;strong&gt;锁库期间所有请求超时 → 对外表现为 500 或超时，且进程本身没崩&lt;/strong&gt;。&lt;/p&gt;&lt;p&gt;监控看起来一切正常（CPU 不高、内存正常、进程存活），但用户疯狂 500。这是”假死”不是”崩溃”，不会触发重启告警，特别容易漏判。&lt;/p&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;结论&lt;a href=&quot;#结论&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;记住这套症状指纹：&lt;strong&gt;每日固定时刻 500 + 维护任务在同一时刻 + 进程没崩&lt;/strong&gt;。下次遇到，第一反应不是去读业务代码，而是 &lt;code&gt;grep Timezone&lt;/code&gt; 看调度时区——这一步能省你一整天瞎排查。&lt;/p&gt;&lt;/section&gt;</content:encoded></item><item><title>数据管道的六阶段验证：别等最后才发现错误</title><link>https://heaven-1314.github.io/posts/data-pipeline-stage-validation/</link><guid isPermaLink="true">https://heaven-1314.github.io/posts/data-pipeline-stage-validation/</guid><description>数据从源头流到页面要过六道关口，每一道都可能引入偏差。在最后一个阶段才抓问题，等于把全部赌注押在末端。</description><pubDate>Wed, 05 Aug 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;数据系统上线后最难受的事：&lt;strong&gt;流程全跑完了，页面数字不对，但你不知道错在哪一步。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;一个典型数据管道长这样：源系统 → 拉取 → 解析 → 清洗 → 计算 → 评分 → 渲染。数据在六个阶段之间流动，&lt;strong&gt;每个阶段都可能悄悄引入偏差&lt;/strong&gt;。如果在最后一个阶段才发现问题，你得逆着整条链路往回查，定位成本成倍上升。&lt;/p&gt;
&lt;section&gt;&lt;h2&gt;六道关口，各自的问题&lt;a href=&quot;#六道关口各自的问题&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;拉取 → 解析 → 清洗 → 计算 → 评分 → 渲染&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;↑       ↑      ↑      ↑      ↑      ↑&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;3&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;检查点  检查点  检查点  检查点  检查点  检查点&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;
































&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;阶段&lt;/th&gt;&lt;th&gt;常见问题&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;&lt;strong&gt;拉取&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;分页不全、权限不足、接口超时、中途断流&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;strong&gt;解析&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;字段缺失、类型错误、编码乱码&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;strong&gt;清洗&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;过滤条件不对、边界情况遗漏&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;strong&gt;计算&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;公式错误、日期基准不同、重复计算&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;strong&gt;评分&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;权重不合理、缺少归一化、口径口径漂移&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;strong&gt;渲染&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;数据格式不匹配、前端解析失败&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;p&gt;每一关都是独立的出错源。最坑的是&lt;strong&gt;相邻两关的偏差会互相掩盖&lt;/strong&gt;——上游多算了一倍，下游以为那是正确值，一路带到最末端。&lt;/p&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;核心教训：每阶段都要有验证点&lt;a href=&quot;#核心教训每阶段都要有验证点&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;不要在最后一个阶段才发现数据问题。做法是&lt;strong&gt;在管道每一段出口都放一个”检查点”&lt;/strong&gt;，用断言守住：&lt;/p&gt;&lt;ul&gt;
&lt;li&gt;拉取完 → 断言”条数 &amp;gt;= 0 且与源系统一致”（分页有没有拉全）&lt;/li&gt;
&lt;li&gt;解析完 → 断言”关键字段全部非空、类型正确”（有没有解析断字段）&lt;/li&gt;
&lt;li&gt;清洗完 → 断言”过滤后的集合等于预期集合”（过滤条件有没有改错边界）&lt;/li&gt;
&lt;li&gt;计算完 → 断言”总账与明细账对得上”（有没有重复计算）&lt;/li&gt;
&lt;li&gt;渲染前 → 断言”数据格式能被前端直接消费”（前端不用自己推导）&lt;/li&gt;
&lt;/ul&gt;&lt;p&gt;&lt;strong&gt;每段出口的断言，把”最后才知道错”变成”当场就知道错在哪一段”。&lt;/strong&gt;&lt;/p&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;边界场景是最常漏的&lt;a href=&quot;#边界场景是最常漏的&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;很多管道 bug 不是主流程错，是&lt;strong&gt;边界场景没覆盖&lt;/strong&gt;：&lt;/p&gt;&lt;ul&gt;
&lt;li&gt;空数据（今天一条都没有，不是异常，但要能显示”0”）&lt;/li&gt;
&lt;li&gt;极值（单条记录异常巨大，不能把均值拉爆）&lt;/li&gt;
&lt;li&gt;重复数据（同一批被拉了两次，幂等要挡住）&lt;/li&gt;
&lt;li&gt;口径切换（统计规则变了，历史数据要不要重新算？）&lt;/li&gt;
&lt;/ul&gt;&lt;p&gt;给每个边界场景写一条独立断言，而不是只在”happy path”上验证。&lt;/p&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;和”缓存失效”是一对&lt;a href=&quot;#和缓存失效是一对&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;管道验证守的是”&lt;strong&gt;算得对不对&lt;/strong&gt;”，&lt;a href=&quot;/posts/write-op-must-invalidate-cache&quot;&gt;缓存同步失效&lt;/a&gt;守的是”&lt;strong&gt;看的是不是新结果&lt;/strong&gt;”。一个管上游、一个管下游，两者都漏才是”改了完全没反应”。&lt;/p&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;结论&lt;a href=&quot;#结论&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;数据系统的可靠性不靠”仔细写代码”，靠&lt;strong&gt;在管道的每一段出口都放检查点&lt;/strong&gt;。六个阶段，六道关卡，任何一道先发现问题，你就知道往哪一段回查，而不是把整个管道从头到尾再审一遍。&lt;/p&gt;&lt;blockquote&gt;&lt;p&gt;&lt;strong&gt;别等最后一个阶段才发现数据问题——每一段都加验证点。&lt;/strong&gt;&lt;/p&gt;&lt;/blockquote&gt;&lt;/section&gt;</content:encoded></item><item><title>Docker 部署的红线经验：什么时候用、什么时候别用</title><link>https://heaven-1314.github.io/posts/docker-deploy-redlines/</link><guid isPermaLink="true">https://heaven-1314.github.io/posts/docker-deploy-redlines/</guid><description>Docker 不是所有部署的答案。用错时机、碰错红线，一次构建打满系统盘就能让所有服务一起卡死。</description><pubDate>Wed, 05 Aug 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Docker 很香，但&lt;strong&gt;不是所有部署都该用 Docker&lt;/strong&gt;。用错时机、碰错红线，付出的代价远超省下的环境隔离时间。&lt;/p&gt;
&lt;section&gt;&lt;h2&gt;什么时候用 Docker&lt;a href=&quot;#什么时候用-docker&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;上游有官方镜像且维护活跃&lt;/strong&gt;：装一个官方长期维护的服务，直接用现成镜像&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;依赖复杂&lt;/strong&gt;：Python / Node 多版本共存，环境互相污染，容器隔离干净&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;需要隔离环境&lt;/strong&gt;：不同项目要不同版本的库&lt;/li&gt;
&lt;/ul&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;什么时候别用 Docker&lt;a href=&quot;#什么时候别用-docker&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;简单服务&lt;/strong&gt;（单个 Python / Node 进程）：直接 systemd 托管更轻、更好排查&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;轻量工具&lt;/strong&gt;：能走标准输入输出的，不需要容器&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;大镜像构建（&amp;gt;5GB）&lt;/strong&gt;：构建峰值可能吃掉十几个 G，系统盘扛不住&lt;/li&gt;
&lt;/ul&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;四条红线（每一条都踩出过事故）&lt;a href=&quot;#四条红线每一条都踩出过事故&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;section&gt;&lt;h3&gt;红线一：容器运行时的 daemon 绝不能随意重启&lt;a href=&quot;#红线一容器运行时的-daemon-绝不能随意重启&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;&lt;code&gt;systemctl restart docker&lt;/code&gt; 会杀掉&lt;strong&gt;所有&lt;/strong&gt;容器，等于一次全站停机。如果你在服务器上还连着对话、跑着业务，这一下全断。&lt;/p&gt;&lt;blockquote&gt;&lt;p&gt;要重启容器，用 &lt;code&gt;docker restart &amp;lt;容器名&amp;gt;&lt;/code&gt;。daemon 重启是核弹，单容器重启是手术刀。&lt;/p&gt;&lt;/blockquote&gt;&lt;/section&gt;&lt;section&gt;&lt;h3&gt;红线二：构建前必须看磁盘空间&lt;a href=&quot;#红线二构建前必须看磁盘空间&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;一次构建峰值吃掉 25GB，系统盘总共几十 G——直接打满，所有服务卡死。&lt;/p&gt;&lt;p&gt;规则：&lt;/p&gt;&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;构建大东西之前先 &lt;code&gt;df -h&lt;/code&gt;&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;大项目、大数据集放数据盘&lt;/li&gt;
&lt;li&gt;系统盘容量是红线，任何超过几个 G 的操作都危险&lt;/li&gt;
&lt;/ul&gt;&lt;/section&gt;&lt;section&gt;&lt;h3&gt;红线三：容器内访问宿主机，别用 127.0.0.1&lt;a href=&quot;#红线三容器内访问宿主机别用-127001&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;Docker 网桥下，容器里的 &lt;code&gt;127.0.0.1&lt;/code&gt; 是容器自己，不是宿主机。容器内要访问宿主机服务：&lt;/p&gt;&lt;ul&gt;
&lt;li&gt;用 &lt;code&gt;host.docker.internal&lt;/code&gt;（Docker 提供的主机别名）&lt;/li&gt;
&lt;li&gt;宿主机的服务要监听 &lt;code&gt;0.0.0.0&lt;/code&gt; 而不是 &lt;code&gt;127.0.0.1&lt;/code&gt;，否则容器连不上&lt;/li&gt;
&lt;/ul&gt;&lt;/section&gt;&lt;section&gt;&lt;h3&gt;红线四：常驻服务必须 systemd 托管&lt;a href=&quot;#红线四常驻服务必须-systemd-托管&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;不要手动 &lt;code&gt;nohup&lt;/code&gt; 起一个长期服务——服务器重启后它就没了。&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;常驻服务 → systemd unit + systemctl enable（开机自启）&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;/section&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;容器网络一句话总结&lt;a href=&quot;#容器网络一句话总结&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;容器内 127.0.0.1 = 容器自己&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;宿主机服务 = host.docker.internal（需监听 0.0.0.0）&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;长期运行的隐性风险&lt;a href=&quot;#长期运行的隐性风险&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;容器长期不重启会&lt;strong&gt;隐藏 latent bug&lt;/strong&gt;：内存缓慢泄漏、文件句柄累积、缓存过期逻辑从没触发过。定期滚动重启 + 状态巡检，让这些问题在可控时暴露，而不是在某个高峰期崩。&lt;/p&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;结论&lt;a href=&quot;#结论&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;Docker 部署的四条红线：&lt;/p&gt;&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;daemon 不随便重启&lt;/strong&gt;，单容器重启走 &lt;code&gt;docker restart&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;构建前先 &lt;code&gt;df -h&lt;/code&gt;&lt;/strong&gt;，大东西放数据盘&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;容器访问宿主机用 &lt;code&gt;host.docker.internal&lt;/code&gt;&lt;/strong&gt;，别用 127.0.0.1&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;常驻服务 systemd 托管&lt;/strong&gt;，别裸跑&lt;/li&gt;
&lt;/ol&gt;&lt;blockquote&gt;&lt;p&gt;&lt;strong&gt;Docker 解决环境隔离，但环境隔离解决不了磁盘、网络、进程管理的问题。用对时机，别让工具绑架架构。&lt;/strong&gt;&lt;/p&gt;&lt;/blockquote&gt;&lt;/section&gt;</content:encoded></item><item><title>从 Vibe Coding 到 Spec Coding：AI 编码该有的样子</title><link>https://heaven-1314.github.io/posts/from-vibe-to-spec-coding/</link><guid isPermaLink="true">https://heaven-1314.github.io/posts/from-vibe-to-spec-coding/</guid><description>Vibe coding 让人爽在&quot;一句话出代码&quot;，但项目一旦长期化就崩。Spec coding 不是流程化，是把项目事实从对话里沉淀到结构里。</description><pubDate>Wed, 05 Aug 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;上一篇聊了 &lt;a href=&quot;/posts/agent-model-plus-harness&quot;&gt;Agent = Model + Harness&lt;/a&gt;，说真正让 AI 编码稳定的是 Harness 层。这篇接下去：&lt;strong&gt;Harness 落地到日常编码，就是 spec coding——规范编码。&lt;/strong&gt; 它和 vibe coding 是两种完全不同的工作方式。&lt;/p&gt;
&lt;section&gt;&lt;h2&gt;两种编码风格&lt;a href=&quot;#两种编码风格&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;&lt;strong&gt;Vibe coding（凭感觉编码）&lt;/strong&gt;：
打开 AI，把需求一股脑丢过去，让它”帮我写个 XXX”。AI 生成什么你就用什么，不顺再让它改。爽在即时反馈，像在飙车。&lt;/p&gt;&lt;p&gt;&lt;strong&gt;Spec coding（规范编码）&lt;/strong&gt;：
写代码之前，先把项目规范、任务进度、上下文、经验教训&lt;strong&gt;沉淀到项目结构里&lt;/strong&gt;，再让 AI 基于这些事实干活。&lt;/p&gt;&lt;p&gt;vibe coding 不是”错”，它在 demo、原型、一次性脚本里效率极高。&lt;strong&gt;但一旦项目长期化、需要多人协作或持续迭代，vibe coding 必崩&lt;/strong&gt;——因为 AI 记不住上次怎么做的，每次都重新猜。&lt;/p&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;为什么 vibe coding 会崩&lt;a href=&quot;#为什么-vibe-coding-会崩&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;根因是&lt;strong&gt;会话失忆&lt;/strong&gt;：&lt;/p&gt;&lt;ul&gt;
&lt;li&gt;今天和 AI 说”这个项目用 camelCase 命名”，明天新开会话它又给你 snake_case&lt;/li&gt;
&lt;li&gt;上周定下的”错误码用数字枚举”，这周它给你写字符串&lt;/li&gt;
&lt;li&gt;你踩过的坑，下次它会让你再踩一遍&lt;/li&gt;
&lt;/ul&gt;&lt;p&gt;vibe coding 把所有”应该怎么做”放在&lt;strong&gt;临时对话&lt;/strong&gt;里。对话一结束，这些事实就蒸发了。你被迫每次重新传递上下文——传少了 AI 乱来，传多了 token 爆炸。&lt;/p&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;Spec coding 的核心：把事实从对话搬到结构&lt;a href=&quot;#spec-coding-的核心把事实从对话搬到结构&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;spec coding 不是”把每件事都流程化”（那是官僚主义）。它的重点是：&lt;/p&gt;&lt;blockquote&gt;&lt;p&gt;&lt;strong&gt;沉淀长期有复用价值的项目事实，不在于把每件事都流程化。&lt;/strong&gt;&lt;/p&gt;&lt;/blockquote&gt;&lt;p&gt;什么是”项目事实”：&lt;/p&gt;&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;规范&lt;/strong&gt;：命名约定、代码风格、技术栈选型、禁止行为清单&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;架构决策&lt;/strong&gt;：为什么选 A 不选 B、哪些边界不能破&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;任务进度&lt;/strong&gt;：做到哪了、下一步是什么、哪些坑已踩&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;经验教训&lt;/strong&gt;：bug 根因、修复方案、踩过的陷阱&lt;/li&gt;
&lt;/ul&gt;&lt;p&gt;这些事实原来散在对话里，spec coding 把它们搬到&lt;strong&gt;项目结构&lt;/strong&gt;里——README、ARCHITECTURE、规范文件、CLAUDE.md、任务清单、bug 日志。AI 每次干活前先读这些，自动对齐，不用你每次重讲。&lt;/p&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;三个具体做法&lt;a href=&quot;#三个具体做法&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;&lt;strong&gt;1. 规范文件&lt;/strong&gt;：项目根目录放一份规范（比如 &lt;code&gt;CLAUDE.md&lt;/code&gt;），写清”这个项目该怎么做、不该怎么做”。AI 每次进来先读。&lt;/p&gt;&lt;p&gt;&lt;strong&gt;2. 架构先行&lt;/strong&gt;：动手前先让 AI 输出功能解析 + 架构图 + 边界清单，确认无误再写代码。比直接让它”写”慢 5 分钟，但少走 2 小时弯路。&lt;/p&gt;&lt;p&gt;&lt;strong&gt;3. 经验闭环&lt;/strong&gt;：修完 bug、做完决策，&lt;strong&gt;立刻沉淀&lt;/strong&gt;到 wiki/文档里，以稳定引用（不是裸行号）记录。下次同类问题 AI 先查历史，不重踩。&lt;/p&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;它和 Harness 是一回事&lt;a href=&quot;#它和-harness-是一回事&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;spec coding 是 &lt;strong&gt;Harness 在编码流程里的具体形态&lt;/strong&gt;。Harness 是”让模型稳定干活的工程约束系统”，spec coding 是”工程师怎么用这个系统”。&lt;/p&gt;&lt;p&gt;我之前那套”永不遗忘系统”（对话记忆 + 代码图谱 + wiki 知识库）就是 spec coding 的工程实现——让我能跨会话、跨项目保留项目事实，AI 进来 5 秒就能对齐上下文。&lt;/p&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;结论&lt;a href=&quot;#结论&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;vibe coding 适合一次性，spec coding 适合长期主义。判断标准很简单：&lt;/p&gt;&lt;blockquote&gt;&lt;p&gt;&lt;strong&gt;这个项目/任务一周后还要不要继续？&lt;/strong&gt; 要 → spec coding；不要 → vibe coding 随便爽。&lt;/p&gt;&lt;/blockquote&gt;&lt;p&gt;从 vibe 到 spec 的转变，本质是给 AI 编码补上”项目事实”这一层。模型会升级，工具会更迭，但”把事实沉淀好”这件事会一直值钱。&lt;/p&gt;&lt;/section&gt;</content:encoded></item><item><title>后端工程师学前端：一条踩坑驱动的学习路径</title><link>https://heaven-1314.github.io/posts/frontend-learning-path/</link><guid isPermaLink="true">https://heaven-1314.github.io/posts/frontend-learning-path/</guid><description>后端转前端的真实路线——Next.js、React、Tailwind、组件库，以及每个技术点背后踩过的构建坑。</description><pubDate>Wed, 05 Aug 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;后端工程师学前端，最常见的坑是&lt;strong&gt;按教程从 HTML/CSS 一路学，学完还是不会做项目&lt;/strong&gt;。我的路线反过来：&lt;strong&gt;直接做项目，每个技术点靠踩坑学会&lt;/strong&gt;。&lt;/p&gt;
&lt;section&gt;&lt;h2&gt;主线：Next.js + React + Tailwind&lt;a href=&quot;#主线nextjs--react--tailwind&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Next.js&lt;/strong&gt;：React 的全栈框架，自带路由、SSR、构建工具，是现在做前端的主线&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;React&lt;/strong&gt;：函数组件 + Hooks 优先，别在类组件上浪费时间&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Tailwind CSS&lt;/strong&gt;：原子化 CSS，不用写一堆 class 文件&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;组件库&lt;/strong&gt;：shadcn/ui 风格——直接把组件源码拷进自己项目改，而不是引入整个依赖&lt;/li&gt;
&lt;/ul&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;每个技术点背后的坑&lt;a href=&quot;#每个技术点背后的坑&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;&lt;strong&gt;1. 字体加载：&lt;code&gt;next/font/google&lt;/code&gt; 会坑你&lt;/strong&gt;&lt;/p&gt;&lt;p&gt;Next.js 的 Turbopack 构建模式下，&lt;code&gt;next/font/google&lt;/code&gt; 会构建失败（Unknown font）。&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;解法：改用 @fontsource-variable/* 自托管&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;自托管的好处：字体加载稳定、不依赖 Google CDN、国内可访问。&lt;/p&gt;&lt;p&gt;&lt;strong&gt;2. Node 版本：Next.js 16 需要 Node 20+&lt;/strong&gt;&lt;/p&gt;&lt;p&gt;Node 18 不支持新版 Next.js。&lt;strong&gt;版本不匹配会在构建时爆，而不是运行时。&lt;/strong&gt; 装新项目前先确认 Node 版本。&lt;/p&gt;&lt;p&gt;&lt;strong&gt;3. Tailwind 原生模块：缺失就构建失败&lt;/strong&gt;&lt;/p&gt;&lt;p&gt;&lt;code&gt;@tailwindcss/oxide&lt;/code&gt; 这类原生模块缺失会导致构建失败。解法通常是重新安装 &lt;code&gt;node_modules&lt;/code&gt;。&lt;/p&gt;&lt;p&gt;&lt;strong&gt;4. 部署：静态导出最轻量&lt;/strong&gt;&lt;/p&gt;&lt;p&gt;纯静态页面用 &lt;code&gt;next export&lt;/code&gt; 导出 + nginx 直接指向目录，是最轻量的部署方式。需要动态能力再用服务器渲染。&lt;/p&gt;&lt;p&gt;&lt;strong&gt;5. 子路径部署：需要 basePath + nginx&lt;/strong&gt;&lt;/p&gt;&lt;p&gt;部署到 &lt;code&gt;/my-app/&lt;/code&gt; 子路径下，Next.js 要配 &lt;code&gt;basePath&lt;/code&gt;，nginx 要配对应的反代规则。不然静态资源和 API 全迷路（详见&lt;a href=&quot;/posts/nginx-subpath-deployment&quot;&gt;nginx 子路径部署&lt;/a&gt;）。&lt;/p&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;学习素材：逆向真实站点&lt;a href=&quot;#学习素材逆向真实站点&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;比看教程更快的方法：&lt;strong&gt;找一个做得好的站点，F12 逆向它的实现&lt;/strong&gt;。&lt;/p&gt;&lt;ul&gt;
&lt;li&gt;看它的技术栈（Next.js + Tailwind + 图标库 + 构建工具）&lt;/li&gt;
&lt;li&gt;看它的设计风格（暗色主题、渐变背景、卡片式布局）&lt;/li&gt;
&lt;li&gt;数一数它的 JS chunk 数，理解模块化架构&lt;/li&gt;
&lt;li&gt;参考它的排版和配色&lt;/li&gt;
&lt;/ul&gt;&lt;p&gt;把真实站点拆开看一遍，比看十篇”什么是 Tailwind”的文章有用。&lt;/p&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;每次做前端都要对照的检查清单&lt;a href=&quot;#每次做前端都要对照的检查清单&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;后端工程师做前端，做完容易”能用就行”。但一个”能用的页面”和”一个像样的页面”差距巨大。我每次做前端必对照 10 项：&lt;/p&gt;&lt;blockquote&gt;&lt;p&gt;图标 / 通知 / 动效 / 暗色模式 / 字体 / 排版 / 三态（默认/悬停/禁用）/ 搜索 / 组件复用 / 响应式&lt;/p&gt;&lt;/blockquote&gt;&lt;p&gt;少一项，页面就”缺点意思”。这 10 项就是&lt;a href=&quot;/posts/frontend-not-llm-default&quot;&gt;前端不该是 LLM 的默认审美&lt;/a&gt;那篇里”反默认审美”的具体落地清单。&lt;/p&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;结论&lt;a href=&quot;#结论&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;后端学前端的正确姿势：&lt;/p&gt;&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;直接做项目&lt;/strong&gt;，技术点靠踩坑学会&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;逆向真实站点&lt;/strong&gt;，比看教程快&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;用组件库源码&lt;/strong&gt;，不重复造轮子&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;每次做完对照检查清单&lt;/strong&gt;，别停在”能用”&lt;/li&gt;
&lt;/ol&gt;&lt;blockquote&gt;&lt;p&gt;&lt;strong&gt;前端不是一门”学完再会用”的课，是一门”做着做着就会”的功夫。&lt;/strong&gt;&lt;/p&gt;&lt;/blockquote&gt;&lt;p&gt;后端功底（数据、接口、性能思维）反而是做前端时的差异化优势——你会比纯前端更在意加载、缓存和接口设计。&lt;/p&gt;&lt;/section&gt;</content:encoded></item><item><title>前端不该是 LLM 的默认审美</title><link>https://heaven-1314.github.io/posts/frontend-not-llm-default/</link><guid isPermaLink="true">https://heaven-1314.github.io/posts/frontend-not-llm-default/</guid><description>让 LLM 直接生成前端，你会得到一份&quot;安全但没灵魂&quot;的页面：蓝紫配色、卡片 grid、眉毛标签、零动效。问题不在模型，在流程。</description><pubDate>Wed, 05 Aug 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;让 LLM 直接”写个 landing page”，你大概率会得到这种东西：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;蓝紫渐变的安全配色&lt;/li&gt;
&lt;li&gt;千篇一律的卡片 grid 布局&lt;/li&gt;
&lt;li&gt;每个区块上方一个”眉毛标签”（eyebrow label）&lt;/li&gt;
&lt;li&gt;全静态，没有任何过渡和动效&lt;/li&gt;
&lt;li&gt;看起来很”现代”，但毫无记忆点&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;这就是 &lt;strong&gt;LLM 的默认审美&lt;/strong&gt;——安全、平庸、一眼 AI 味。问题不在模型能力，在于&lt;strong&gt;直接让模型生成 = 放弃了设计决策&lt;/strong&gt;。&lt;/p&gt;
&lt;section&gt;&lt;h2&gt;为什么会这样&lt;a href=&quot;#为什么会这样&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;LLM 训练数据里充斥着这种”安全模板”：成千上万的中等质量 landing page，配色保守、布局套路、动效为零。模型学到的”前端正确答案”就是这个平均水平。&lt;/p&gt;&lt;p&gt;你让它”做得好看点”，它会加大渐变、加阴影、加 emoji——离”好看”越来越远，离”AI 味”越来越近。&lt;/p&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;Design Token：把设计决策前置&lt;a href=&quot;#design-token把设计决策前置&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;跳出默认审美的第一步，是&lt;strong&gt;先做设计决策，再写代码&lt;/strong&gt;。&lt;/p&gt;&lt;p&gt;具体做法是先定义 &lt;strong&gt;Design Token&lt;/strong&gt;——把颜色、字体、圆角、阴影、间距这些视觉原子，抽成命名的变量：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;color.bg.primary     = #0a0a0f&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;color.text.primary   = #f5f5f7&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;3&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;color.accent         = #ff4d8d   // 强调色，品牌锚点&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;4&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;font.display         = &quot;Inter Display&quot;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;5&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;font.body            = &quot;Inter&quot;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;6&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;radius.card          = 16px&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;7&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;shadow.elevated      = 0 8px 32px rgba(0,0,0,0.3)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;把 &lt;code&gt;#ff4d8d&lt;/code&gt; 抽象成 &lt;code&gt;color.accent&lt;/code&gt;，意义在于：&lt;strong&gt;设计决策（用这个粉色）和代码实现（在哪里用）解耦了&lt;/strong&gt;。改主题只改 token 文件，全局生效。&lt;/p&gt;&lt;p&gt;模型拿到 token 文件后生成，配色就不再是”安全蓝紫”，而是你定的风格。&lt;strong&gt;你把决策权从模型手里拿回来了。&lt;/strong&gt;&lt;/p&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;Design System：让风格可复现&lt;a href=&quot;#design-system让风格可复现&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;Token 之上是 &lt;strong&gt;Design System&lt;/strong&gt;——一套组件的样式契约（按钮的三态、卡片的层级、间距的节奏）。&lt;/p&gt;&lt;p&gt;有了 Design System：&lt;/p&gt;&lt;ul&gt;
&lt;li&gt;换主题 = 换 token 映射表（白标 / 暗色 / 多品牌一键切换）&lt;/li&gt;
&lt;li&gt;风格统一 = 所有页面共享同一套样式逻辑&lt;/li&gt;
&lt;li&gt;AI 生成 = 给定 token + system，生成的界面风格一致，不是每次随机&lt;/li&gt;
&lt;/ul&gt;&lt;p&gt;组件库（shadcn/ui、Radix）本质就是 Design System 的工程实现——它们用 token 定义基础样式的&lt;strong&gt;单一事实源&lt;/strong&gt;，避免样式碎片化。&lt;/p&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;几条反默认审美的实践&lt;a href=&quot;#几条反默认审美的实践&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;&lt;strong&gt;1. 先设计后编码&lt;/strong&gt;
不是”让 AI 写个页面”，而是”先定设计意图（关键词：情绪 / 风格 / 参考站），再让 AI 基于这个意图生成”。设计意图前置，模型才有方向。&lt;/p&gt;&lt;p&gt;&lt;strong&gt;2. 用参考站锚定&lt;/strong&gt;
别用形容词描述设计（“高级感&quot;&quot;极简风”模型听不懂），用具体参考站：“像 linear.app&quot;&quot;像 stripe.com”。模型对具体站点的视觉特征有记忆，比抽象词靠谱。&lt;/p&gt;&lt;p&gt;&lt;strong&gt;3. 强调动效&lt;/strong&gt;
默认生成是静态的。明确要求”hover 有微动效&quot;&quot;滚动有进入动画&quot;&quot;主题切换平滑过渡”，模型才会加。动效是页面”活”的关键。&lt;/p&gt;&lt;p&gt;&lt;strong&gt;4. 自托管字体&lt;/strong&gt;
&lt;code&gt;next/font/google&lt;/code&gt; 在某些构建工具下会报错（Unknown font）。改用 &lt;code&gt;@fontsource-variable/*&lt;/code&gt; 自托管——字体加载稳定、不依赖 Google CDN、国内可访问。&lt;/p&gt;&lt;p&gt;&lt;strong&gt;5. 部署前必须真机验证&lt;/strong&gt;
截图第一次就要 cache-bust（&lt;code&gt;--user-data-dir&lt;/code&gt; 独立 profile 绕缓存），否则你看到的是旧版本，改了等于没改。&lt;/p&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;我的流程&lt;a href=&quot;#我的流程&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;后端工程师做前端，最大的坑不是”不会写”，是”被 LLM 的默认审美带跑”。我现在的工作流：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;定设计意图 + 参考站 → 定义 Design Token → 让 AI 基于 token 生成 → 真机验证 → 微调&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;每一步都把”设计决策”从我手里显式地交给模型，而不是让模型自己拍板。这样出来的前端，才不是 LLM 的默认审美，而是&lt;strong&gt;我的审美被工程化实现&lt;/strong&gt;。&lt;/p&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;结论&lt;a href=&quot;#结论&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;LLM 的默认前端 = 安全 + 平庸 + 一眼 AI。要跳出这个陷阱：&lt;/p&gt;&lt;blockquote&gt;&lt;p&gt;&lt;strong&gt;设计决策前置，模型只负责实现，不负责拍板。&lt;/strong&gt;&lt;/p&gt;&lt;/blockquote&gt;&lt;p&gt;Design Token 和 Design System 是把决策权拿回来的工程工具。前端能力弱的工程师，靠这套流程也能做出有风格、有记忆点的界面——而不是又一个”AI 生成的 landing page”。&lt;/p&gt;&lt;/section&gt;</content:encoded></item><item><title>如何组织一个机器可读的知识库：Obsidian + Markdown 的实践</title><link>https://heaven-1314.github.io/posts/knowledge-base-organization/</link><guid isPermaLink="true">https://heaven-1314.github.io/posts/knowledge-base-organization/</guid><description>个人知识库最怕变成&quot;剪藏垃圾场&quot;。用类型决策树、受控模板、MOC 导航和裸链接，让知识库既给人看也喂得动 AI。</description><pubDate>Wed, 05 Aug 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;个人知识库很容易长成”剪藏垃圾场”：看到好文章就收藏，几个月后想找某个经验，翻半天找不到。&lt;/p&gt;
&lt;p&gt;我踩过这个坑之后，把知识库重构成一套&lt;strong&gt;机器可读&lt;/strong&gt;的规范——不只是给人看的，也是喂得动 AI 的。&lt;/p&gt;
&lt;section&gt;&lt;h2&gt;这库是干什么的：先划边界&lt;a href=&quot;#这库是干什么的先划边界&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;




















&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;要&lt;/th&gt;&lt;th&gt;不要&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;项目决策、Bug 根因、可复用经验&lt;/td&gt;&lt;td&gt;无筛选的剪藏垃圾场&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;人机都能检索的结构化 Markdown&lt;/td&gt;&lt;td&gt;只给插件看的英文双轨分类&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;入口清晰：首页 → 目录 → 页面&lt;/td&gt;&lt;td&gt;只靠侧边栏滚目录&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;p&gt;&lt;strong&gt;底层方法&lt;/strong&gt;：借 ACE 意图（知识 / 行动 / 时间）+ MOC 导航 + 受控页面类型。不采用全库硬切 PARA、无约束卡片狂拆。&lt;/p&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;新建一页前，先走决策树&lt;a href=&quot;#新建一页前先走决策树&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;最防止”垃圾场”的是&lt;strong&gt;新建门槛&lt;/strong&gt;。每次要记东西，先过一遍决策树：&lt;/p&gt;&lt;ol&gt;
&lt;li&gt;这是不是一次&lt;strong&gt;可复用的经验&lt;/strong&gt;？是 → &lt;code&gt;经验&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;是不是&lt;strong&gt;故障闭环&lt;/strong&gt;？是 → &lt;code&gt;bug&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;挂在&lt;strong&gt;现有项目页&lt;/strong&gt;的变更日志够不够？够 → &lt;strong&gt;不新建&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;外部文章 → 只建来源索引，&lt;strong&gt;概念能不拆就不拆&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;&lt;p&gt;第 3 条最关键：&lt;strong&gt;能挂在现有页面就绝不新建&lt;/strong&gt;。页面数量爆炸是知识库烂掉的开始。&lt;/p&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;写正文的规范&lt;a href=&quot;#写正文的规范&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;&lt;strong&gt;1. frontmatter 填全&lt;/strong&gt;：&lt;code&gt;type / title / created / updated&lt;/code&gt;，业务页补状态和所属领域。&lt;/p&gt;&lt;p&gt;&lt;strong&gt;2. 链接只用裸链接&lt;/strong&gt;：&lt;code&gt;[[页面名]]&lt;/code&gt; 而不是 &lt;code&gt;[[目录/页面]]&lt;/code&gt;。裸链接按名字解析，文件移动不断链。&lt;/p&gt;&lt;p&gt;&lt;strong&gt;3. 结论先看&lt;/strong&gt;：高价值的经验页，开头先给一句话结论，别让人翻到底才知道这页讲什么。&lt;/p&gt;&lt;p&gt;&lt;strong&gt;4. 用醒目块区分关键信息&lt;/strong&gt;：症状 / 根因 / 修复 / 教训 / 风险，用不同的强调块区分，一眼扫到重点。&lt;/p&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;MOC：只做索引，不写长文&lt;a href=&quot;#moc只做索引不写长文&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;MOC（Map of Content）是知识库的&lt;strong&gt;导航层&lt;/strong&gt;，只放链接，不写正文。&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;MOC-Agent与Harness&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;MOC-部署与网关&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;3&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;MOC-前端设计&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;4&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;...&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;5&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;6&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;新增一页 → 在对应 MOC 下加一行&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;7&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;- [[页面名]] — 一句话&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;首页 → MOC → 页面，三级入口。新知识库成员（人或 AI）进来，5 秒知道有什么、在哪。&lt;/p&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;为什么”机器可读”很重要&lt;a href=&quot;#为什么机器可读很重要&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;这套规范最大的受益者其实是 &lt;strong&gt;AI 助手&lt;/strong&gt;。给 AI 配一个知识库 MCP 接口后：&lt;/p&gt;&lt;ul&gt;
&lt;li&gt;它按 &lt;code&gt;type&lt;/code&gt; 字段知道一页是经验还是 bug&lt;/li&gt;
&lt;li&gt;它按裸链接能顺着 &lt;code&gt;[[关联]]&lt;/code&gt; 跳转相关页&lt;/li&gt;
&lt;li&gt;它按 MOC 能找到”这个领域有哪些内容”&lt;/li&gt;
&lt;li&gt;它读经验页时&lt;strong&gt;先看到结论&lt;/strong&gt;，不用全文读完&lt;/li&gt;
&lt;/ul&gt;&lt;p&gt;&lt;strong&gt;规范即接口&lt;/strong&gt;。你给知识库定的结构，就是 AI 检索它的接口定义。&lt;/p&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;两条禁忌&lt;a href=&quot;#两条禁忌&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;不为单篇剪藏自动生成十几个概念页&lt;/strong&gt;——那是垃圾场膨胀的自动版&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;不写路径链接&lt;/strong&gt;（&lt;code&gt;[[dir/page]]&lt;/code&gt;）——文件一移动全断&lt;/li&gt;
&lt;/ol&gt;&lt;p&gt;看到”不自动生成概念页”，你可能会问：&lt;strong&gt;那什么时候才该建概念页？&lt;/strong&gt;&lt;/p&gt;&lt;p&gt;这里的关键不是”建不建”，而是”&lt;strong&gt;门槛设在哪&lt;/strong&gt;”。LLM Wiki 和 Obsidian 自动生成概念页的逻辑，本质是把门槛降到了”出现一次就建”——每个专有名词一页，为的是给关系图谱提供节点。这在”领域百科”型知识库（系统收集一个领域的术语）里是对的，术语就是知识骨架；但你的库如果是”项目记忆库”（记经验、记决策、记 bug），概念就是配角，无脑拆页只会让图谱被空壳节点淹没。&lt;/p&gt;&lt;p&gt;图谱的节点&lt;strong&gt;不一定是概念页&lt;/strong&gt;——项目页、经验页、bug 页互相 &lt;code&gt;[[关联]]&lt;/code&gt;，本身就是图谱。MOC 是手动策展的导航层，图谱是自动的关联层，两者互补。你要的是”节点和边够多”，不是”每个名词都有一页”。&lt;/p&gt;&lt;p&gt;那什么时候把一个名词”升格”成独立概念页？三条门槛，满足其一才建：&lt;/p&gt;
























&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;判断维度&lt;/th&gt;&lt;th&gt;问自己&lt;/th&gt;&lt;th&gt;门槛&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;&lt;strong&gt;复用度&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;这个概念会被几个页面引用？&lt;/td&gt;&lt;td&gt;≥2 个才建；只在单篇剪藏出现 → 留在原文&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;strong&gt;权威性&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;这个词的语义需要统一定义吗？&lt;/td&gt;&lt;td&gt;多处说法会不一致才建页”定锚”&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;strong&gt;丢失风险&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;不建这页，它会丢吗？&lt;/td&gt;&lt;td&gt;散落各处检索不到才建；不会 → 不建&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;p&gt;三条都答”否” → 不建，让它留在记录页里。&lt;/p&gt;&lt;blockquote&gt;&lt;p&gt;&lt;strong&gt;自动生成和克制升格，差的只是这道门槛设在哪。门可以低，不能为零。&lt;/strong&gt;&lt;/p&gt;&lt;/blockquote&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;结论&lt;a href=&quot;#结论&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;知识库组织规范的本质是三件事：&lt;/p&gt;&lt;blockquote&gt;&lt;p&gt;&lt;strong&gt;建前有门槛（决策树），写时有模板（受控类型），检索有入口（MOC）。&lt;/strong&gt;&lt;/p&gt;&lt;/blockquote&gt;&lt;p&gt;个人知识库不需要复杂工具，一套克制的规范 + Markdown + Git 版本控制就够了。&lt;strong&gt;克制，是知识库最稀缺的品质。&lt;/strong&gt;&lt;/p&gt;&lt;/section&gt;</content:encoded></item><item><title>LangChain / LangGraph 学习笔记：什么时候该用，什么时候别用</title><link>https://heaven-1314.github.io/posts/langchain-when-to-use/</link><guid isPermaLink="true">https://heaven-1314.github.io/posts/langchain-when-to-use/</guid><description>每个 AI 工程师都该学 LangChain，但不是每个项目都该用 LangChain。框架选型的关键，是先分清&quot;通用框架&quot;和&quot;专用系统&quot;。</description><pubDate>Wed, 05 Aug 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;LangChain / LangGraph 几乎是 AI 应用开发绕不开的名字。我学它的时候记了三句话，后来发现这三句话就是选型时最该想清楚的事。&lt;/p&gt;
&lt;section&gt;&lt;h2&gt;先搞懂它是什么&lt;a href=&quot;#先搞懂它是什么&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;LangChain&lt;/strong&gt;：链式调用大模型 + 工具的组合框架。把”调模型、接工具、做中间处理”串成一条链。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;LangGraph&lt;/strong&gt;：有状态的 Agent 工作流。用图结构表达”先做什么、条件分支、循环、多步骤状态”，适合复杂流程。&lt;/li&gt;
&lt;/ul&gt;&lt;p&gt;一句话：&lt;strong&gt;LangChain 是”链”，LangGraph 是”图”。&lt;/strong&gt; 简单流程用链，复杂状态机用图。&lt;/p&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;三句学习笔记&lt;a href=&quot;#三句学习笔记&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;&lt;strong&gt;第一句：通用框架 vs 专用系统，先分清。&lt;/strong&gt;&lt;/p&gt;&lt;p&gt;LangChain/LangGraph 是&lt;strong&gt;通用&lt;/strong&gt;的 Agent 框架——什么业务都能套。而有些 Agent 系统是&lt;strong&gt;专用&lt;/strong&gt;的（为特定场景设计好了一整套能力和边界）。&lt;/p&gt;&lt;p&gt;通用框架的好处是灵活、可定制；坏处是&lt;strong&gt;所有东西都要自己搭&lt;/strong&gt;——工具调用、记忆、状态管理、错误处理，每个都要配置和调试。专用系统则相反：上手快、开箱即用，但扩展受限。&lt;/p&gt;&lt;p&gt;&lt;strong&gt;选型的关键不是”哪个更流行”，是”你的项目需要多强的定制”。&lt;/strong&gt;&lt;/p&gt;&lt;p&gt;&lt;strong&gt;第二句：学它 ≠ 用它。&lt;/strong&gt;&lt;/p&gt;&lt;p&gt;学习 LangChain 是为了&lt;strong&gt;理解 AI 应用的通用范式&lt;/strong&gt;——工具调用、链式编排、状态管理、上下文处理。这套心智模型是通用的，换个框架、甚至自己写，都用得上。&lt;/p&gt;&lt;p&gt;但学到 ≠ 生产项目必须用它。如果你的项目用一个专用 Agent 系统已经能满足需求，&lt;strong&gt;不要为了”用上了热门框架”而强行迁移&lt;/strong&gt;。技术选型的成本收益要算清：迁移一个正在运行的系统，要付出什么，得到什么？&lt;/p&gt;&lt;p&gt;&lt;strong&gt;第三句：没有它，现在的需求也能完成吗？&lt;/strong&gt;&lt;/p&gt;&lt;p&gt;每次想引入一个框架，先问这句。如果现有工具链已经覆盖需求，新框架引入的是&lt;strong&gt;新的依赖、新的学习成本、新的 bug 面&lt;/strong&gt;——而不是新能力。&lt;/p&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;什么项目适合 LangGraph&lt;a href=&quot;#什么项目适合-langgraph&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;复杂、有状态、需要精细控制流程的项目：&lt;/p&gt;&lt;ul&gt;
&lt;li&gt;多步骤流程，步骤间有依赖和分支&lt;/li&gt;
&lt;li&gt;需要持久化中间状态（做到一半保存，下次继续）&lt;/li&gt;
&lt;li&gt;需要人对关键步骤审批 / 干预&lt;/li&gt;
&lt;li&gt;需要精细控制每步用哪个模型、什么上下文&lt;/li&gt;
&lt;/ul&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;什么项目别用&lt;a href=&quot;#什么项目别用&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;ul&gt;
&lt;li&gt;简单的一次性调用（调模型 → 得结果 → 结束）&lt;/li&gt;
&lt;li&gt;已有专用系统覆盖的场景&lt;/li&gt;
&lt;li&gt;团队没人熟悉、需要从零学起的项目&lt;/li&gt;
&lt;/ul&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;结论&lt;a href=&quot;#结论&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;blockquote&gt;&lt;p&gt;&lt;strong&gt;每个 AI 工程师都该学 LangChain，但不是每个项目都该用 LangChain。&lt;/strong&gt;&lt;/p&gt;&lt;/blockquote&gt;&lt;p&gt;学它，是为了建立 AI 应用的通用心智模型；选不选它，要回到需求本身。&lt;strong&gt;框架是工具，不是信仰。&lt;/strong&gt; 一个在跑的专用系统，不会因为”更流行的框架出现了”就自动变差。&lt;/p&gt;&lt;/section&gt;</content:encoded></item><item><title>LLM API 统一管理实践：多模型路由的配置与踩坑</title><link>https://heaven-1314.github.io/posts/llm-api-multimodel-routing/</link><guid isPermaLink="true">https://heaven-1314.github.io/posts/llm-api-multimodel-routing/</guid><description>项目里要用多个厂商的模型，统一网关帮你把&quot;用哪个模型&quot;变成一次配置。但模型跨渠道挂载、额度耗尽、嵌入维度不兼容，每个都是坑。</description><pubDate>Wed, 05 Aug 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;做 AI 应用，几乎都会走到这一步：&lt;strong&gt;代码里要用好几种模型&lt;/strong&gt;——代码生成用强模型、日常对话用便宜模型、前端任务用快模型、嵌入用专门的向量模型，还来自不同厂商。&lt;/p&gt;
&lt;p&gt;最省事的做法是搭一个统一网关，所有模型在一个面板里管理，代码只认网关的标准接口。&lt;/p&gt;
&lt;section&gt;&lt;h2&gt;统一网关解决了什么&lt;a href=&quot;#统一网关解决了什么&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;一个 API 入口&lt;/strong&gt;：代码里不用记每个厂商的地址和密钥&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;统一接口格式&lt;/strong&gt;：OpenAI 兼容的接口，换模型不改代码&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;集中管理渠道&lt;/strong&gt;：每个模型挂在哪个渠道、哪个厂商、额度还剩多少，一个面板看清&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;按用途分发&lt;/strong&gt;：把”用哪个模型”从代码里抽出来，变成面板上的配置&lt;/li&gt;
&lt;/ul&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;一张模型用途表&lt;a href=&quot;#一张模型用途表&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;典型的多模型分工：&lt;/p&gt;


































&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;用途&lt;/th&gt;&lt;th&gt;模型&lt;/th&gt;&lt;th&gt;说明&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;代码生成&lt;/td&gt;&lt;td&gt;强模型&lt;/td&gt;&lt;td&gt;贵但准，关键任务用&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;日常对话&lt;/td&gt;&lt;td&gt;便宜模型&lt;/td&gt;&lt;td&gt;量大，用便宜快的&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;前端 / 轻任务&lt;/td&gt;&lt;td&gt;快模型&lt;/td&gt;&lt;td&gt;低延迟优先&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;嵌入&lt;/td&gt;&lt;td&gt;向量模型&lt;/td&gt;&lt;td&gt;专门做 embedding&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;视觉&lt;/td&gt;&lt;td&gt;多模态模型&lt;/td&gt;&lt;td&gt;处理图片&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;踩坑一：同一个模型挂了多个渠道&lt;a href=&quot;#踩坑一同一个模型挂了多个渠道&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;这是最隐蔽的坑。一个模型（比如”强代码模型”）同时在两个渠道上挂载：&lt;/p&gt;&lt;ul&gt;
&lt;li&gt;渠道 A：付费渠道，额度充足&lt;/li&gt;
&lt;li&gt;渠道 B：免费渠道，额度&lt;strong&gt;已经耗尽&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;&lt;p&gt;网关做路由时，可能在 A 和 B 之间随机/轮询。请求被路由到额度耗尽的渠道 B → 直接 &lt;strong&gt;403 拒绝&lt;/strong&gt;。表现就是：&lt;strong&gt;同一个请求，有时能用，有时报错，完全随缘。&lt;/strong&gt;&lt;/p&gt;&lt;p&gt;修复：&lt;strong&gt;同一个模型只挂一个渠道&lt;/strong&gt;。如果多渠道确实需要，把额度耗尽的渠道上移除该模型，让路由只有一条可达路径。&lt;/p&gt;&lt;blockquote&gt;&lt;p&gt;经验法则：模型 × 渠道是多对多的关系，但&lt;strong&gt;一个模型的可用渠道必须随时可验证&lt;/strong&gt;。别让”可能路由到坏渠道”的概率留在线上。&lt;/p&gt;&lt;/blockquote&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;踩坑二：嵌入模型换了维度就数据不兼容&lt;a href=&quot;#踩坑二嵌入模型换了维度就数据不兼容&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;嵌入（embedding）模型有一个隐藏属性：&lt;strong&gt;输出维度&lt;/strong&gt;。不同的嵌入模型维度不同（比如 1024 维 vs 3072 维）。&lt;/p&gt;&lt;p&gt;如果你把应用里用的嵌入模型从 A 换成 B，而新旧模型维度不同：&lt;/p&gt;&lt;ul&gt;
&lt;li&gt;旧的向量库数据（A 生成的 1024 维向量）和新请求（B 生成的 3072 维向量）&lt;strong&gt;无法互相比较&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;相似度检索结果全乱&lt;/li&gt;
&lt;/ul&gt;&lt;p&gt;修复：&lt;strong&gt;切换嵌入模型必须清空旧向量库重新生成&lt;/strong&gt;，或者做好版本隔离。维度不一致的数据，比没有数据更危险。&lt;/p&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;踩坑三：网关的安全&lt;a href=&quot;#踩坑三网关的安全&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;统一网关是把所有模型密钥集中在&lt;strong&gt;一个入口&lt;/strong&gt;，它也变成高价值攻击目标：&lt;/p&gt;&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;关闭自助注册&lt;/strong&gt;：开源网关默认开放注册，曾有外部 IP 自助注册后白嫖额度。第一时间关掉注册开关&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;入口收敛&lt;/strong&gt;：网关面板和管理端口不要直接暴露公网&lt;/li&gt;
&lt;/ul&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;四条经验&lt;a href=&quot;#四条经验&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;一个模型只挂一个可用渠道&lt;/strong&gt;，随时在面板确认渠道状态&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;切嵌入模型 = 清旧向量库&lt;/strong&gt;，维度不同数据不兼容&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;统一网关的安全要当回事&lt;/strong&gt;，关注册、收敛入口&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;网关的数据库和配置要备份&lt;/strong&gt;，它是所有模型访问的单一故障点&lt;/li&gt;
&lt;/ol&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;结论&lt;a href=&quot;#结论&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;多模型统一网关的价值：&lt;strong&gt;把”用哪个模型”变成一次配置，而不是一次改代码。&lt;/strong&gt;&lt;/p&gt;&lt;p&gt;但统一也意味着集中——单点故障、安全暴露、路由错误都会放大。用它的收益，也要接受它的纪律：&lt;/p&gt;&lt;blockquote&gt;&lt;p&gt;&lt;strong&gt;路由要可验证，模型不跨坏渠道，嵌入版本要隔离，入口要收敛。&lt;/strong&gt;&lt;/p&gt;&lt;/blockquote&gt;&lt;p&gt;做到这四条，多模型路由就从”配置麻烦”变成”一次配置，长期稳定”。&lt;/p&gt;&lt;/section&gt;</content:encoded></item><item><title>AI 记忆系统的三次进化：从外挂检索到代理化生命周期</title><link>https://heaven-1314.github.io/posts/memory-system-three-stages/</link><guid isPermaLink="true">https://heaven-1314.github.io/posts/memory-system-three-stages/</guid><description>AI 的记忆系统沿着&quot;外部检索 → 内化记忆 → 复合生命周期&quot;演进。看懂这条路径，就能理解各种记忆方案在拼图里的位置。</description><pubDate>Wed, 05 Aug 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;大模型的记忆方案五花八门：向量库、RAG、知识图谱、Wiki、会话记录…… 但把它们放到一条演进路径上，位置一目了然。&lt;/p&gt;
&lt;blockquote&gt;&lt;p&gt;&lt;strong&gt;记忆系统沿”外部检索 → 内化记忆 → 复合生命周期”演进。&lt;/strong&gt;&lt;/p&gt;&lt;/blockquote&gt;
&lt;p&gt;三个代际，三种核心问题。&lt;/p&gt;
&lt;section&gt;&lt;h2&gt;第一代：RAG 时代——记忆的外挂化&lt;a href=&quot;#第一代rag-时代记忆的外挂化&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;&lt;strong&gt;核心问题：模型无记忆，怎么外挂一个？&lt;/strong&gt;&lt;/p&gt;&lt;p&gt;记忆完全放在外部索引里，每次提问时从向量库或关键词索引捞出片段，注入提示窗口。本质是”&lt;strong&gt;无状态、有索引&lt;/strong&gt;”。&lt;/p&gt;&lt;p&gt;局限也很明显：&lt;/p&gt;&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;成本高&lt;/strong&gt;：索引 500 页文档动辄几十到上百美元&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;延迟大&lt;/strong&gt;：每次提问都要检索注入&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;对问法敏感&lt;/strong&gt;：换一种问法就捞不到&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;被动&lt;/strong&gt;：只在你问的时候才干活，没有理解&lt;/li&gt;
&lt;/ul&gt;&lt;p&gt;第一代的进步是”让模型能查到记忆”，但记忆和模型是两张皮。&lt;/p&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;第二代：结构化时代——LLM 主动维护&lt;a href=&quot;#第二代结构化时代llm-主动维护&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;&lt;strong&gt;核心问题：如何跨会话维持知识？&lt;/strong&gt;&lt;/p&gt;&lt;p&gt;第一代的问题在于”现场检索”——提问时才开始理解。第二代把工作前置：&lt;strong&gt;让模型提前把记忆组织成结构&lt;/strong&gt;，而不是临场捞。&lt;/p&gt;&lt;p&gt;代表方案分几类：&lt;/p&gt;&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;LLM 维护的 Wiki&lt;/strong&gt;：模型增量维护一个 Markdown 知识库，无数据库、版本可控（Git）、人机共用&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;自动会话记录&lt;/strong&gt;：每次对话自动记录，新会话开始时注入相关历史&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;代码知识图谱&lt;/strong&gt;：跨项目语义关联，支持调用链和影响半径查询&lt;/li&gt;
&lt;/ul&gt;&lt;p&gt;这一代的价值是”记忆有了归属和结构”，但仍有局限：维护边界模糊、整理不自动化、搜索依赖文件系统而非语义。&lt;/p&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;第三代：代理化时代——记忆的生命周期&lt;a href=&quot;#第三代代理化时代记忆的生命周期&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;&lt;strong&gt;核心问题：如何让记忆自我进化？&lt;/strong&gt;&lt;/p&gt;&lt;p&gt;第三代让&lt;strong&gt;模型自己成为记忆的管理者&lt;/strong&gt;：自主决定记什么、忘什么、何时回忆、如何进化（总结、合并、抽象）。记忆不再是”存了就忘不掉的档案”，而是一组&lt;strong&gt;可被压缩、重组、遗忘的活实体&lt;/strong&gt;。&lt;/p&gt;&lt;p&gt;核心特征：&lt;/p&gt;&lt;ul&gt;
&lt;li&gt;记忆有&lt;strong&gt;优先级和衰减曲线&lt;/strong&gt;（更像人脑的长期记忆增强）&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;后台线程持续运作&lt;/strong&gt;，主动扫描、修正、提炼&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;模型自驱动&lt;/strong&gt;，不等人类提出正确的问题&lt;/li&gt;
&lt;/ul&gt;&lt;p&gt;这一代还在探索期，可控性与可靠性都未验证——让模型自主决定”忘什么”，边界在哪还没人答清楚。&lt;/p&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;三代之间不是替代，是叠加&lt;a href=&quot;#三代之间不是替代是叠加&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;三阶段不是严格取代，存在重叠和过渡。现在最务实的组合是：&lt;/p&gt;&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;会话记忆&lt;/strong&gt;（自动记录）负责”我说过什么”&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;结构化 Wiki&lt;/strong&gt;（长期决策）负责”我为什么这么做”&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;知识图谱&lt;/strong&gt;（语义关联）负责”代码之间什么关系”&lt;/li&gt;
&lt;/ul&gt;&lt;p&gt;三个各司其职，互相补位。等到代理化记忆成熟，它接管的是”整理和遗忘”这件现在最耗人的事。&lt;/p&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;为什么值得关注&lt;a href=&quot;#为什么值得关注&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;记忆系统解决的不是”检索快不快”，是&lt;strong&gt;知识能不能跨会话、跨项目积累&lt;/strong&gt;。&lt;/p&gt;&lt;p&gt;一个好的记忆系统应该做到：修过一次的 bug 不再重踩，做过一次的架构决策下次不用重新推导。这比任何单点工具的升级都值钱。&lt;/p&gt;&lt;blockquote&gt;&lt;p&gt;&lt;strong&gt;模型的进化看能力，系统的进化看记忆。&lt;/strong&gt;&lt;/p&gt;&lt;/blockquote&gt;&lt;p&gt;方向清楚了，下一个问题是执行——代理化记忆的”遗忘策略”怎么设计，这会是未来几年最有意思的工程问题之一。&lt;/p&gt;&lt;/section&gt;</content:encoded></item><item><title>需求多维化拆分的防扩散设计原则</title><link>https://heaven-1314.github.io/posts/multidim-split-anti-diffusion/</link><guid isPermaLink="true">https://heaven-1314.github.io/posts/multidim-split-anti-diffusion/</guid><description>把一个概念从 1 类拆成 N 类，不是&quot;加个 if&quot;，而是架构适配。改动散落到所有消费点，漏一处就是一个 bug。</description><pubDate>Wed, 05 Aug 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;需求来了：“把’延期’从一类拆成三类（研发延期 / 测试延期 / 父任务延期）“。听起来就是加几个 if。但实际做下去，bug 一个接一个，过几天才陆续暴露。&lt;/p&gt;
&lt;p&gt;这类 bug 的根因不在”代码写错”，而在&lt;strong&gt;改动的影响面没有被识别和控制&lt;/strong&gt;。属于设计思路错误，不是编码错误。&lt;/p&gt;
&lt;section&gt;&lt;h2&gt;为什么”1→N 拆分”是 bug 高发区&lt;a href=&quot;#为什么1n-拆分是-bug-高发区&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;典型模式：&lt;/p&gt;&lt;ol&gt;
&lt;li&gt;旧需求定义了一个单维度概念（“延期” = 看子任务 deadline）&lt;/li&gt;
&lt;li&gt;新需求把它拆成多维度（“延期” = 研发延期 / 测试延期 / 父任务延期 三类）&lt;/li&gt;
&lt;li&gt;拆分时没有重整架构，而是&lt;strong&gt;在每个消费点各自加分类判断&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;消费点散落在计算层、AI 层、导出层、前端 N 个页面&lt;/li&gt;
&lt;li&gt;漏改 / 改错一处 → bug，且因边界场景才触发，往往上线几天后才被发现&lt;/li&gt;
&lt;/ol&gt;&lt;p&gt;&lt;strong&gt;同一个概念，N 个地方各写一遍分类逻辑，必然漂移。&lt;/strong&gt;&lt;/p&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;五条防扩散设计原则&lt;a href=&quot;#五条防扩散设计原则&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;








































&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;#&lt;/th&gt;&lt;th&gt;原则&lt;/th&gt;&lt;th&gt;踩坑表现&lt;/th&gt;&lt;th&gt;正确做法&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;&lt;strong&gt;1&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;&lt;strong&gt;单一数据源&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;”X 怎么算”在 N 个函数各写一遍，必然漂移&lt;/td&gt;&lt;td&gt;抽一个函数，所有页面/导出/AI 都调它，&lt;strong&gt;禁止重复实现同一计算&lt;/strong&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;strong&gt;2&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;&lt;strong&gt;展示层与计算层分离&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;把分类标签混进了核心计算，逻辑纠缠&lt;/td&gt;&lt;td&gt;核心值先算出来；分类标签在&lt;strong&gt;最后渲染时&lt;/strong&gt;再贴，计算层不感知分类&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;strong&gt;3&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;&lt;strong&gt;大改动先列影响面清单&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;改完等用户报 bug，才知道哪个页面漏了&lt;/td&gt;&lt;td&gt;维度拆分前先 &lt;code&gt;grep&lt;/code&gt; 所有读该概念的地方，列成清单逐个验证&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;strong&gt;4&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;&lt;strong&gt;口径变更要带边界用例&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;没有测试，靠人工点页面&lt;/td&gt;&lt;td&gt;为每个分类写边界用例，测试驱动&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;strong&gt;5&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;&lt;strong&gt;同语义双实现要自动对齐&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;两个函数各算一遍没断言 → 必然漂移&lt;/td&gt;&lt;td&gt;加测试断言”同一对象在不同页面的统计值相等”，用测试守住一致性&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;启动三问（下次遇到”1→N 分类”需求时必问）&lt;a href=&quot;#启动三问下次遇到1n-分类需求时必问&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;改之前先回答这三句，&lt;strong&gt;任何一句答不上来就先补设计、别急着写代码&lt;/strong&gt;：&lt;/p&gt;&lt;p&gt;&lt;strong&gt;1. 计算层的单一数据源在哪？&lt;/strong&gt;
这个概念的核心计算，是不是只有一个函数在算？如果 N 个地方各算一遍，先收口。&lt;/p&gt;&lt;p&gt;&lt;strong&gt;2. 分类能不能只活在展示层？&lt;/strong&gt;
新增的维度能不能做成最后渲染时贴的标签，而不是侵入计算的 &lt;code&gt;if&lt;/code&gt;？&lt;/p&gt;&lt;p&gt;&lt;strong&gt;3. 所有消费点是否都有测试覆盖？&lt;/strong&gt;
grep 出来的每个消费点，有没有对应的用例守着？漏改了能不能被测试抓住？&lt;/p&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;一个讽刺的真实现场&lt;a href=&quot;#一个讽刺的真实现场&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;某系统”人数”这个统计被 3 处消费：顶部总览、部门卡、横向对比。某天领导要求”人数排除组长”——三处逐个改完。&lt;strong&gt;当天领导又改口”人数要含组长”&lt;/strong&gt;（组长数据仍排除在工时/任务外，但人数含组长）——三处又得逐个改回。&lt;/p&gt;&lt;p&gt;&lt;strong&gt;如果当初抽了 &lt;code&gt;group_member_count()&lt;/code&gt; 单一函数，这次翻转只改一个函数、所有页面自动同步。&lt;/strong&gt; 朝令夕改在业务系统是常态，“一处改全同步”的价值正是在这种时候兑现。&lt;/p&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;什么时候必须建聚合函数层&lt;a href=&quot;#什么时候必须建聚合函数层&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;不是所有项目都要做。&lt;strong&gt;涉及上万条级数据库记录 + 偏仪表盘 / 多页面对账的网页&lt;/strong&gt;才需要——同一个统计口径被多个页面 / 接口 / 导出 / AI 同时消费时，&lt;strong&gt;立项阶段&lt;/strong&gt;就要建统一聚合函数层，别等 bug 出来再补。&lt;/p&gt;&lt;p&gt;分层规范：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;数据源 DB → 统一聚合函数（一个概念一个函数）&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;         &lt;/span&gt;&lt;/span&gt;&lt;span&gt;→ 后端算好派生字段（禁止把原始复合结构抛给消费端各自解读）&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;3&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;         &lt;/span&gt;&lt;/span&gt;&lt;span&gt;→ API 契约（字段字典，前端只读渲染，不做 len() 推导）&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;加 &lt;strong&gt;跨页一致性断言测试&lt;/strong&gt; 兜底：“同一对象在 A 页的统计 = B 页 = C 页”，后端改错立刻被抓，不用等用户报。&lt;/p&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;结论&lt;a href=&quot;#结论&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;需求多维化本身没错，错的是&lt;strong&gt;用”加 if”的方式去做架构级的改动&lt;/strong&gt;。下次遇到”把 X 拆成几类”的需求，先停一下，回答启动三问——任何一个答不上来，先补设计。&lt;/p&gt;&lt;p&gt;&lt;strong&gt;改动的影响面必须被显式识别，不能靠”改完看看”兜底。&lt;/strong&gt;&lt;/p&gt;&lt;/section&gt;</content:encoded></item><item><title>nginx 子路径部署的隐形炸弹：绝对路径怎么把页面变白屏</title><link>https://heaven-1314.github.io/posts/nginx-subpath-deployment/</link><guid isPermaLink="true">https://heaven-1314.github.io/posts/nginx-subpath-deployment/</guid><description>应用部署到 nginx 子路径下页面全白，根因往往是应用内部用了绝对路径，被 nginx 的 catch-all 静默吞掉。200 但内容是错的，比 404 更隐蔽。</description><pubDate>Wed, 05 Aug 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;一台服务器上跑多个应用，通常用 nginx 子路径区分：&lt;code&gt;/app-a/&lt;/code&gt;、&lt;code&gt;/app-b/&lt;/code&gt;、&lt;code&gt;/app-c/&lt;/code&gt;。部署第三个应用时，页面全白——控制台里一堆静态资源 404，但&lt;strong&gt;浏览器不报错&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;这种”页面白屏”是最难排查的一类，因为它是&lt;strong&gt;静默失败&lt;/strong&gt;。&lt;/p&gt;
&lt;section&gt;&lt;h2&gt;根因：绝对路径逃出子路径&lt;a href=&quot;#根因绝对路径逃出子路径&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;SPA 应用（React、Next.js、Vue）内部常用&lt;strong&gt;绝对路径&lt;/strong&gt;引用资源：&lt;/p&gt;&lt;ul&gt;
&lt;li&gt;静态资源：&lt;code&gt;/_next/static/chunks/xxx.js&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;后端 API：&lt;code&gt;/api/config&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;页面路由：&lt;code&gt;/settings&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;&lt;p&gt;应用部署在 &lt;code&gt;/app-c/&lt;/code&gt; 子路径下时，浏览器把这些绝对路径解析为：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;http://host:port/_next/static/xxx.js     ← 没有前缀&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;http://host:port/api/config               ← 没有前缀&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;nginx 找不到对应的 location，请求落入 &lt;strong&gt;catch-all &lt;code&gt;location /&lt;/code&gt;&lt;/strong&gt;。如果 catch-all 恰好反代到另一个 SPA 应用，它会返回一个 200 的 HTML 壳（因为 SPA 对任意路径都返回首页）。浏览器拿到 HTML 但内容不是 JS 文件 → &lt;strong&gt;白屏，且不报错&lt;/strong&gt;。&lt;/p&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;为什么 200 比 404 更坑&lt;a href=&quot;#为什么-200-比-404-更坑&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;404 至少告诉你”路径不对”。这里的问题是：&lt;/p&gt;&lt;ul&gt;
&lt;li&gt;请求 &lt;code&gt;/api/config&lt;/code&gt; → 落 catch-all → 返回别的应用的 HTML → &lt;strong&gt;200&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;前端以为成功，实际拿到的是垃圾 → 业务静默失败&lt;/li&gt;
&lt;/ul&gt;&lt;p&gt;&lt;strong&gt;200 但内容是错的，比 404 隐蔽十倍。&lt;/strong&gt; 排查时不要只看状态码，要看返回的 Content-Type 和实际内容。&lt;/p&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;子路径部署必配的三类路径&lt;a href=&quot;#子路径部署必配的三类路径&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
























&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;路径类型&lt;/th&gt;&lt;th&gt;例子&lt;/th&gt;&lt;th&gt;要不要配&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;页面路由&lt;/td&gt;&lt;td&gt;&lt;code&gt;/app-c/settings&lt;/code&gt;&lt;/td&gt;&lt;td&gt;✅ proxy_pass 到前端端口&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;静态资源&lt;/td&gt;&lt;td&gt;&lt;code&gt;/_next/static/&lt;/code&gt;&lt;/td&gt;&lt;td&gt;✅ 单独 location（SPA 框架的资源路径固定）&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;后端 API&lt;/td&gt;&lt;td&gt;&lt;code&gt;/app-c/api/&lt;/code&gt;&lt;/td&gt;&lt;td&gt;✅ proxy_pass 到后端端口（不要 redirect，会丢请求体）&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;p&gt;框架静态资源路径速查：&lt;/p&gt;&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Next.js&lt;/strong&gt;：&lt;code&gt;/_next/static/&lt;/code&gt; 必须单独配 location&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Vite&lt;/strong&gt;：&lt;code&gt;/assets/&lt;/code&gt; 通常相对路径，但要检查&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Create React App&lt;/strong&gt;：&lt;code&gt;/static/&lt;/code&gt; 同理&lt;/li&gt;
&lt;/ul&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;部署前 5 分钟检查，避免 1 小时返工&lt;a href=&quot;#部署前-5-分钟检查避免-1-小时返工&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;Terminal window&lt;/span&gt;&lt;span&gt;展开&lt;/span&gt;&lt;span&gt;收起&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;# 1. 看 HTML 里引用了哪些路径&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;curl&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;-s&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;http://localhost:PORT/&lt;/span&gt;&lt;span&gt; | &lt;/span&gt;&lt;span&gt;grep&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;-oE&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&apos;(src|href|action)=&quot;[^&quot;]+&quot;&apos;&lt;/span&gt;&lt;span&gt; | &lt;/span&gt;&lt;span&gt;sort&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;-u&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;3&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;4&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;# 2. 识别绝对路径（以 / 开头的）&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;5&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;#    /_next/... → 静态资源&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;6&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;#    /api/...   → API 调用&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;7&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;#    /xxx       → 页面路由&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;8&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;9&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;# 3. 每个绝对路径都要有 nginx location&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;10&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;#    静态资源 → proxy_pass 到前端端口&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;11&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;#    API 路径 → proxy_pass 到后端端口（保留请求体）&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;12&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;#    页面路径 → 带前缀反代&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;13&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;14&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;# 4. 一次性测试所有路径&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;15&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;curl&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;-I&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;http://domain:port/_next/static/chunks/xxx.js&lt;/span&gt;&lt;span&gt;   &lt;/span&gt;&lt;span&gt;# 静态资源&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;16&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;curl&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;-X&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;POST&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;http://domain:port/api/config&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;-d&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&apos;{}&apos;&lt;/span&gt;&lt;span&gt;      &lt;/span&gt;&lt;span&gt;# API（测请求体）&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;span&gt;展开&lt;/span&gt;&lt;span&gt;收起&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;一次惨痛的失败教训&lt;a href=&quot;#一次惨痛的失败教训&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;有一次部署因为没做上面的检查，一路边部署边修：页面白 → 配静态资源 → 链接 404 → 配页面路由 → API 失败 → 配 API → CORS 报错 → 加 CORS 头 → 前端硬编码 localhost → 改相对路径……&lt;/p&gt;&lt;p&gt;用户反复测试了 7 次，总共花了 1 小时 10 分钟，最终项目被放弃删除。&lt;strong&gt;本该 10 分钟搞定的事。&lt;/strong&gt;&lt;/p&gt;&lt;p&gt;根因就是：&lt;strong&gt;没在部署前一次性检查所有绝对路径&lt;/strong&gt;，而是让用户当测试员，一轮一轮发现问题。&lt;/p&gt;&lt;p&gt;正确流程：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;读部署检查清单 → 部署前检查所有路径 → 本地测试所有路径 → 一次性配好 → 用户最终验证（1 次）&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;结论&lt;a href=&quot;#结论&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;子路径部署的铁律：&lt;/p&gt;&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;部署前把 HTML 里所有绝对路径列出来&lt;/strong&gt;，为每个配 nginx location&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;catch-all &lt;code&gt;location /&lt;/code&gt; 是隐患&lt;/strong&gt;，能改成导航页就改&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;用户只做最终验证，不做问题测试员&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;页面白屏先查&lt;strong&gt;是不是 200 但内容错误&lt;/strong&gt;，别只盯 404&lt;/li&gt;
&lt;/ol&gt;&lt;blockquote&gt;&lt;p&gt;&lt;strong&gt;子路径部署的坑不在 nginx，在应用内部的绝对路径。先查路径，再查配置。&lt;/strong&gt;&lt;/p&gt;&lt;/blockquote&gt;&lt;/section&gt;</content:encoded></item><item><title>图片进管道，先分清楚：要&quot;取字&quot;还是&quot;理解&quot;</title><link>https://heaven-1314.github.io/posts/ocr-vs-vision-division/</link><guid isPermaLink="true">https://heaven-1314.github.io/posts/ocr-vs-vision-division/</guid><description>处理图片时最常见的选择错误，是把&quot;提取文字&quot;和&quot;理解内容&quot;混为一谈。两种需求，两类工具，先分清楚再动手。</description><pubDate>Wed, 05 Aug 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;业务系统里经常有”处理一张图”的需求，最常见的选择错误是：&lt;strong&gt;把”提取文字”和”理解内容”混为一谈。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;一个典型场景：用户上传了一张流程图图片，你的 AI 助手需要分析它。你让它”读一下这张图”——结果它返回的是图片里的&lt;strong&gt;内嵌引用&lt;/strong&gt;，而不是图里的文字。因为它走错了工具。&lt;/p&gt;
&lt;section&gt;&lt;h2&gt;两个能力，边界要划清&lt;a href=&quot;#两个能力边界要划清&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;处理图片有两类完全不同的能力：&lt;/p&gt;&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;OCR（光学字符识别）&lt;/strong&gt;：把图里的文字&lt;strong&gt;提取&lt;/strong&gt;出来。只取字，不理解意思。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Vision（多模态理解）&lt;/strong&gt;：理解图片&lt;strong&gt;内容&lt;/strong&gt;。能看图说话、描述画面、回答关于图的问题。&lt;/li&gt;
&lt;/ul&gt;&lt;p&gt;一个”提取文字”的问题，用理解模型去做，又贵又容易跑偏；一个”理解图意”的问题，用纯 OCR 去做，得到一堆字却回答不了问题。&lt;/p&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;边界划分表（铁律）&lt;a href=&quot;#边界划分表铁律&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
























&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;需求&lt;/th&gt;&lt;th&gt;用什么&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;纯提取文字（发票、截图、扫描件）&lt;/td&gt;&lt;td&gt;OCR&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;理解图片内容再回答（“这图在讲什么”）&lt;/td&gt;&lt;td&gt;多模态 Vision&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;流程图 / 截图里的文字&lt;/td&gt;&lt;td&gt;OCR（开启”图片内文字”模式）&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;需要理解图意再回答&lt;/td&gt;&lt;td&gt;多模态 Vision&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;p&gt;&lt;strong&gt;关键判断&lt;/strong&gt;：你最终需要的是”一串文字”，还是”对图的理解”？要字 → OCR；要懂 → Vision。&lt;/p&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;一个真实的坑&lt;a href=&quot;#一个真实的坑&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;用户提交流程图图片，走 OCR 分析。结果 OCR 返回的是&lt;strong&gt;嵌入式图片引用&lt;/strong&gt;而非流程图文字——数据到手却是错的。&lt;/p&gt;&lt;p&gt;原因：OCR 默认模式处理”图片里有图片”的场景，把内嵌图当成了引用，没把文字抽出来。&lt;strong&gt;切换模型版本并开启”图片内文字”选项后，才正确输出流程图文字。&lt;/strong&gt;&lt;/p&gt;&lt;p&gt;这个坑的教训：工具选对只是第一步，&lt;strong&gt;同一类工具的配置细节也可能让它答非所问&lt;/strong&gt;。验证时不要只看”返回了什么”，要确认返回的是你要的那一层内容。&lt;/p&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;文字模型永远别”猜图”&lt;a href=&quot;#文字模型永远别猜图&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;还有一个更隐蔽的坑：&lt;strong&gt;没有视觉能力的纯文字模型，绝不能被要求”看一下这张图”&lt;/strong&gt;。&lt;/p&gt;&lt;p&gt;文字模型处理图片，本质是在猜。对话上下文里有点线索它就能”脑补”出一段看似合理的描述——但那是编的。如果系统里必须有一个文字模型和图片打交道，正确的做法是：&lt;strong&gt;先用多模态模型把图片转成文字描述，再交给文字模型继续处理&lt;/strong&gt;。取字走 OCR，理解走 Vision，文字模型只接文字。&lt;/p&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;结论&lt;a href=&quot;#结论&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;图片处理的第一步不是写代码，是&lt;strong&gt;回答一个问题：这图要的是字，还是懂？&lt;/strong&gt;&lt;/p&gt;&lt;ul&gt;
&lt;li&gt;要字 → OCR（便宜、快、准）&lt;/li&gt;
&lt;li&gt;要懂 → 多模态 Vision（能回答、能描述）&lt;/li&gt;
&lt;li&gt;纯文字模型 → 永远不要让它直接碰图，先转文字再交接&lt;/li&gt;
&lt;/ul&gt;&lt;p&gt;把边界划清楚，处理图片的需求能少踩一半的坑。&lt;/p&gt;&lt;/section&gt;</content:encoded></item><item><title>个人服务器的日常运维清单：磁盘、内存、服务、安全</title><link>https://heaven-1314.github.io/posts/server-ops-checklist/</link><guid isPermaLink="true">https://heaven-1314.github.io/posts/server-ops-checklist/</guid><description>个人跑业务服务器的日常检查清单——磁盘红线、内存尖峰、常驻服务、安全规则、清理动作，以及三条用挂一次换来的教训。</description><pubDate>Wed, 05 Aug 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;一台个人服务器，上面跑着几个业务服务和 Docker 容器。系统盘只有几十 G，某天构建一个东西把盘打满，&lt;strong&gt;所有服务一起卡死&lt;/strong&gt;。运维的核心不是”出事才修”，是&lt;strong&gt;定期检查 + 预判红线&lt;/strong&gt;。&lt;/p&gt;
&lt;section&gt;&lt;h2&gt;日常检查四件套&lt;a href=&quot;#日常检查四件套&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;Terminal window&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;# 1. 磁盘空间（系统盘是红线，数据盘才是大仓库）&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;df&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;-h&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;/&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;3&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;4&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;# 2. 内存（历史上有过突发尖峰挂死）&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;5&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;free&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;-h&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;6&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;7&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;# 3. 常驻服务健康&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;8&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;systemctl&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;list-units&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--state=running&lt;/span&gt;&lt;span&gt; | &lt;/span&gt;&lt;span&gt;grep&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;-E&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&quot;nginx|sshd|docker&quot;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;9&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;10&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;# 4. Docker 容器&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;11&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;docker&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;ps&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;三条用挂一次换来的教训&lt;a href=&quot;#三条用挂一次换来的教训&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;&lt;strong&gt;教训一：系统盘有红线，大东西放数据盘。&lt;/strong&gt; 系统盘空间有限（几十 G），任何大于几 G 的操作（构建镜像、装大数据集、日志堆积）都可能打满。大项目必须放到数据盘（几百 G）。&lt;strong&gt;构建大东西之前先 &lt;code&gt;df -h&lt;/code&gt;，别赌。&lt;/strong&gt;&lt;/p&gt;&lt;p&gt;&lt;strong&gt;教训二：重启验证。&lt;/strong&gt; 改文件、改路径、迁移数据后，立刻 &lt;code&gt;systemctl restart&lt;/code&gt; 验证一次。&lt;strong&gt;运行正常 ≠ 重启后正常&lt;/strong&gt;——有些 bug 只在冷启动时暴露，长期运行的进程会把它藏起来。&lt;/p&gt;&lt;p&gt;&lt;strong&gt;教训三：uptime 会隐藏 bug。&lt;/strong&gt; 一个服务跑了 200 天没出过事，不代表它”健康”，只代表”还没触发那个 bug”。定期滚动重启、定期做状态巡检，让隐藏问题提前暴露。&lt;/p&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;常驻服务清单（可审计）&lt;a href=&quot;#常驻服务清单可审计&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;每个常驻服务都应该有：&lt;strong&gt;systemd 管理 + 开机自启 + 归属说明&lt;/strong&gt;。&lt;/p&gt;





























&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;服务&lt;/th&gt;&lt;th&gt;类型&lt;/th&gt;&lt;th&gt;说明&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;网关入口&lt;/td&gt;&lt;td&gt;systemd&lt;/td&gt;&lt;td&gt;所有流量的入口&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;API 管理&lt;/td&gt;&lt;td&gt;Docker&lt;/td&gt;&lt;td&gt;LLM API 统一管理&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;容器运行时&lt;/td&gt;&lt;td&gt;systemd&lt;/td&gt;&lt;td&gt;容器基础设施（&lt;strong&gt;有独立纪律&lt;/strong&gt;）&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;代理网关&lt;/td&gt;&lt;td&gt;systemd&lt;/td&gt;&lt;td&gt;网络代理&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;p&gt;核心纪律：&lt;strong&gt;基础设施容器绝不能随意重启&lt;/strong&gt;——重启容器运行时会杀掉所有容器，等于一次全站停机。要重启某个容器，用 &lt;code&gt;docker restart &amp;lt;容器名&amp;gt;&lt;/code&gt;。&lt;/p&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;安全规则&lt;a href=&quot;#安全规则&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;SSH 端口暴露公网&lt;/strong&gt;：公网端口每天被大量扫描，靠强密码 + 云安全组兜底&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;关闭不必要的注册 / 开放&lt;/strong&gt;：开源服务默认开着注册就有人来白嫖资源，第一时间关&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;本机不放多余的防火墙&lt;/strong&gt;：云安全组管入口，本机别叠一堆规则增加复杂度&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;对外不裸露真实地址&lt;/strong&gt;：前端 / 给外部看的链接不带服务器真实 IP&lt;/li&gt;
&lt;/ol&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;清理动作&lt;a href=&quot;#清理动作&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;Terminal window&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;# Docker 无用资源&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;docker&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;system&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;prune&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;-f&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;3&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;4&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;# 系统日志（保留 3 天）&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;5&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;journalctl&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--vacuum-time=3d&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;6&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;7&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;# 包管理器缓存&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;8&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;# （pip 等按实际清理）&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;日志和缓存是磁盘的隐形杀手，定期清理比扩容便宜得多。&lt;/p&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;结论&lt;a href=&quot;#结论&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;个人服务器运维就三句话：&lt;/p&gt;&lt;blockquote&gt;&lt;p&gt;&lt;strong&gt;红线提前设，检查定期做，重启勤验证。&lt;/strong&gt;&lt;/p&gt;&lt;/blockquote&gt;&lt;p&gt;系统盘容量、内存尖峰、日志堆积都是”慢性病”，等它爆发那天就是全站卡死。每天花 30 秒跑一遍四件套，比出事修一晚上划算。&lt;/p&gt;&lt;/section&gt;</content:encoded></item><item><title>AI 技能生态治理：做减法比做加法难</title><link>https://heaven-1314.github.io/posts/skill-ecosystem-subtraction/</link><guid isPermaLink="true">https://heaven-1314.github.io/posts/skill-ecosystem-subtraction/</guid><description>装 skill 像买书，装完就完。但 skill 越多，AI 干活前的选型成本越高、token 越浪费、互相调用越混乱。治理的关键是做减法。</description><pubDate>Wed, 05 Aug 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;用过 AI Agent 的人都会经历一个阶段：&lt;strong&gt;疯狂安装 skill 和插件&lt;/strong&gt;。社区推荐帖里全是”必装&quot;&quot;神器”，装上就觉得自己强了。&lt;/p&gt;
&lt;p&gt;然后发现：skill 越来越多，但 AI 干活并没有变好，反而——选型变慢了、每次调用更费了、有些 skill 互相打架、有的装了根本没碰过。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;技能生态的管理，做减法比做加法难。&lt;/strong&gt;&lt;/p&gt;
&lt;section&gt;&lt;h2&gt;五条治理原则&lt;a href=&quot;#五条治理原则&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;&lt;strong&gt;1. 按需取用，禁止堆砌。&lt;/strong&gt; 不因为”热门”就装。先问：这个技能解决什么具体问题？没有它我现在能不能完成？&lt;/p&gt;&lt;p&gt;&lt;strong&gt;2. 同类只留一个。&lt;/strong&gt; 功能重叠的技能会互相调用、关系混乱，每次选型都是浪费。同类对比后只留最好用的一个，不共存。&lt;/p&gt;&lt;p&gt;&lt;strong&gt;3. 安装前安全扫描。&lt;/strong&gt; 技能和 MCP server 权限很大（能碰文件、网络、Shell）。来源不明的，先扫一遍它的定义文件再决定装不装。&lt;/p&gt;&lt;p&gt;&lt;strong&gt;4. 先用现有工具，再补新技能。&lt;/strong&gt; 遇到新需求，第一反应不是”装个新技能”，而是”现有能力里有没有能覆盖的”。搜索有搜索的、深度研究有深度研究的、办公有办公的——先翻自己兜里。&lt;/p&gt;&lt;p&gt;&lt;strong&gt;5. 定期审计。&lt;/strong&gt; 每季度清点一次已装技能的使用频率，低使用率且可替代的，下架归档。&lt;/p&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;一次真实清理：5 个重叠技能&lt;a href=&quot;#一次真实清理5-个重叠技能&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;有一次，我发现自己装了 5 个功能高度重叠的技能（并行 agent 分发、子代理驱动开发、写计划、追问、再一个追问），互相调用关系混乱，导致每次让 AI 干活时选型都纠结，token 白白浪费。&lt;/p&gt;&lt;p&gt;处理方式：&lt;/p&gt;&lt;ol&gt;
&lt;li&gt;把 5 个重叠技能&lt;strong&gt;归档&lt;/strong&gt;到备份目录&lt;/li&gt;
&lt;li&gt;只保留功能最完整的一个同类技能&lt;/li&gt;
&lt;li&gt;其他工作流能力用更轻量的方式替代&lt;/li&gt;
&lt;/ol&gt;&lt;p&gt;结果是：技能选择变清晰了，调用路径变明确了，AI 不再”不知道用哪个”。&lt;/p&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;一次反面教材：重型工具草率引入&lt;a href=&quot;#一次反面教材重型工具草率引入&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;另一次，未经充分评估就装了一个”全量安装”的压缩/模型类工具。依赖链巨大（CUDA + 模型文件），国内下载极慢，最后整个安装卡死。&lt;/p&gt;&lt;p&gt;代价是一整套卸载：清依赖、清配置、清注册的钩子。教训很直接：&lt;/p&gt;&lt;ul&gt;
&lt;li&gt;重型工具先看&lt;strong&gt;依赖体积和离线可用性&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;生产环境 / 主会话&lt;strong&gt;不要试验重型工具&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;评估不了就先不装，别”试试再说”&lt;/li&gt;
&lt;/ul&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;推荐安装流程&lt;a href=&quot;#推荐安装流程&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;社区发现新技能 → 静态扫描定义文件 → 判断现有技能是否已覆盖 → 小步试用（本地、非生产）→ 确认有效才正式引入 → 定期回顾，低使用率下架。&lt;/p&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;选型决策树&lt;a href=&quot;#选型决策树&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;需要新能力？&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;├─ 现有技能能覆盖？            → 直接用，不装&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;3&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;├─ 同类产品已存在？            → 对比后替换，不共存&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;4&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;├─ 来源可信？                → 先扫描再安装&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;5&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;├─ 依赖可接受？              → 小步试，能离线最好&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;6&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;└─ 确认有效后沉淀取舍原因       → 下次不用重新纠结&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;结论&lt;a href=&quot;#结论&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;技能生态治理的本质是&lt;strong&gt;对抗”囤积本能”&lt;/strong&gt;。每一个技能都是你 Agent 的一项”可选项”，选项越多，决策成本越高。&lt;/p&gt;&lt;blockquote&gt;&lt;p&gt;&lt;strong&gt;装得快是本能，删得狠是能力。&lt;/strong&gt;&lt;/p&gt;&lt;/blockquote&gt;&lt;p&gt;定期给 Agent 的技能库做减法，和给代码仓库做重构一样，都是长期健康的一部分。&lt;/p&gt;&lt;/section&gt;</content:encoded></item><item><title>SMB 网络盘上的 Git 锁文件：一个网络文件系统引发的坑</title><link>https://heaven-1314.github.io/posts/smb-git-lock-files/</link><guid isPermaLink="true">https://heaven-1314.github.io/posts/smb-git-lock-files/</guid><description>把 Git 仓库放在 SMB 网络盘上，断网抖动后 Git 全部操作卡死——.git 目录里残留了删不掉的锁文件。</description><pubDate>Wed, 05 Aug 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;把 Git 仓库放在网络盘上（比如用 Obsidian 管理一个远程服务器上的笔记库，本地通过 SMB 挂载），平时没事，&lt;strong&gt;某次网络抖动之后，Git 突然全部操作报错&lt;/strong&gt;：&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;fatal: Unable to create &apos;.git/index.lock&apos;: File exists.&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;fatal: cannot lock ref &apos;HEAD&apos;: Unable to create &apos;.git/refs/heads/main.lock&apos;: File exists.&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;添加、提交、推送全挂了。这是 Git 的锁文件机制在网络文件系统上被”坑”了。&lt;/p&gt;
&lt;section&gt;&lt;h2&gt;根因：网络文件系统不保证原子删除&lt;a href=&quot;#根因网络文件系统不保证原子删除&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;Git 的锁机制依赖文件系统的一个原子操作：&lt;strong&gt;创建锁文件用 &lt;code&gt;O_CREAT | O_EXCL&lt;/code&gt;&lt;/strong&gt;——“如果文件不存在就创建，存在就报错”，这个操作是原子的，用来防止两个进程同时写。&lt;/p&gt;&lt;p&gt;但在 SMB 网络文件系统上，这个保证&lt;strong&gt;不牢靠&lt;/strong&gt;。网络抖动时：&lt;/p&gt;&lt;ol&gt;
&lt;li&gt;Git 进程创建了锁文件（&lt;code&gt;index.lock&lt;/code&gt;）&lt;/li&gt;
&lt;li&gt;删除锁文件的操作因为 SMB 网络延迟 / 抖动&lt;strong&gt;失败&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;锁文件残留在 &lt;code&gt;.git&lt;/code&gt; 目录里&lt;/li&gt;
&lt;li&gt;下次任何 Git 操作看到锁文件存在 → 认为”有进程正在写” → 拒绝执行&lt;/li&gt;
&lt;/ol&gt;&lt;p&gt;&lt;strong&gt;网络断一下，锁就永远留下来了。&lt;/strong&gt;&lt;/p&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;哪些锁文件会阻塞什么&lt;a href=&quot;#哪些锁文件会阻塞什么&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;




















&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;锁文件&lt;/th&gt;&lt;th&gt;阻塞的操作&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;.git/index.lock&lt;/code&gt;&lt;/td&gt;&lt;td&gt;任何改暂存区的操作（&lt;code&gt;add&lt;/code&gt;、&lt;code&gt;commit&lt;/code&gt;）&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;.git/HEAD.lock&lt;/code&gt;&lt;/td&gt;&lt;td&gt;改 HEAD（&lt;code&gt;commit&lt;/code&gt;、&lt;code&gt;merge&lt;/code&gt;、&lt;code&gt;rebase&lt;/code&gt;）&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;.git/refs/heads/*.lock&lt;/code&gt;&lt;/td&gt;&lt;td&gt;更新分支引用（&lt;code&gt;commit&lt;/code&gt;、&lt;code&gt;push&lt;/code&gt;、&lt;code&gt;pull&lt;/code&gt;）&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;立即修复：删掉残留锁文件&lt;a href=&quot;#立即修复删掉残留锁文件&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;在仓库根目录执行：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;Terminal window&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;find&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;.git&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;-name&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&quot;*.lock&quot;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;-delete&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;Windows 命令提示符：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;del /q .git\index.lock .git\HEAD.lock 2&amp;gt;nul&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;del /q .git\refs\heads\*.lock 2&amp;gt;nul&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;注意：删锁文件前确认&lt;strong&gt;没有 Git 进程在真正运行&lt;/strong&gt;——如果有个卡死的 Git 进程握着锁，删了也会被重新创建。&lt;/p&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;预防：降低碰撞概率&lt;a href=&quot;#预防降低碰撞概率&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;&lt;strong&gt;1. 降低自动提交频率。&lt;/strong&gt; 如果用了”自动备份”类插件（定时自动 commit），把间隔调大到 10-30 分钟。自动提交越频繁，并发碰撞的概率越高。&lt;/p&gt;&lt;p&gt;&lt;strong&gt;2. 避免设置包装脚本。&lt;/strong&gt; 某些工具支持配置自定义 Git 路径 / 包装脚本，但配置可能被迁移到工具内部存储里，之后即使从配置文件删掉也不生效——必须在工具设置界面里手动清空。&lt;/p&gt;&lt;p&gt;&lt;strong&gt;3. 考虑本地 + 远程双副本。&lt;/strong&gt; 如果对可靠性要求高，把仓库放本地，通过同步工具推送到远程，而不是直接在网络盘上跑 Git。&lt;/p&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;一句话经验&lt;a href=&quot;#一句话经验&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;blockquote&gt;&lt;p&gt;&lt;strong&gt;网络文件系统（SMB / NFS / 网盘）对 Git 的原子锁操作支持不稳定。仓库尽量放本地盘，放网络盘就要接受”网络抖动 = 锁残留”的风险，并准备好清理手段。&lt;/strong&gt;&lt;/p&gt;&lt;/blockquote&gt;&lt;p&gt;这不是 Git 的 bug，是”网络文件系统不保证本地盘语义”这个通用事实。凡是依赖原子操作的工具（数据库、Git、构建缓存），放在网络盘上都要多一份警惕。&lt;/p&gt;&lt;/section&gt;</content:encoded></item><item><title>一个人 + AI 的团队，开发测试体系怎么建</title><link>https://heaven-1314.github.io/posts/solo-ai-dev-test-system/</link><guid isPermaLink="true">https://heaven-1314.github.io/posts/solo-ai-dev-test-system/</guid><description>没有测试同事、没有产品经理、没有专职 QA，一个人用 AI 交付业务系统，靠的是把&quot;测试职责&quot;写成制度——三层测试、数据管道检查点、修复验证清单。</description><pubDate>Wed, 05 Aug 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;一个人 + AI 做业务系统，最大的风险不是写不出代码，是&lt;strong&gt;没人帮你发现”改坏了”&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;传统团队里，改了代码有人 review、有人测、有人验收。一人团队里这些职责全没了，只能靠&lt;strong&gt;制度代替人&lt;/strong&gt;——把”怎么证明改对了”写进流程，强制执行。&lt;/p&gt;
&lt;section&gt;&lt;h2&gt;一、数据管道：先设计再编码&lt;a href=&quot;#一数据管道先设计再编码&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;任何新数据源接入，数据要经过 6 个阶段流动：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;拉取 → 解析 → 清洗 → 计算 → 评分 → 渲染&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;&lt;strong&gt;铁律&lt;/strong&gt;：不能在最后一个阶段（渲染）才发现第一个阶段（拉取）的问题。每一段出口放一个检查点（详见&lt;a href=&quot;/posts/data-pipeline-stage-validation&quot;&gt;数据管道六阶段验证&lt;/a&gt;）。&lt;/p&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;二、API 契约先行&lt;a href=&quot;#二api-契约先行&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;前后端最容易扯皮的是”字段名对不上”。解法是&lt;strong&gt;契约先行&lt;/strong&gt;：&lt;/p&gt;&lt;blockquote&gt;&lt;p&gt;设计阶段先写出请求/响应的 JSON 形状，前后端各自确认，再开始编码。&lt;/p&gt;&lt;/blockquote&gt;&lt;p&gt;禁止”后端写完让前端适配”或”前端写完让后端照着返回”。契约文件就是你们的产品需求文档。&lt;/p&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;三、三层测试架构&lt;a href=&quot;#三三层测试架构&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;没有 QA，就把”测试职责”拆成三层，每层负责一类问题：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;Layer 3：用户视角（浏览器自动化，走真实网关）&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;├─ 每页截图存档&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;3&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;├─ 验证渲染出来的值正确（不是&quot;元素存在&quot;）&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;4&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;└─ 跨页面导航完整走通&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;5&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;6&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;Layer 2：全链路追踪（浏览器抓网络请求）&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;7&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;├─ 用户点击 → 前端 → HTTP → 网关 → 后端 → 数据库&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;8&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;└─ 验证每一步的 URL、响应码、内容类型&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;9&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;10&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;Layer 1：后端单元 + 接口集成（自动化测试）&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;11&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;├─ 接口形状验证（响应里关键字段都在）&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;12&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;├─ 字段名一致性验证&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;13&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;└─ 边界条件&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;&lt;strong&gt;三层缺一不可&lt;/strong&gt;：Layer 1 证明”接口返回对”，Layer 2 证明”网关转发对”，Layer 3 证明”用户看得到对”。只测一层就宣布”修好了”，是”声称修复但用户看不到”的最常见来源。&lt;/p&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;四、修复验证清单（每次改 bug 必须过）&lt;a href=&quot;#四修复验证清单每次改-bug-必须过&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;修完任何一个 bug，逐项打勾：&lt;/p&gt;&lt;ul&gt;
&lt;li&gt; Layer 1：接口返回正确数据？&lt;/li&gt;
&lt;li&gt; Layer 2：网关代理正常？&lt;/li&gt;
&lt;li&gt; Layer 3：用户能看到正确内容？&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;所有页面&lt;/strong&gt;都验证（不只改的那一页）&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;所有角色/分组&lt;/strong&gt;都验证（不只默认的那个）&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;每个按钮&lt;/strong&gt;点击有效果&lt;/li&gt;
&lt;li&gt; 截图存档作为证据&lt;/li&gt;
&lt;/ul&gt;&lt;p&gt;重点看两条：&lt;/p&gt;&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;“所有 X”类验证&lt;/strong&gt;：很多 bug 只修了”默认的那个”——默认页面修了，其他页面还坏；默认分组测了，其他分组没测。一次改动必须验证所有实例。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;截图存档&lt;/strong&gt;：没有证据的”应该好了”，等于没修。&lt;/li&gt;
&lt;/ol&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;五、bug 修复的层次思维&lt;a href=&quot;#五bug-修复的层次思维&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;业务系统的数据通常是分层的（全局 / 分组 / 个人）。修 bug 时：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;发现 bug → 确认影响范围 → 检查所有层级 → 修复 → 验证所有层级&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;&lt;strong&gt;禁止&lt;/strong&gt;：修完一个层级就声称”修好了”。全局改对了，分组和个人可能还是旧的。&lt;/p&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;六、部署链路检查&lt;a href=&quot;#六部署链路检查&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;从代码到线上，中间每一步都可能出错：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;产出 → 语法检查 → 检查外部依赖 → 检查路径 → 对接数据 → 部署 → 走网关验证&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;具体的坑：脚本语法错误、引用了境外 CDN、用了绝对路径、字段名前后端不一致。每一项都有对应的自动化检查。&lt;/p&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;结论&lt;a href=&quot;#结论&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;一人 + AI 团队的本质，是&lt;strong&gt;把团队的分工写成制度，用工具执行&lt;/strong&gt;：&lt;/p&gt;&lt;ul&gt;
&lt;li&gt;数据管道检查点 = 数据正确性的”代码审查”&lt;/li&gt;
&lt;li&gt;API 契约 = 前后端的”需求文档”&lt;/li&gt;
&lt;li&gt;三层测试 = 你的”测试同事”&lt;/li&gt;
&lt;li&gt;修复验证清单 = 你的”验收人”&lt;/li&gt;
&lt;/ul&gt;&lt;blockquote&gt;&lt;p&gt;&lt;strong&gt;一个人做事，最怕的是”自己觉得没问题”。制度就是那个说”你不许觉得没问题”的人。&lt;/strong&gt;&lt;/p&gt;&lt;/blockquote&gt;&lt;p&gt;没有团队，就给自己造一个虚拟团队——分工、流程、验收标准一个不少。这正是 Agent 时代一个人能顶一个团队的关键。&lt;/p&gt;&lt;/section&gt;</content:encoded></item><item><title>SSH 频繁断连的系统化排查：先列排除清单，别重复试错</title><link>https://heaven-1314.github.io/posts/ssh-drop-systematic-debug/</link><guid isPermaLink="true">https://heaven-1314.github.io/posts/ssh-drop-systematic-debug/</guid><description>SSH 天天断连，试了一堆&quot;常见解法&quot;都没用。问题在于每次只换一个变量的纪律没建立，乱试一通。</description><pubDate>Wed, 05 Aug 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;一个让人抓狂的场景：SSH 连上几分钟就断，天天断。网上搜一圈，答案五花八门——调 keepalive、换客户端、看带宽、查丢包……&lt;strong&gt;挨个试了一遍，全都没用。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;问题不在这些解法，在于&lt;strong&gt;没有用系统化排除法，而是凭感觉乱试&lt;/strong&gt;。&lt;/p&gt;
&lt;section&gt;&lt;h2&gt;正确的打开方式：列排除清单&lt;a href=&quot;#正确的打开方式列排除清单&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;先别动手改配置，把候选变量全列出来，然后&lt;strong&gt;逐项排除，每次只动一个变量&lt;/strong&gt;。&lt;/p&gt;&lt;section&gt;&lt;h3&gt;第一轮：先排除”软件配置”层（改动成本低）&lt;a href=&quot;#第一轮先排除软件配置层改动成本低&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;


































&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;候选&lt;/th&gt;&lt;th&gt;排除方法&lt;/th&gt;&lt;th&gt;结果&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;服务器资源&lt;/td&gt;&lt;td&gt;看内存 / CPU / 磁盘&lt;/td&gt;&lt;td&gt;❌ 正常&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;SSH 配置&lt;/td&gt;&lt;td&gt;服务端加存活探测（如 ClientAliveInterval）&lt;/td&gt;&lt;td&gt;❌ 仍断&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;客户端软件&lt;/td&gt;&lt;td&gt;换一个客户端（A 换成 B）&lt;/td&gt;&lt;td&gt;❌ 仍断&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;带宽限制&lt;/td&gt;&lt;td&gt;检查有没有流量整形 / 限速规则&lt;/td&gt;&lt;td&gt;❌ 移除后带宽恢复但连接仍断&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;丢包&lt;/td&gt;&lt;td&gt;ping 长期监控&lt;/td&gt;&lt;td&gt;❌ 0% 丢包、延迟正常&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;p&gt;&lt;strong&gt;这一轮排除了五个常见嫌疑&lt;/strong&gt;，说明问题不在服务器和软件配置。&lt;/p&gt;&lt;/section&gt;&lt;section&gt;&lt;h3&gt;第二轮：锁定”网络路径”层（剩下的候选）&lt;a href=&quot;#第二轮锁定网络路径层剩下的候选&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;排除掉软件层之后，剩下的候选都指向网络路径的某个环节：&lt;/p&gt;&lt;ul&gt;
&lt;li&gt;⚠️ 双网络环境（有线 + WiFi 同时开）导致&lt;strong&gt;路由切换&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;⚠️ 本地路由器 &lt;strong&gt;NAT 表空闲回收&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;⚠️ ISP 中间设备 &lt;strong&gt;TCP 空闲连接回收&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;⚠️ 网卡硬件 / 驱动问题&lt;/li&gt;
&lt;/ul&gt;&lt;p&gt;其中”双网络同时开”嫌疑最大——两个网卡并存时，系统可能在切换默认路由，SSH 长连接经不起切换。&lt;/p&gt;&lt;p&gt;验证方法同样是一变量一改：&lt;strong&gt;拔掉有线只用 WiFi 跑 25 分钟&lt;/strong&gt;，连接稳定了；但这只是单次观察，还需要长周期确认。&lt;/p&gt;&lt;/section&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;三个排查纪律&lt;a href=&quot;#三个排查纪律&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;&lt;strong&gt;1. 每次只动一个变量。&lt;/strong&gt; 同时改 keepalive + 换客户端 + 调带宽，断连了就不知道是谁的功劳。&lt;/p&gt;&lt;p&gt;&lt;strong&gt;2. 记录每次尝试的效果。&lt;/strong&gt; 一张表格（日期 / 尝试 / 效果），几轮下来哪些排除过一目了然，避免重复试错。&lt;/p&gt;&lt;p&gt;&lt;strong&gt;3. 先排除便宜假设，再动贵的。&lt;/strong&gt; 改配置、换客户端便宜，换网络环境、换硬件贵。先排便宜的。&lt;/p&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;一个更深的教训&lt;a href=&quot;#一个更深的教训&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;排查到最后，很多”SSH 断连”其实是&lt;strong&gt;网络空闲回收&lt;/strong&gt;——运营商 / 路由器对长时间空闲的 TCP 连接做回收。这类问题的共性特征是：&lt;/p&gt;&lt;ul&gt;
&lt;li&gt;不是”断”，是”一段时间不用就死”&lt;/li&gt;
&lt;li&gt;高峰期活跃时反而不掉&lt;/li&gt;
&lt;li&gt;客户端和服务端配置都”看起来没问题”&lt;/li&gt;
&lt;/ul&gt;&lt;p&gt;如果你遇到的是这个模式，方向就不是调 SSH 配置，而是看&lt;strong&gt;网络路径上谁在回收空闲连接&lt;/strong&gt;。&lt;/p&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;结论&lt;a href=&quot;#结论&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;SSH 断连这类”配置看着都对但就是不行”的问题，最大的敌人是&lt;strong&gt;病急乱投医&lt;/strong&gt;。&lt;/p&gt;&lt;blockquote&gt;&lt;p&gt;&lt;strong&gt;先列排除清单，每次只动一个变量，记录效果，先便宜后贵。&lt;/strong&gt;&lt;/p&gt;&lt;/blockquote&gt;&lt;p&gt;系统化的排除，比灵机一动的十个”偏方”都有效。&lt;/p&gt;&lt;/section&gt;</content:encoded></item><item><title>天地图 API + Leaflet：一个通勤选房的轻量级实践</title><link>https://heaven-1314.github.io/posts/tianditu-leaflet-housing-map/</link><guid isPermaLink="true">https://heaven-1314.github.io/posts/tianditu-leaflet-housing-map/</guid><description>几十个候选小区，怎么筛出对两人通勤都友好的？用天地图 API 预计算通勤时间 + Leaflet 可视化，静态页面秒开、不暴露 API key。</description><pubDate>Wed, 05 Aug 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;需求很实际：北京的一批保障房候选小区，怎么挑一个对&lt;strong&gt;两个人的工作地通勤都友好&lt;/strong&gt;的？&lt;/p&gt;
&lt;p&gt;方案：天地图 API 算通勤 + Leaflet 做地图可视化。全程预计算成静态 JSON，页面秒开、不暴露 API key。&lt;/p&gt;
&lt;section&gt;&lt;h2&gt;架构：预计算，不实时调用&lt;a href=&quot;#架构预计算不实时调用&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;候选小区列表 → 地理编码(地址→经纬度) → 到双方工作地的路径规划(公交/驾车)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;            &lt;/span&gt;&lt;/span&gt;&lt;span&gt;→ 通勤时间打分 → 排序 → 静态 JSON(预计算) → Leaflet 渲染&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;&lt;strong&gt;关键设计：所有地图 API 调用在生成静态数据时一次性跑完&lt;/strong&gt;，页面只读预计算的 JSON 渲染，不做实时 API 调用。&lt;/p&gt;&lt;p&gt;好处：&lt;/p&gt;&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;省调用量&lt;/strong&gt;：几十个小区每个算 2 条路线，一次跑完&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;页面秒开&lt;/strong&gt;：没有等待 API 的延迟&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;不暴露 key&lt;/strong&gt;：key 只在生成脚本里，不进页面&lt;/li&gt;
&lt;/ul&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;天地图 API 三个接口&lt;a href=&quot;#天地图-api-三个接口&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;天地图 = 国家地理信息公共服务平台（&lt;code&gt;lbs.tianditu.gov.cn&lt;/code&gt;）。&lt;/p&gt;&lt;p&gt;&lt;strong&gt;1. 地理编码（地址 → 经纬度）&lt;/strong&gt;&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;ds &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; json.&lt;/span&gt;&lt;span&gt;dumps&lt;/span&gt;&lt;span&gt;({&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&quot;keyName&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;北京市海淀区xxx&quot;&lt;/span&gt;&lt;span&gt;}, &lt;/span&gt;&lt;span&gt;ensure_ascii&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;False&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;url &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;&quot;http://api.tianditu.gov.cn/geocoder?ds=&quot;&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;+&lt;/span&gt;&lt;span&gt; urllib.parse.&lt;/span&gt;&lt;span&gt;quote&lt;/span&gt;&lt;span&gt;(ds) &lt;/span&gt;&lt;span&gt;+&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;&quot;&amp;amp;tk=&quot;&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;+&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;TK&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;3&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;# 返回 {&quot;location&quot;: {&quot;lon&quot;: 116.3, &quot;lat&quot;: 39.9, ...}}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;&lt;strong&gt;2. 公交路径规划（参数坑最多）&lt;/strong&gt;&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;ps &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; json.&lt;/span&gt;&lt;span&gt;dumps&lt;/span&gt;&lt;span&gt;({&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;&quot;startposition&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;f&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt;slon&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt;slat&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;3&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;&quot;endposition&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;f&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt;elon&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt;elat&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;4&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;&quot;linetype&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;1&quot;&lt;/span&gt;&lt;span&gt;   &lt;/span&gt;&lt;span&gt;# 1=较快捷 / 2=少换乘 / 4=少步行 / 8=不坐地铁&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;5&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;}, &lt;/span&gt;&lt;span&gt;ensure_ascii&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;False&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;6&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;url &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;&quot;http://api.tianditu.gov.cn/transit?type=busline&amp;amp;postStr=&quot;&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;+&lt;/span&gt;&lt;span&gt; urllib.parse.&lt;/span&gt;&lt;span&gt;quote&lt;/span&gt;&lt;span&gt;(ps) &lt;/span&gt;&lt;span&gt;+&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;&quot;&amp;amp;tk=&quot;&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;+&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;TK&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;7&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;# 注意：字段是全小写 startposition/endposition/linetype，不是 start/end&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;8&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;# 总时长 = results[].lines[].segments[].segmentLine[0].segmentTime 累加&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;&lt;strong&gt;3. 驾车路径规划（字段名和公交不同，别混）&lt;/strong&gt;&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;ps &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; json.&lt;/span&gt;&lt;span&gt;dumps&lt;/span&gt;&lt;span&gt;({&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&quot;orig&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;f&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt;slon&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt;slat&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;&quot;dest&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;f&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt;elon&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt;elat&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;&quot;style&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;0&quot;&lt;/span&gt;&lt;span&gt;}, &lt;/span&gt;&lt;span&gt;ensure_ascii&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;False&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;url &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;&quot;http://api.tianditu.gov.cn/drive?type=drive&amp;amp;postStr=&quot;&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;+&lt;/span&gt;&lt;span&gt; urllib.parse.&lt;/span&gt;&lt;span&gt;quote&lt;/span&gt;&lt;span&gt;(ps) &lt;/span&gt;&lt;span&gt;+&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;&quot;&amp;amp;tk=&quot;&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;+&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;TK&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;3&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;# 驾车用 orig/dest/style，公交用 startposition/endposition/linetype&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;三个实测的坑&lt;a href=&quot;#三个实测的坑&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;&lt;strong&gt;坑一：公交接口字段名全小写。&lt;/strong&gt; &lt;code&gt;startposition&lt;/code&gt;/&lt;code&gt;endposition&lt;/code&gt;/&lt;code&gt;linetype&lt;/code&gt;，写成 &lt;code&gt;start&lt;/code&gt;/&lt;code&gt;end&lt;/code&gt; 直接报参数错误。每个接口的字段各不相同，别复用错。&lt;/p&gt;&lt;p&gt;&lt;strong&gt;坑二：服务端 key 浏览器直调会被 WAF 拒。&lt;/strong&gt; 天地图检测到浏览器请求（UA 是浏览器 / 带 Referer）就拒绝；跨源还会被浏览器的跨域安全策略拦（&lt;code&gt;ERR_BLOCKED_BY_ORB&lt;/code&gt;）。解法：&lt;strong&gt;key 只放后端 / 代理&lt;/strong&gt;，服务端调完把结果给前端。&lt;/p&gt;&lt;p&gt;&lt;strong&gt;坑三：底图瓦片也要同源反代。&lt;/strong&gt; 瓦片请求同样面临跨域问题，用 nginx 反代瓦片，伪装成非浏览器请求：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;location&lt;/span&gt;&lt;span&gt; ~ &lt;/span&gt;&lt;span&gt;^/tdt/vec/(\d+)/(\d+)/(\d+)\.png$ &lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;   &lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;set &lt;/span&gt;&lt;span&gt;$&lt;/span&gt;&lt;span&gt;tk&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&quot;你的KEY&quot;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;3&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;   &lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;proxy_pass &lt;/span&gt;&lt;span&gt;http://tile.your-domain.com/vec_w/wmts?SERVICE=WMTS&amp;amp;...&amp;amp;tk=$&lt;/span&gt;&lt;span&gt;tk&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;4&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;   &lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;proxy_set_header &lt;/span&gt;&lt;span&gt;User-Agent &lt;/span&gt;&lt;span&gt;&quot;curl/7.68.0&quot;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;5&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;   &lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;proxy_set_header &lt;/span&gt;&lt;span&gt;Referer &lt;/span&gt;&lt;span&gt;&quot;&quot;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;6&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;   &lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;proxy_set_header &lt;/span&gt;&lt;span&gt;Origin &lt;/span&gt;&lt;span&gt;&quot;&quot;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;7&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;8&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;# 前端直接用 L.tileLayer(&apos;/tdt/vec/{z}/{x}/{y}.png&apos;)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;批量调用要限速 + 重试&lt;a href=&quot;#批量调用要限速--重试&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;几十个小区逐个算公交路线，批量调用必须&lt;strong&gt;限速 + 超时重试&lt;/strong&gt;（本项目逐小区跑，超时重试 3 次 + 间隔）。别一把梭全发出去，会被限流。&lt;/p&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;可视化&lt;a href=&quot;#可视化&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;Leaflet 画地图，圆形标记按通勤排名分级着色，点击显示通勤分钟 / 月租。纯前端，无框架，一个 HTML 文件搞定。&lt;/p&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;结论&lt;a href=&quot;#结论&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;这个项目最值得借鉴的&lt;strong&gt;不是地图本身，是”预计算”这个设计&lt;/strong&gt;：&lt;/p&gt;&lt;blockquote&gt;&lt;p&gt;&lt;strong&gt;能提前算完的，就别让用户等；能在服务端算的，就别让浏览器碰。&lt;/strong&gt;&lt;/p&gt;&lt;/blockquote&gt;&lt;p&gt;用一次性的预计算脚本把 API 调用全部跑完、结果落成静态 JSON，页面只做渲染——省调用、省延迟、保安全。很多”地图类”需求都适用这个套路。&lt;/p&gt;&lt;/section&gt;</content:encoded></item><item><title>Windows 网络盘的&quot;双幽灵&quot;事故：换盘符不是容灾</title><link>https://heaven-1314.github.io/posts/windows-network-drive-ghost/</link><guid isPermaLink="true">https://heaven-1314.github.io/posts/windows-network-drive-ghost/</guid><description>映射网络盘断开后，加了第二个&quot;备用盘符&quot;，结果 Explorer 显示两个断开的幽灵盘，越修越乱。根因是把盘符名当成了独立故障域。</description><pubDate>Wed, 05 Aug 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Windows 上把远程目录映射成网络盘（SMB），某天盘突然”断开”了。常见的第一反应：换个盘符重新映射，或者加一个”备用盘符”做容灾。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;这一步就是事故的开始。&lt;/strong&gt;&lt;/p&gt;
&lt;section&gt;&lt;h2&gt;现场：两个盘符，一个幽灵&lt;a href=&quot;#现场两个盘符一个幽灵&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;映射盘 &lt;code&gt;N:&lt;/code&gt; 断开后，你加了 &lt;code&gt;O:&lt;/code&gt; 做后备。结果 Explorer 里&lt;strong&gt;同时显示两个”断开的网络驱动器”&lt;/strong&gt;，都访问不了。&lt;/p&gt;&lt;p&gt;逐层取证，发现一个矛盾：&lt;/p&gt;&lt;ul&gt;
&lt;li&gt;&lt;code&gt;net use&lt;/code&gt; 列表：&lt;strong&gt;空&lt;/strong&gt;（系统认为没有映射）&lt;/li&gt;
&lt;li&gt;注册表持久映射：&lt;strong&gt;不存在&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;但 Explorer 显示盘存在，底层设备表也显示盘符指向那个共享&lt;/li&gt;
&lt;/ul&gt;&lt;p&gt;&lt;strong&gt;系统”认为”盘不存在，但 Explorer 和底层设备都”显示”它存在。&lt;/strong&gt; 这是 Windows SMB 重定向层的一个”幽灵”状态——介于存在和不存在之间的第三种状态。&lt;/p&gt;&lt;p&gt;更关键的是：&lt;strong&gt;换盘符根本解决不了&lt;/strong&gt;。新加的 &lt;code&gt;O:&lt;/code&gt; 也报”盘名已占用”，两个盘符共享同一条故障链路。&lt;/p&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;为什么之前的修复都没用&lt;a href=&quot;#为什么之前的修复都没用&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
























&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;尝试&lt;/th&gt;&lt;th&gt;修的层&lt;/th&gt;&lt;th&gt;实际故障层&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;清理残留隧道进程&lt;/td&gt;&lt;td&gt;隧道层&lt;/td&gt;&lt;td&gt;重定向层&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;调整 keepalive&lt;/td&gt;&lt;td&gt;隧道层&lt;/td&gt;&lt;td&gt;重定向层&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;换盘符做后备&lt;/td&gt;&lt;td&gt;&lt;strong&gt;盘符层&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;重定向层&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;p&gt;前几轮在隧道 / 进程层打转，因为故障根本不在那一层。&lt;strong&gt;最危险的是”换盘符做后备”&lt;/strong&gt;——它假设”换盘符 = 换故障域”，但两个盘符映射同一个共享，共享的是同一条 SMB 会话、同一个重定向状态。&lt;strong&gt;换盘符只是把同一个幽灵复制成第二个。&lt;/strong&gt;&lt;/p&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;根因：盘符名不是独立故障域&lt;a href=&quot;#根因盘符名不是独立故障域&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;blockquote&gt;&lt;p&gt;&lt;strong&gt;盘符名不是独立故障域。&lt;/strong&gt; &lt;code&gt;N:&lt;/code&gt; 和 &lt;code&gt;O:&lt;/code&gt; 映射同一个共享，共享 Windows SMB 重定向器的同一会话。把”换盘符”当成”容灾”，只会把同一个幽灵复制到第二个盘符。&lt;/p&gt;&lt;/blockquote&gt;&lt;p&gt;一个映射盘的健康状态由多层决定：盘符（别名）、SMB 会话、网络重定向设备、系统记住的连接。&lt;strong&gt;盘符只是最外层的别名&lt;/strong&gt;，它不代表任何一层故障隔离。&lt;/p&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;修复：单盘符 + 双层护栏&lt;a href=&quot;#修复单盘符--双层护栏&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;修复方案的核心是&lt;strong&gt;承认盘符不提供容灾&lt;/strong&gt;，然后用架构护栏让”再加一个盘符”这件事从物理上不可能：&lt;/p&gt;&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;单盘符状态机&lt;/strong&gt;：只管理一个盘符，不创建任何备用。盘不可读但底层隧道健康时，判定为”重定向层问题”，不重启健康的底层进程。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;启动期护栏&lt;/strong&gt;：配置文件里如果出现第二个盘符，服务&lt;strong&gt;拒绝启动&lt;/strong&gt;，并给出说明：“备用盘符不是独立故障域，会复现双幽灵。要在证明故障域独立后才能扩展。”&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;运行时护栏&lt;/strong&gt;：每次挂载成功前，扫描所有盘符，发现&lt;strong&gt;多于一个盘符绑定同一个共享&lt;/strong&gt;，立即报错，而不是继续在第二个幽灵上叠挂载。&lt;/li&gt;
&lt;/ol&gt;&lt;p&gt;护栏的意义：&lt;strong&gt;让错误假设在”发生前”就失败，而不是运行几天后才以幽灵的形式暴露。&lt;/strong&gt;&lt;/p&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;四条可复用的经验&lt;a href=&quot;#四条可复用的经验&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;&lt;strong&gt;1. 容灾设计必须先画故障域表。&lt;/strong&gt; “后备”只有在故障域独立时才成立。设计任何后备方案前，先回答：&lt;strong&gt;后备目标和主目标在哪些层是独立的？&lt;/strong&gt; 同服务器、同凭据、同会话——答不上来，就不是后备，是别名。&lt;/p&gt;&lt;p&gt;&lt;strong&gt;2. 权威状态源不止一个。&lt;/strong&gt; 判断一个盘是否存在，不能只看 &lt;code&gt;net use&lt;/code&gt;（系统会话层）。还要看设备表（底层设备层）和 Explorer（UI 层）。&lt;strong&gt;&lt;code&gt;net use&lt;/code&gt; 看不到，不代表盘真的不存在。&lt;/strong&gt;&lt;/p&gt;&lt;p&gt;&lt;strong&gt;3. “对象存在” ≠ “功能可用”。&lt;/strong&gt; 映射条目存在 / 进程存活 / 配置存在，都&lt;strong&gt;不等于&lt;/strong&gt;实际可达、可读、健康。任何状态判断都要验证最终的用户路径（真实读取、真实连接），而不是只看对象存在。&lt;/p&gt;&lt;p&gt;&lt;strong&gt;4. 错误码有歧义。&lt;/strong&gt; 同一个错误码（比如”设备名已占用”）可能代表”真的被幽灵占用”，也可能代表”其实已经正常映射了”。&lt;strong&gt;不能凭一次错误码就下结论&lt;/strong&gt;——先真实读取，不可读才重建，重建仍失败再判异常。&lt;/p&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;结论&lt;a href=&quot;#结论&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;这个事故的根因不在代码细节，在&lt;strong&gt;设计阶段的一个错误假设&lt;/strong&gt;：想当然地认为”换个名字就是新的路径”。&lt;/p&gt;&lt;blockquote&gt;&lt;p&gt;&lt;strong&gt;迁移不是搬代码，是保持旧系统所有可观察行为；容灾不是加别名，是先证明故障域独立。&lt;/strong&gt;&lt;/p&gt;&lt;/blockquote&gt;&lt;p&gt;当系统出现”越修越乱”的迹象时，停下来问一句：我是不是在同一个幽灵上复制副本？&lt;/p&gt;&lt;/section&gt;</content:encoded></item><item><title>写操作必须同步失效缓存：为什么&quot;改了却没生效&quot;</title><link>https://heaven-1314.github.io/posts/write-op-must-invalidate-cache/</link><guid isPermaLink="true">https://heaven-1314.github.io/posts/write-op-must-invalidate-cache/</guid><description>给接口加缓存后，缓存就成了必须维护的&quot;消费端&quot;。任何写操作改了底层数据却不清缓存，用户就会看到&quot;改了为什么还没好&quot;。</description><pubDate>Wed, 05 Aug 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;系统里有个”部门人数”统计，为了性能加了 60 秒内存缓存。某天做”离职”操作，人明明走了，用户刷新一看人数没变；过一会儿再看，人又”消失”了。&lt;strong&gt;同一天里同一个数字两次不一致。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;这不是性能问题，是架构问题：&lt;strong&gt;给接口加缓存之后，缓存就变成了一条必须维护的消费端。&lt;/strong&gt;&lt;/p&gt;
&lt;section&gt;&lt;h2&gt;问题长什么样&lt;a href=&quot;#问题长什么样&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;典型场景：&lt;/p&gt;&lt;ol&gt;
&lt;li&gt;一个读接口被频繁访问，于是加了缓存（key 按查询参数，TTL 60 秒）&lt;/li&gt;
&lt;li&gt;一系列&lt;strong&gt;写操作&lt;/strong&gt;会改变这个接口的底层数据：新增、删除、批量导入、重新计算&lt;/li&gt;
&lt;li&gt;写操作成功了，但&lt;strong&gt;没清缓存&lt;/strong&gt; → 读接口继续吐旧数据&lt;/li&gt;
&lt;li&gt;直到 TTL 过期或 key 变化，用户才看到新数据&lt;/li&gt;
&lt;/ol&gt;&lt;p&gt;用户视角就是一句”&lt;strong&gt;改了为什么还没好&lt;/strong&gt;”。等 TTL 过期？60 秒都等不起，更别说用户会手动刷新触发各种不可预期的顺序。&lt;/p&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;两个同类 bug&lt;a href=&quot;#两个同类-bug&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;&lt;strong&gt;案例一：离职/入职没清缓存。&lt;/strong&gt; “部门人数”缓存 key 只含时间段参数，但”有没有人离职”跟时间段无关——单靠换 key 根本挡不住。离职接口成功返回，缓存还是旧的，人还在里面。直到用户切到别的时间段再切回来，缓存过期重查，人才消失。&lt;strong&gt;同一个操作，两次看到两个不同的结果。&lt;/strong&gt;&lt;/p&gt;&lt;p&gt;&lt;strong&gt;案例二：改 AI 生成结论的提示词没清缓存。&lt;/strong&gt; 把大模型结论的生成规则改了（比如禁用一个指标），服务重启了，但结论缓存表里的旧记录还在，页面读缓存直接显示，用户看到的还是改之前的结论。代码层完全正确——提示词对、数据对、单测过、服务重启了——&lt;strong&gt;但用户视角就是”反复改都没好”&lt;/strong&gt;。&lt;/p&gt;&lt;p&gt;两个案例同一个根因：&lt;strong&gt;写操作（改数据 / 改口径）与缓存失效脱钩了。&lt;/strong&gt;&lt;/p&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;五条设计原则&lt;a href=&quot;#五条设计原则&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;


































&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;#&lt;/th&gt;&lt;th&gt;原则&lt;/th&gt;&lt;th&gt;做法&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;&lt;strong&gt;1&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;&lt;strong&gt;写操作 = 缓存失效点&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;设计功能点时先问：这个数据被谁缓存？哪些写操作会改变它？列成”写操作 × 消费端缓存”清单，一个不漏&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;strong&gt;2&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;&lt;strong&gt;写操作统一走失效函数&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;项目里有缓存，就建一个 &lt;code&gt;invalidate_xxx_cache()&lt;/code&gt;，所有写操作成功后可调用，不要散落一堆 &lt;code&gt;cache.clear()&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;strong&gt;3&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;&lt;strong&gt;缓存 key 覆盖不了所有维度&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;key 只能按查询参数区分，但影响数据的维度（新增 / 删除 / 重算）往往跟查询参数无关——必须显式失效&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;strong&gt;4&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;&lt;strong&gt;TTL 只是兜底，不是方案&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;永远不要说”等过期”。用户的预期是”改了马上看到”&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;strong&gt;5&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;&lt;strong&gt;影响面清单把缓存算进去&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;大改动列影响面时，缓存消费端要进清单，详见&lt;a href=&quot;/posts/multidim-split-anti-diffusion&quot;&gt;需求拆分防扩散&lt;/a&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;上线前的触发三问&lt;a href=&quot;#上线前的触发三问&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;任何写操作上线前，先回答：&lt;/p&gt;&lt;p&gt;&lt;strong&gt;1. 这个写操作改了哪些数据？&lt;/strong&gt;（新增？删除？修改属性？重算结论？）&lt;/p&gt;&lt;p&gt;&lt;strong&gt;2. 哪些接口 / 页面缓存了这些数据？&lt;/strong&gt;（内存缓存？持久化缓存表？TTL 多久？）&lt;/p&gt;&lt;p&gt;&lt;strong&gt;3. 写操作成功后缓存失效了吗？&lt;/strong&gt;（有失效函数吗？没有就补上。）&lt;/p&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;结论&lt;a href=&quot;#结论&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;“改了没生效”这一类 bug 的共同点，是&lt;strong&gt;改了源头 ≠ 改了运行时 ≠ 改了展示链路的旧状态&lt;/strong&gt;。代码正确只是第一步，展示端的历史缓存不清，用户看到的永远是新代码之前的旧世界。&lt;/p&gt;&lt;blockquote&gt;&lt;p&gt;&lt;strong&gt;写操作与缓存失效必须同步，TTL 永远只是兜底。&lt;/strong&gt;&lt;/p&gt;&lt;/blockquote&gt;&lt;/section&gt;</content:encoded></item></channel></rss>