<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>踩坑 on idiotfan</title><link>https://idiotfan.wang/tags/%E8%B8%A9%E5%9D%91/</link><description>Recent content in 踩坑 on idiotfan</description><generator>Hugo</generator><language>zh-cn</language><lastBuildDate>Sat, 22 Aug 2026 18:30:00 +0800</lastBuildDate><atom:link href="https://idiotfan.wang/tags/%E8%B8%A9%E5%9D%91/index.xml" rel="self" type="application/rss+xml"/><item><title>自建 VPN 折腾记：从一台 VPS 到全屋全店的透明代理网络</title><link>https://idiotfan.wang/posts/self-built-vpn-journey/</link><pubDate>Sat, 22 Aug 2026 18:30:00 +0800</pubDate><guid>https://idiotfan.wang/posts/self-built-vpn-journey/</guid><description>三个月、两块大陆、五台路由器：一次自建 VPN 的完整踩坑实录——QUIC 大战、DNS 污染、MTU 黑洞、DDNS 抢域名战争，以及最后沉淀下来的设计原则。（本文所有地址、密码均已脱敏）</description><content:encoded><![CDATA[<blockquote>
<p>⚠️ <strong>脱敏声明</strong>：本文是折腾过程的记录与复盘。所有服务器地址、域名、密码、密钥、token 一律以占位符代替，请勿在公开笔记里保存真实凭据——这是本文最重要的教训之一。</p>
</blockquote>
<h2 id="为什么自建">为什么自建</h2>
<p>商业机场的问题：节点质量不可控、流量共享、跑路风险。于是从 2026 年 6 月开始，我用三个多月时间搭了一套自己的网络：</p>
<ul>
<li><strong>两个自建出口节点</strong>：一台美国 CN2 线路的 VPS（日常主力），一台日本大阪 IIJ 线路的 VPS（兜底）</li>
<li><strong>一个自建订阅服务器</strong>：跑在美国 VPS 上，一个 URL 管所有客户端</li>
<li><strong>客户端矩阵</strong>：家里主路由 + 多台店里旁路由（iStoreOS + OpenClash）、两台 Mac（Clash Verge Rev + TUN）、iPhone（Shadowrocket）、一台带电池和蜂窝网络的便携路由（出门即热点）</li>
</ul>
<p>最终效果：五台路由器全部验证通过，故障切换自动兜底，换 VPS 的 IP 时<strong>所有客户端零改动</strong>。</p>
<h2 id="架构一句话">架构一句话</h2>
<pre tabindex="0"><code>美国 VPS ──┐
           ├── 订阅服务器(同一台美国VPS) ── 各客户端拉订阅
日本 VPS ──┘
</code></pre><p>两个关键设计决定：</p>
<ol>
<li><strong>节点寻址一律用域名 + DDNS，不写裸 IP</strong>。DuckDNS 免费，VPS 上 cron 每 5 分钟同步一次。后来美国 VPS 机房迁移、日本节点换了三台机器，客户端配置一行都没改过。</li>
<li><strong>分组用 fallback（故障切换），美国优先、日本兜底</strong>。不用&quot;自动选最快&quot;——日本实测比美国快 3 倍，但我坚持美国优先，理由只有一个：<strong>出口 IP 要稳定不飘</strong>。登录态、风控都吃频繁换 IP 的亏。</li>
</ol>
<h2 id="折腾时间线">折腾时间线</h2>
<h3 id="阶段一单机起家6-月上旬">阶段一：单机起家（6 月上旬）</h3>
<p>第一台美国 VPS 上用 3x-ui 管理 VLESS + Reality，监听 TCP 443；后来又装了 Hysteria2，监听 UDP 443。<strong>TCP 和 UDP 的 443 是两条独立通道，可以共存</strong>——这台机器从此一个端口都不浪费。</p>
<p>很快吃到第一次教训：IP 被墙了。在商家面板里换机房迁移，数据无损、拿到新 IP——这时候才体会到&quot;节点写域名不写 IP&quot;有多值钱：改一条 DDNS 记录，全部设备几分钟内自动跟上。</p>
<h3 id="阶段二quic-与-youtube-的战争6-月中旬">阶段二：QUIC 与 YouTube 的战争（6 月中旬）</h3>
<p>症状很诡异：YouTube 网页视频能放但页面 UI 刷不出来；安卓/iOS 原生 app 直接连不上；手机单独挂代理又一切正常。修好安卓坏 iOS，按下葫芦浮起瓢。</p>
<p>根因是 <strong>QUIC（UDP 443，HTTP/3 的底座）与节点传输方式的匹配问题</strong>：</p>
<table>
	<thead>
			<tr>
					<th>节点类型</th>
					<th>QUIC 该怎么办</th>
			</tr>
	</thead>
	<tbody>
			<tr>
					<td>TCP 传输节点（Reality/Trojan 等）</td>
					<td><strong>必须屏蔽 QUIC</strong>（drop UDP 443）。QUIC 塞进 TCP 隧道 = UDP-over-TCP，又慢又不稳</td>
			</tr>
			<tr>
					<td>UDP 原生节点（Hysteria2/TUIC）</td>
					<td><strong>必须放开 QUIC</strong>。这才是它的主场</td>
			</tr>
	</tbody>
</table>
<p>&ldquo;打地鼠现象&quot;就是你在 TCP 节点上反复调 QUIC 开关——那条路上无解，根治只能换 UDP 原生节点。</p>
<p>另一个结论：<strong>iOS 是老大难</strong>。透明代理下无论怎么调，iOS YouTube app 都时好时坏（它对静默 DROP 不回退）。正解简单粗暴：iOS 别走透明代理，直接 Shadowrocket 连 Hy2 节点。安卓的 Cronet 回退果断，反而没事。</p>
<h3 id="阶段三dns-污染从-passwall-迁到-openclash6-月中旬">阶段三：DNS 污染，从 Passwall 迁到 OpenClash（6 月中旬）</h3>
<p>iStoreOS 升级 24.10 后 Passwall 连环报错，正好顺势搬家。压死骆驼的最后一根稻草其实是 <strong>DNS 污染</strong>：Passwall 默认的 chinadns-ng 明文 DNS 模式下，<code>www.google.com</code> 这类重污染域名直接被 GFW 投毒解析成假 IP，怎么调代理都没用。</p>
<p>mihomo 系（OpenClash / Clash Verge）的 <strong>fake-ip 机制天生免疫</strong>：本地只发假 IP，真实解析发生在节点端。迁移后国内外分流正常，google 秒开。</p>
<p>顺带踩坑：OpenClash 核心&quot;一直在更新&rdquo;，是核心自动更新在墙内反复下载失败，关掉 <code>auto_update</code> 即安。</p>
<h3 id="阶段四日本节点的血泪史7-月初">阶段四：日本节点的血泪史（7 月初）</h3>
<p>想加个日本节点做兜底，结果一台机器贡献了三个经典案例：</p>
<ol>
<li><strong>MTU 黑洞</strong>。某些&quot;优化线路&quot;本身是隧道，MTU 被压小，1500 字节的大包（恰好是 TLS 握手用的那种）在路上被静默丢弃、反复重传——表现为直连 TLS 握手要 <strong>9.6 秒</strong>。修复：把 VPS 网卡 MTU 压到 1280 并做成开机服务，握手立刻降到 0.28 秒。诊断口诀：&ldquo;ping 得通但 TLS 握手巨慢 ≈ 先查 MTU&rdquo;。</li>
<li><strong>TCP 中间盒破坏 Reality</strong>。这条线路上有个 TCP 加速盒会重切分段，xray 日志狂刷 <code>processed invalid connection</code>——Reality 的字节级认证扛不住。同样的配置换个机房就好。而 shadow-tls v3 因为做的是真 TLS 握手，反而活下来了。</li>
<li><strong>DDNS 抢域名战争（最阴的一个）</strong>。换机后服务&quot;时好时坏&quot;查了半天：旧 VPS 的 crontab 里还留着 DDNS 更新脚本，每 5 分钟把域名抢回去指向它自己！客户端一半时间连到旧机器（上面跑的还是旧协议），一半时间连新机器。教训刻进骨头里：<strong>复用 DDNS 域名换节点前，先 <code>crontab -l | grep duck</code> 清掉旧机器的定时任务</strong>。</li>
</ol>
<p>8 月终于翻盘：换到一条干净线路的新机器，ping 41ms / 0% 丢包，Hysteria2 直接起飞，下载实测 140Mbps，比美国主力快 3 倍。MTU 都不用压——好线路和烂线路的差距就是这么赤裸裸。</p>
<h3 id="阶段五自建订阅服务器67-月">阶段五：自建订阅服务器（6–7 月）</h3>
<p>不想手动给每台设备发配置，就在美国 VPS 上用 python3 起了个 HTTPS 小服务，按随机 token 路径提供两份订阅：Clash YAML（给 mihomo 全家）+ base64 分享链接（Shadowrocket 对 YAML 订阅支持差，必须单独喂）。</p>
<p>两个值得记的坑：</p>
<ol>
<li><strong>服务卡死 bug</strong>：最初把 TLS 握手直接包在 accept 主循环里同步做，任何一个境内客户端握手卡住就堵死所有人。改成每个连接在自己的线程里做握手 + 30 秒超时，世界清净了。</li>
<li><strong>非标准端口会被掐</strong>：订阅端口不是 443，从家宽/店宽直连时 TCP 都建不起来。所以<strong>别指望各路由自动更新订阅，最可靠的方式是 scp 直接推配置文件</strong>再重启服务。根治方案（挪到标准 443）还在待办上。</li>
</ol>
<p>彩蛋功能：给订阅响应注入 <code>Subscription-Userinfo</code> 头，Clash Verge 卡片上就有流量用量进度条，数据来自商家只读 API，缓存 5 分钟。</p>
<h3 id="阶段六远程管理与自愈7-月">阶段六：远程管理与自愈（7 月）</h3>
<p>路由器散落在不同的内网，现场维护不现实。用 FRP 打通：一台<strong>境内</strong>云主机当中转，每台路由跑 frpc 反向连接，SSH 随时可达。铁律：<strong>境内实名中转机只做管理隧道，绝不跑代理流量</strong>——合规红线。</p>
<p>再加一层保险：每台路由装 watchdog，每 5 分钟经节点探测 <code>generate_204</code>，连续两次不通就自动重启代理服务刷新 DNS 解析，15 分钟冷却防抖。VPS 再换 IP，路由器也能自己爬起来。</p>
<h2 id="沉淀下来的设计原则">沉淀下来的设计原则</h2>
<ol>
<li><strong>一切寻址用域名，IP 只是临时状态</strong></li>
<li><strong>VPS 无状态化</strong>：一份重建脚本，新机器 root 下跑一遍 = 2 分钟还原全套服务（含证书续期、开机自启）</li>
<li><strong>单一真源</strong>：订阅服务器是唯一配置源头，本地只留副本</li>
<li><strong>fallback 保稳定，不为速度牺牲出口一致性</strong></li>
<li><strong>TCP 节点屏蔽 QUIC，UDP 节点放开 QUIC，先看协议再动手</strong></li>
<li><strong>换机先清旧机器的 cron</strong>（DDNS 血泪）</li>
<li><strong>远程运维走独立通道，与业务流量物理隔离</strong></li>
</ol>
<h2 id="安全清单公开笔记前自查">安全清单（公开笔记前自查）</h2>
<p>这次整理博客顺便做了遍审计：</p>
<ul>
<li><input disabled="" type="checkbox"> 所有密码/密钥/token 是否已从文档移除？（包括&quot;看起来无害&quot;的面板地址）</li>
<li><input disabled="" type="checkbox"> 曾在 AI 对话中出现过的凭据，<strong>当作已泄露处理，全部轮换</strong></li>
<li><input disabled="" type="checkbox"> 管理面板端口用防火墙 DROP 公网访问，仅留 SSH 隧道入口</li>
<li><input disabled="" type="checkbox"> SSH 改密钥登录、禁密码（待办清单上的常客，别学我拖了两个月）</li>
<li><input disabled="" type="checkbox"> 家宽/店铺拓扑、内部网段不在公开内容里出现</li>
</ul>
<h2 id="后记">后记</h2>
<p>整个过程我是和 AI 结对完成的：让它读交接文档、远程上机器排查、把每次排障结论固化成可复制的 SOP。回头看，最值钱的不是哪条命令，而是&quot;域名寻址、无状态重建、单一真源&quot;这几个原则——它们让后面每一次故障都从&quot;事故&quot;降级成了&quot;练习&quot;。</p>
<hr>
<p><em>本文由 <strong>ox-alpha</strong>（模型 ID：<code>openrouter/stealth/ox-alpha</code>，undisclosed organization 出品）基于作者的项目交接文档协助整理撰写；文中所有敏感信息均已脱敏。</em></p>
]]></content:encoded></item><item><title>用代码做商业餐饮海报：字体子集、GrabCut 抠图与无损导出避坑指南</title><link>https://idiotfan.wang/posts/maybelab-poster-web-export-pipeline/</link><pubDate>Sat, 22 Aug 2026 17:00:00 +0800</pubDate><guid>https://idiotfan.wang/posts/maybelab-poster-web-export-pipeline/</guid><description>丢掉 Photoshop，用纯 HTML/CSS 做商业级餐饮海报：从 html2canvas 到 Chrome Headless、字体子集化豆腐块排查、OpenCV GrabCut 菜盘智能抠图的全套踩坑实录。</description><content:encoded><![CDATA[<p>在给可能实验室（大梦餐饮）制作午市特惠、台风天畅饮、球赛之夜等一系列商业促销海报时，我做了一个决定：<strong>不打开 Photoshop，全部用纯 HTML + CSS + Python 脚本来做</strong>。</p>
<p>用 Web 技术做海报有极大的优势：布局可以用 Flexbox/Grid 极速调整、文案改动只需改一行字符串、多尺寸适配通过 CSS 变量瞬间完成，而且全套物料可以纳入 Git 版本管理。</p>
<p>但在真正借助 <strong>Claude Fable 5</strong> 与 <strong>Grok Imagine 图像模型</strong> 将网页无损导出为 300DPI 商业级印刷海报的过程中，我们踩遍了前端渲染、字体子集化、图像抠图与色彩空间的几乎所有暗坑。</p>
<p>这篇文章把这些工程细节与解决方案彻底拆解，给同样想用代码做设计的朋友一份避坑指南。</p>
<hr>
<h2 id="一-导出引擎的演进从-html2canvas-到-chrome-headless">一、 导出引擎的演进：从 html2canvas 到 Chrome Headless</h2>
<p>网页在浏览器里看着很美，但要导出一张像素完美（Pixel Perfect）的 1600×2400 高清大图，首先要选对渲染引擎。</p>
<h3 id="1-html2canvas-的翻车表现">1. html2canvas 的「翻车」表现</h3>
<p>最初我们尝试了老牌的 <code>html2canvas</code>，结果导出的图片与原页面差异巨大：</p>
<ul>
<li><code>filter: drop-shadow(...)</code> 阴影几乎全丢。</li>
<li>径向渐变（Radial Gradient）在 Canvas 绘制时变成了生硬的同心圆硬聚光。</li>
<li>精细的金边暗纹和半透明毛玻璃背景变得惨白模糊。</li>
</ul>
<h3 id="2-html-to-imagesvg-foreignobject-路线">2. html-to-image（SVG foreignObject 路线）</h3>
<p>随后我们切换到了 <code>html-to-image</code> 库。它利用浏览器的 <code>&lt;foreignObject&gt;</code> 将 DOM 结构直接转化为 SVG，再绘制成 PNG，真正做到了「浏览器里看什么，导出来就是什么」。</p>
<h3 id="3-最强终局方案chrome-headless-命令行直出">3. 最强终局方案：Chrome Headless 命令行直出</h3>
<p>在批量生成物料时，最稳健的做法甚至不需要在页面里挂载任何导出 JS，直接用本地 Google Chrome 的无头模式截图：</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span><span class="lnt">5
</span><span class="lnt">6
</span><span class="lnt">7
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl"><span class="s2">&#34;/Applications/Google Chrome.app/Contents/MacOS/Google Chrome&#34;</span> <span class="se">\
</span></span></span><span class="line"><span class="cl">  --headless<span class="o">=</span>new <span class="se">\
</span></span></span><span class="line"><span class="cl">  --screenshot<span class="o">=</span><span class="s2">&#34;海报_1600x2400.png&#34;</span> <span class="se">\
</span></span></span><span class="line"><span class="cl">  --window-size<span class="o">=</span>800,1200 <span class="se">\
</span></span></span><span class="line"><span class="cl">  --force-device-scale-factor<span class="o">=</span><span class="m">2</span> <span class="se">\
</span></span></span><span class="line"><span class="cl">  --hide-scrollbars <span class="se">\
</span></span></span><span class="line"><span class="cl">  <span class="s2">&#34;file://</span><span class="nv">$PWD</span><span class="s2">/午餐套餐海报.html&#34;</span>
</span></span></code></pre></td></tr></table>
</div>
</div><p>通过 <code>--force-device-scale-factor=2</code>，浏览器会以 Retina 双倍像素密度渲染，零网络依赖，瞬间得到 1600×2400 的高保真图片。</p>
<hr>
<h2 id="二-字体子集化与豆腐块幽灵-bug">二、 字体子集化与「豆腐块□」幽灵 Bug</h2>
<p>为了保证海报在任何离线环境下打开都拥有精美的衬线字体（如 Noto Serif SC 与 Cormorant Garamond），我们将字体通过 Google Fonts <code>text=</code> API 抓取字形子集，转为 Base64 内嵌进 HTML。</p>
<p>但这里埋下了两个极其隐蔽的排版陷阱：</p>
<h3 id="1-改动文案后的豆腐块危机">1. 改动文案后的「豆腐块」危机</h3>
<p>当我们将菜名从原先的通用名改为「泰式打抛猪肉饭」、「夏威夷菠萝牛肉饭」后，网页在浏览器里渲染正常（触发了本地系统字体兜底），但<strong>导出的 PNG 却在「泰、式、夏、菠」等新字上全部显示为方块豆腐□</strong>！</p>
<p><strong>根因</strong>：内嵌的 Base64 字体子集只包含老版文案的文字。
<strong>解法</strong>：编写自动化脚本，每次修改文案后，自动提取 HTML 中全部可见汉字、英文字母与标点符号，利用 <code>fonttools</code> 和 <code>brotli</code> 重新生成最小化的 woff2 子集并热替换到 CSS 中。</p>
<h3 id="2-macos-宋体-900-缺失符号的渲染-bug">2. macOS 宋体 900 缺失「¥」符号的渲染 Bug</h3>
<p>在制作价格标签时，标题采用 <code>Songti SC</code>（宋体）搭配 <code>font-weight: 900</code>。我们发现页面上的价格符号 <code>¥</code> <strong>莫名其妙地消失了，变成了一片空白</strong>。</p>
<p><strong>排查发现</strong>：macOS 自带的宋体在字重为 900（Heavy）时，其字符映射表中标准半角人民币符号 <code>U+00A5 (¥)</code> 存在字形空白缺失的缺陷。
<strong>解决方案</strong>：文案中强制使用全角人民币符号 <code>U+FFE5 (￥)</code>，或在 CSS 中设置字体回退链优先使用 <code>PingFang SC</code> 渲染金额符号。</p>
<hr>
<h2 id="三-opencv-grabcut-菜盘抠图与等面积对齐">三、 OpenCV GrabCut 菜盘抠图与等面积对齐</h2>
<p>在午市套餐海报中，需要展示三款主食（打抛饭、番茄肉酱饭、菠萝牛肉饭）的高清盘装实拍图。</p>
<p>直接给实拍照片抠图并排版，会遇到一系列算法与视觉问题：</p>
<pre tabindex="0"><code>[实拍带背景菜品] ──► [OpenCV GrabCut 保留盘沿] ──► [几何均值面积等比缩放] ──► [统一暖白平衡调色] ──► [内嵌 WebP]
</code></pre><h3 id="1-为什么-ai-抠图模型rembg--birefnet会失效">1. 为什么 AI 抠图模型（rembg / BiRefNet）会失效？</h3>
<ul>
<li><code>rembg</code>（基于 u2net/isnet）：会将浅色/米白色的陶瓷盘子误判为背景，抠完只剩下一堆悬空的肉末和米饭，完全失去了餐饮出品的质感。</li>
<li><code>BiRefNet</code>：边缘识别精细，但在 CPU 上处理单张图耗时超过 12 分钟，无法进行快速迭代。</li>
</ul>
<h3 id="2-解决方案opencv-grabcut-智能保留整盘">2. 解决方案：OpenCV GrabCut 智能保留整盘</h3>
<p>我们改用 OpenCV 的 <strong>GrabCut 算法（矩形初始化，边框 Inset 3%）</strong>：</p>
<ul>
<li>对于盘子与阴影同色的疑难图片（如灰盘配灰色桌面），先对盘芯做腐蚀运算（Erosion），拟合出精确的椭圆轮廓（<code>cv2.fitEllipse</code>），再等比放大回盘沿，完美裁掉外部多余投影，完整保留陶瓷盘子的边缘光泽。</li>
</ul>
<h3 id="3-三张主食图的等面积对齐">3. 三张主食图的「等面积对齐」</h3>
<p>三张菜品如果简单地以正方形长轴缩放，扁平的椭圆盘子在视觉上会显得极其单薄瘦小。</p>
<p>我们计算了三张盘子的<strong>几何均值面积（$\text{Geomean} = \sqrt{W \times H}$）</strong>，按等面积缩放后贴入统一的 860×647 画布底部居中。这样在 CSS 中只需固定 <code>max-width: 206px</code>，三款主食在视觉体量上就达到了完美的平衡。</p>
<h3 id="4-颜色与方向标准化">4. 颜色与方向标准化</h3>
<ul>
<li><strong>白平衡校准</strong>：提取每张照片盘子的最高白点，通过色彩矩阵统一映射到暖白基准值 <code>(236, 232, 223)</code>，并统一增加 7% 对比度与自然饱和度。</li>
<li><strong>PIL EXIF 方向坑</strong>：手机拍摄的原图常带有 <code>Orientation: 6</code>（顺时针 90° 旋转），PIL 的 <code>Image.open</code> 默认不会自动应用 EXIF 旋转矩阵。处理前必须显式调用 <code>ImageOps.exif_transpose(im)</code>，否则抠出来的菜品全部是倒挂的。</li>
</ul>
<hr>
<h2 id="结语">结语</h2>
<p>从一行 HTML 标签，到一张可以直接送到印刷厂或挂在商场 LED 巨幕上的高清海报，中间横跨了排版引擎、字体编码、色彩校准与图像算法的诸多细节。</p>
<p>代码赋予设计的，不仅是像素级别的精准控制力，更是一种<strong>可复现、可自动化、可规模化交付的工程确定性</strong>。</p>
<hr>
<h3 id="-项目环境与模型署名">🛠️ 项目环境与模型署名</h3>
<ul>
<li><strong>主导架构与排版代码生成</strong>：Anthropic <strong>Claude Fable 5</strong>（桌面端默认模型）</li>
<li><strong>商业海报主题背景生成</strong>：xAI <strong>Grok Imagine</strong>（grok.com/imagine 网页版 + <code>grok-imagine-image-2.0</code> API）</li>
<li><strong>客户端环境</strong>：<strong>Claude Desktop 桌面端</strong></li>
<li><strong>图像与排版工具链</strong>：OpenCV 4 (<code>GrabCut</code>, <code>fitEllipse</code>) + Python Pillow (<code>ImageOps.exif_transpose</code>) + Google Fonts API (<code>fonttools</code>, <code>brotli</code>) + <code>html-to-image</code> + Chrome Headless 截图管线</li>
</ul>
]]></content:encoded></item></channel></rss>