<?xml version="1.0" encoding="UTF-8"?><rss version="2.0" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/"><channel><title>longBlog</title><description>记录所看所思所得</description><link>https://huowenlong.com/</link><language>zh-CN</language><dc:creator>Minlonghuo</dc:creator><atom:link href="https://huowenlong.com/rss.xml" rel="self" type="application/rss+xml"/><lastBuildDate>Tue, 20 Oct 2026 03:06:26 GMT</lastBuildDate><item><title>说说页面加速与自动更新原理详解</title><link>https://huowenlong.com/blog/xtusIYhPQyK5/</link><guid isPermaLink="true">https://huowenlong.com/blog/xtusIYhPQyK5/</guid><description>为解决说说页面加载慢，作者用 CloudBase 云函数把公开说说同步成展示快照和缩略图，经 EdgeOne 边缘缓存分发，浏览器只凭版本文件判断是否更新，省掉串行请求并压缩图片体积。</description><pubDate>Tue, 20 Oct 2026 03:06:26 GMT</pubDate><content:encoded>&lt;blockquote&gt;&lt;p&gt;我之前购买了 CloudBase 个人版的套餐（月付 19.9 元），本意是要部署 Memos 服务，不过我发现，部署 Docker 真的太耗费资源了，个人版套餐完全不够用，遂弃用，不过它能备案域名加速我的博客倒是挺不错，现在我使用它的云函数功能。&lt;/p&gt;&lt;p&gt;由于受不了每次如果不开代理，访问说说页面加载速度慢甚至无法加载，我做了以下优化…&lt;/p&gt;&lt;/blockquote&gt;&lt;h3&gt;一、整体概念&lt;/h3&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;Cloudflare 保存原始说说，继续承接 MoeMemos 的读写和同步。&lt;/li&gt;&lt;li&gt;CloudBase 云函数将公开内容同步成展示快照，并生成图片缩略图。&lt;/li&gt;&lt;li&gt;博客现有的 EdgeOne 为访客提供快照和缩略图缓存。&lt;/li&gt;&lt;li&gt;浏览器定期检查版本，有变化才更新列表，并执行渐入、渐出动画。&lt;/li&gt;&lt;/ol&gt;&lt;p&gt;因此，加速来自请求减少、数据提前准备、图片缩小和边缘缓存共同发挥作用。&lt;/p&gt;&lt;h3&gt;二、原来的页面加载速度&lt;/h3&gt;&lt;h4&gt;2.1 首次进入存在两次串行请求&lt;/h4&gt;&lt;p&gt;原来的加载逻辑先请求少量数据，识别接口版本，再发送正式列表请求。&lt;/p&gt;&lt;pre&gt;&lt;code&gt;打开页面
   ↓
请求 API，判断使用哪种接口
   ↓ 等待上一步完成
请求首屏说说列表
   ↓
渲染内容&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;这两次请求都访问 Cloudflare，耗时会相加。本次同一网络的测量中，单次说说 API 请求约为 1.6–1.9 秒，两次串行请求合计约为 3.2–3.6 秒。&lt;/p&gt;&lt;p&gt;优化后，正常路径直接读取快照；回退原 API 时，接口识别也会复用第一次正式列表请求的结果，省掉单独的探测请求。&lt;/p&gt;&lt;h4&gt;2.2 小图片位置下载了大原图&lt;/h4&gt;&lt;p&gt;页面上展示的是很小的图片方块，原先请求的却是完整图片。浏览器虽然把图片缩小显示，网络仍需要下载原图的全部字节。&lt;/p&gt;&lt;p&gt;本次首页的一张图片，原始大小为 &lt;strong&gt;3,376,464 字节&lt;/strong&gt;。对列表中的小图来说，这部分下载量明显超过展示需要。&lt;/p&gt;&lt;h4&gt;2.3 自动更新需要反复读取列表&lt;/h4&gt;&lt;p&gt;此前为了检测新增和删除，前台页面每 30 秒重新读取已加载范围的数据。随着用户继续翻页，需要重新核对的内容也会增加。&lt;/p&gt;&lt;p&gt;如果内容长时间没有变化，这些列表请求仍然发生。新方案将日常检查改成一个很小的版本文件。&lt;/p&gt;&lt;h3&gt;三、各个平台的职责&lt;/h3&gt;&lt;p&gt;&lt;strong&gt;博客页面已部署在腾讯 EdgeOne Makers；说说 API 和原始附件位于 Cloudflare。&lt;/strong&gt;&lt;/p&gt;&lt;figure&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;MoeMemos&lt;/td&gt;&lt;td&gt;发布、编辑、删除、同步说说&lt;/td&gt;&lt;td&gt;手机中的客户端数据&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Cloudflare Worker&lt;/td&gt;&lt;td&gt;处理原始说说 API 请求&lt;/td&gt;&lt;td&gt;读写逻辑、权限校验&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Cloudflare D1&lt;/td&gt;&lt;td&gt;保存原始结构化数据&lt;/td&gt;&lt;td&gt;说说、标签、附件关联等&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Cloudflare R2&lt;/td&gt;&lt;td&gt;保存原始附件&lt;/td&gt;&lt;td&gt;原图及其他上传文件&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;CloudBase 云函数&lt;/td&gt;&lt;td&gt;同步、校验、生成缩略图&lt;/td&gt;&lt;td&gt;临时处理公开数据&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;CloudBase COS 静态存储&lt;/td&gt;&lt;td&gt;保存展示副本&lt;/td&gt;&lt;td&gt;完整公开快照、版本文件、缩略图&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;CloudBase PostgreSQL&lt;/td&gt;&lt;td&gt;协调并发同步&lt;/td&gt;&lt;td&gt;一条同步租约记录&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;EdgeOne Makers&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;/figure&gt;&lt;p&gt;Cloudflare 仍然是原始数据的权威来源。CloudBase 中的内容是可重新生成的展示副本。&lt;/p&gt;&lt;h3&gt;四、整体数据流&lt;/h3&gt;&lt;pre&gt;&lt;code&gt;flowchart TD
    A[在 MoeMemos 发布、修改或删除] --&amp;gt; B[Cloudflare API 完成原始写入]
    B --&amp;gt;|后台发送同步信号| C[CloudBase 同步云函数]
    T[每 5 分钟定时校验] --&amp;gt; C
    C --&amp;gt;|匿名读取公开且未归档内容| B
    C --&amp;gt; D[校验完整列表与计算内容摘要]
    D --&amp;gt; E[复用或生成 160px 缩略图]
    E --&amp;gt; F[CloudBase COS：快照与版本文件]
    F --&amp;gt; G[EdgeOne 命名缓存]
    G --&amp;gt; H[博客说说页面]
    H --&amp;gt;|每 30 秒检查版本| G&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;访客正常浏览时，不需要让 CloudBase 云函数临时去 Cloudflare 查询一次数据库。云函数提前完成了准备工作。&lt;/p&gt;&lt;h3&gt;五、CloudBase 如何知道内容发生了变化&lt;/h3&gt;&lt;h4&gt;5.1 成功写入后发送同步通知&lt;/h4&gt;&lt;p&gt;Cloudflare 在相关 API 的写入操作成功后，后台通知 CloudBase。覆盖的请求包括说说新增、修改、删除、置顶，以及附件、标签、公开设置、Telegram 相关写入等。&lt;/p&gt;&lt;p&gt;通知的请求正文只有：&lt;/p&gt;&lt;pre&gt;&lt;code&gt;{}&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;这表示「请重新检查公开内容」。通知不会携带帖子正文、私密说说或用户登录凭证。&lt;/p&gt;&lt;p&gt;CloudBase 同步端点使用独立的 Bearer 凭证验证通知来源。该凭证专用于同步，与 MoeMemos 登录凭证分开保存。&lt;/p&gt;&lt;p&gt;通知在 Worker 的后台任务中执行，不阻塞原始写入。遇到失败会进行有限重试；镜像故障不会让用户已经成功保存的说说变成保存失败。&lt;/p&gt;&lt;h4&gt;5.2 每 5 分钟进行一次补漏校验&lt;/h4&gt;&lt;p&gt;网络抖动、通知超时等情况可能使某次通知没有及时完成。因此，CloudBase 还配置了每 5 分钟运行一次的定时任务。&lt;/p&gt;&lt;p&gt;定时任务重新读取公开内容，核对快照并更新同步心跳。如果通知遗漏，在源站及相关服务可用的情况下，下一次成功的定时校验会补齐变化。&lt;/p&gt;&lt;h3&gt;六、快照和版本文件&lt;/h3&gt;&lt;h4&gt;6.1 完整快照：保存展示需要的数据&lt;/h4&gt;&lt;p&gt;&lt;code&gt;snapshot.json&lt;/code&gt; 保存完整公开列表。当前规模下，浏览器可以一次下载，然后在本地分页。&lt;/p&gt;&lt;p&gt;下面仅为结构示意，省略了部分字段；摘要和帖子内容均为占位示例：&lt;/p&gt;&lt;pre&gt;&lt;code&gt;{
  &quot;schemaVersion&quot;: 1,
  &quot;complete&quot;: true,
  &quot;revision&quot;: &quot;&amp;lt;64 位 SHA-256 内容摘要&amp;gt;&quot;,
  &quot;memos&quot;: [
    {
      &quot;id&quot;: 1001,
      &quot;content&quot;: &quot;示例说说&quot;,
      &quot;visibility&quot;: &quot;PUBLIC&quot;,
      &quot;rowStatus&quot;: &quot;NORMAL&quot;,
      &quot;resourceList&quot;: []
    }
  ]
}&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;只有匿名可读、&lt;code&gt;PUBLIC&lt;/code&gt; 且 &lt;code&gt;NORMAL&lt;/code&gt; 的说说会进入快照。浏览器在使用快照前还会检查结构、ID、可见性和版本格式。&lt;/p&gt;&lt;h4&gt;6.2 版本文件：用少量数据判断是否需要更新&lt;/h4&gt;&lt;p&gt;&lt;code&gt;version.json&lt;/code&gt; 只保存版本摘要、条数和同步心跳等少量信息。本次验收时大小约为 &lt;strong&gt;134 字节&lt;/strong&gt;；随着字段值长度变化，大小也可能略有变化。&lt;/p&gt;&lt;pre&gt;&lt;code&gt;{
  &quot;schemaVersion&quot;: 1,
  &quot;revision&quot;: &quot;&amp;lt;64 位 SHA-256 内容摘要&amp;gt;&quot;,
  &quot;count&quot;: 52,
  &quot;checkedAt&quot;: 1792341900000
}&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;上面的时间戳也是结构示例。&lt;code&gt;checkedAt&lt;/code&gt; 使用毫秒时间戳，表示服务端最近一次成功检查的时间。&lt;/p&gt;&lt;p&gt;浏览器判断逻辑如下：&lt;/p&gt;&lt;pre&gt;&lt;code&gt;获取版本文件
   ↓
revision 与本地相同？
   ├─ 相同 → 保留当前列表，不下载完整快照
   └─ 不同 → 获取完整快照，校验后更新列表&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;code&gt;revision&lt;/code&gt; 根据展示数据计算 SHA-256 摘要。它与内容对应，不能只根据「现在是几点」生成，否则每次检查都会被误认为内容发生变化。&lt;/p&gt;&lt;p&gt;&lt;code&gt;checkedAt&lt;/code&gt; 则用于检查同步服务是否仍然正常工作。内容没有变化时，同步心跳仍可以更新，而浏览器不必因此重新下载列表。&lt;/p&gt;&lt;h4&gt;6.3 先发布数据，再发布版本标记&lt;/h4&gt;&lt;p&gt;同步函数先写入新的完整快照，再写入对应的版本文件。浏览器根据版本文件请求新快照时，会核对两者的 &lt;code&gt;revision&lt;/code&gt; 是否一致。&lt;/p&gt;&lt;p&gt;快照和版本文件是两个独立文件，这种顺序发布不等于二者拥有数据库级原子事务。不同节点短暂缓存不同版本时，浏览器可能读到不匹配的组合；此时会保留当前内容，后续检查再重试。&lt;/p&gt;&lt;p&gt;这样可以避免把发布中的临时状态直接应用到页面。&lt;/p&gt;&lt;h3&gt;七、EdgeOne 缓存&lt;/h3&gt;&lt;h4&gt;7.1 缓存命中和未命中&lt;/h4&gt;&lt;p&gt;缓存相当于在离访客较近的节点保存一份短期副本：&lt;/p&gt;&lt;pre&gt;&lt;code&gt;访客请求快照
   ↓
EdgeOne 查找有效缓存
   ├─ 找到 → 直接返回，省去 CloudBase 回源请求
   └─ 未找到 → 读取 CloudBase 文件 → 校验 → 保存缓存 → 返回&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;首次访问某个节点、缓存到期、缓存被淘汰等情况，仍然需要回源读取。但回源对象已经是国内存储中准备好的文件。&lt;/p&gt;&lt;h4&gt;7.2 本次采用的三层缓存&lt;/h4&gt;&lt;figure&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;EdgeOne 命名缓存&lt;/td&gt;&lt;td&gt;在边缘节点复用公开响应，减少回源&lt;/td&gt;&lt;td&gt;命中受节点、有效期和平台运行情况影响&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;EdgeOne 实例缓存&lt;/td&gt;&lt;td&gt;命名缓存不可用时，在仍存活的实例中复用小文件&lt;/td&gt;&lt;td&gt;最多 64 项、总计 1 MiB；实例回收后消失&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;/figure&gt;&lt;p&gt;实例内存缓存只是一种加速手段。快照的持久副本保存在 CloudBase COS，实例回收不会丢失原始数据或展示快照。&lt;/p&gt;&lt;h4&gt;7.3 缓存时间设置&lt;/h4&gt;&lt;figure&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;边缘缓存 15 秒&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;约 30 秒作为新鲜度判断窗口&lt;/td&gt;&lt;td&gt;尽快恢复页面，并继续定期校验&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/figure&gt;&lt;p&gt;对 JSON，返回浏览器的响应使用 &lt;code&gt;max-age=0, s-maxage=15&lt;/code&gt;：浏览器需要重验证，共享缓存可以在 15 秒内复用副本。前端对版本检查也明确使用重验证策略。&lt;/p&gt;&lt;p&gt;公开接口还会归一化查询参数，防止不同的无关参数制造大量重复缓存项。请求路径只允许快照、版本文件和规定格式的缩略图，不能通过该接口任意代理其他 URL。&lt;/p&gt;&lt;h3&gt;八、图片加载结果&lt;/h3&gt;&lt;h4&gt;8.1 展示尺寸与下载尺寸分开处理&lt;/h4&gt;&lt;p&gt;列表中的小图片使用预先生成的 &lt;strong&gt;160×160 WebP 缩略图&lt;/strong&gt;。用户点击查看大图时，才请求原始图片地址。&lt;/p&gt;&lt;pre&gt;&lt;code&gt;浏览列表 → 下载小缩略图
点击图片 → 按需下载原图&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;本次验收的一个例子：&lt;/p&gt;&lt;figure&gt;&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;图片形式&lt;/th&gt;&lt;th&gt;&lt;p&gt;大小&lt;/p&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;p&gt;3,376,464 字节，约 3.4 MB&lt;/p&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;列表缩略图&lt;/td&gt;&lt;td&gt;&lt;p&gt;3,632 字节，约 3.6 KB&lt;/p&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/figure&gt;&lt;p&gt;该例的缩略图大小约为原图的 0.108%，减少约 99.89%。这只说明该图片的结果，不代表每一张图片都有相同压缩比例。&lt;/p&gt;&lt;h4&gt;8.2 已有缩略图尽量复用&lt;/h4&gt;&lt;p&gt;缩略图文件名根据资源 ID、更新时间、大小和原图地址等属性计算。同步时会复用仍然有效的已有缩略图，避免每次校验都重新下载和压缩相同图片。&lt;/p&gt;&lt;p&gt;偶尔出现 &lt;code&gt;MISS&lt;/code&gt; 正常，尤其是缓存刚到期或首次访问某个节点时。&lt;/p&gt;&lt;h3&gt;九、文档参考&lt;/h3&gt;&lt;ul&gt;&lt;li&gt;&lt;a href=&quot;https://docs.cloudbase.net/service/custom-domain&quot;&gt;CloudBase 自定义域名与边缘加速&lt;/a&gt;&lt;/li&gt;&lt;li&gt;&lt;a href=&quot;https://docs.cloudbase.net/service/cache&quot;&gt;CloudBase 缓存配置&lt;/a&gt;&lt;/li&gt;&lt;li&gt;&lt;a href=&quot;https://docs.cloudbase.net/api-reference/manager/node/hosting&quot;&gt;CloudBase 静态网站托管&lt;/a&gt;&lt;/li&gt;&lt;li&gt;&lt;a href=&quot;https://pages.edgeone.ai/document/edge-functions&quot;&gt;EdgeOne Makers 边缘函数&lt;/a&gt;&lt;/li&gt;&lt;li&gt;&lt;a href=&quot;https://edgeone.ai/document/52684&quot;&gt;EdgeOne Cache API&lt;/a&gt;&lt;/li&gt;&lt;/ul&gt;</content:encoded></item><item><title>记一次华为杯数学建模协作经验</title><link>https://huowenlong.com/blog/cyZ6AgNjauV8/</link><guid isPermaLink="true">https://huowenlong.com/blog/cyZ6AgNjauV8/</guid><description>华为杯四天四小时、三人协作的实录：用 Windows 加 Docker 自建 Overleaf 供校园网共享，并让 DeepSeek Harness 与 Codex 两个 AI 代理分担 LaTeX 同步与写作，最终按时交稿。</description><pubDate>Tue, 06 Oct 2026 20:01:39 GMT</pubDate><content:encoded>&lt;h2&gt;前言&lt;/h2&gt;&lt;p&gt;研赛的节奏是这样的：9 月 23 日早上八点开题，9 月 27 日中午十二点前必须提交论文的 MD5 码——&lt;strong&gt;四天四小时，三个人，一篇几十页的论文&lt;/strong&gt;。&lt;/p&gt;&lt;p&gt;先交 MD5、再传正文，意味着十二点那一刻你的文件必须已经是最终版，改一个字节都对不上。关于论文写作，如果用word的话，调整格式太麻烦了且不方便AI操作；如果使用latex的话，三个人各自在本地写 &lt;code&gt;.tex&lt;/code&gt;，合并的时候宏包冲突、&lt;code&gt;\label&lt;/code&gt; 重名、图片路径不对、公式编号全乱。&lt;/p&gt;&lt;p&gt;所以华为杯之前，我提前做了两件事：&lt;/p&gt;&lt;ol&gt;&lt;li&gt;用 Windows 电脑 + Docker &lt;strong&gt;搭一个属于我们三个人的 Overleaf&lt;/strong&gt;，通过校园网这个大局域网共享出去；&lt;/li&gt;&lt;li&gt;让 &lt;strong&gt;DeepSeek Harness 和 Codex 两个 AI 代理接管 LaTeX 的同步与写作&lt;/strong&gt;（这一部分只针对笔者工作而言）。&lt;/li&gt;&lt;/ol&gt;&lt;p&gt;这篇文章就是这两件事的完整记录。&lt;/p&gt;&lt;figure&gt;&lt;img src=&quot;https://huowenlong.com/trilium-assets/cyZ6AgNjauV8/2fd1482b8edf0594.png&quot; width=&quot;2000&quot; height=&quot;1349&quot; /&gt;&lt;/figure&gt;&lt;h2&gt;一、为什么自己搭一个 Overleaf&lt;/h2&gt;&lt;p&gt;在线版 Overleaf 当然能用，但放在比赛场景里，有三个绕不过去的点：&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;code&gt;algorithm2e&lt;/code&gt;、&lt;code&gt;booktabs&lt;/code&gt;、国标参考文献之类的宏包不一定能随手装&lt;/li&gt;&lt;li&gt;&lt;strong&gt;数据在别人服务器上&lt;/strong&gt;，比赛论文虽然不算机密，但心里总归不踏实&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;自己搭社区版（CE）就全解决了：无限项目、随便装宏包、三个人实时协作，跟 Google Docs 一样的体验。&lt;/p&gt;&lt;blockquote&gt;&lt;p&gt;我之前在 NAS 上完整部署过一套 Overleaf（含域名、HTTPS、外网访问），步骤和踩坑都写在《Overleaf 部署实战指南》里了。&lt;strong&gt;这次是另一个场景&lt;/strong&gt;：比赛现场只有一台 Windows 电脑，不需要公网，只需要&quot;在场的三个人能同时编辑&quot;。所以部署方式完全不同——不搞域名和证书，直接 80 端口映射到校园网。&lt;/p&gt;&lt;/blockquote&gt;&lt;h2&gt;二、在 Windows 上用 Docker 部署&lt;/h2&gt;&lt;h3&gt;2.1 三个容器&lt;/h3&gt;&lt;p&gt;Overleaf CE 本体是三个服务：Web 应用（sharelatex）+ 数据库（mongo）+ 缓存（redis）。用 Docker 把它们拉起来，宿主机上只需要暴露一个 80 端口。&lt;/p&gt;&lt;pre&gt;&lt;code&gt;&lt;span&gt;# docker-compose.yml&lt;/span&gt;
&lt;span&gt;services:&lt;/span&gt;
  &lt;span&gt;sharelatex:&lt;/span&gt;
    &lt;span&gt;image:&lt;/span&gt; &lt;span&gt;sharelatex/sharelatex:latest&lt;/span&gt;
    &lt;span&gt;container_name:&lt;/span&gt; &lt;span&gt;sharelatex&lt;/span&gt;
    &lt;span&gt;depends_on:&lt;/span&gt;
      &lt;span&gt;-&lt;/span&gt; &lt;span&gt;mongo&lt;/span&gt;
      &lt;span&gt;-&lt;/span&gt; &lt;span&gt;redis&lt;/span&gt;
    &lt;span&gt;ports:&lt;/span&gt;
      &lt;span&gt;-&lt;/span&gt; &lt;span&gt;&quot;80:80&quot;&lt;/span&gt;                             &lt;span&gt;# ← 映射到宿主机 80&lt;/span&gt;
    &lt;span&gt;volumes:&lt;/span&gt;
      &lt;span&gt;-&lt;/span&gt; &lt;span&gt;./data/overleaf:/var/lib/overleaf&lt;/span&gt;
      &lt;span&gt;-&lt;/span&gt; &lt;span&gt;./data/texlive:/usr/local/texlive&lt;/span&gt;   &lt;span&gt;# ← 宏包持久化，见踩坑表&lt;/span&gt;
    &lt;span&gt;environment:&lt;/span&gt;
      &lt;span&gt;OVERLEAF_SITE_URL:&lt;/span&gt; &lt;span&gt;http://10.198.119.0&lt;/span&gt;   &lt;span&gt;# ← 换成你的校园网 IP&lt;/span&gt;
      &lt;span&gt;OVERLEAF_NAV_TITLE:&lt;/span&gt; &lt;span&gt;Overleaf&lt;/span&gt;
      &lt;span&gt;OVERLEAF_ADMIN_EMAIL:&lt;/span&gt; &lt;span&gt;you@example.com&lt;/span&gt;
      &lt;span&gt;OVERLEAF_MONGO_URL:&lt;/span&gt; &lt;span&gt;mongodb://mongo/sharelatex&lt;/span&gt;
      &lt;span&gt;OVERLEAF_REDIS_HOST:&lt;/span&gt; &lt;span&gt;redis&lt;/span&gt;

  &lt;span&gt;mongo:&lt;/span&gt;
    &lt;span&gt;image:&lt;/span&gt; &lt;span&gt;mongo:8&lt;/span&gt;
    &lt;span&gt;volumes:&lt;/span&gt;
      &lt;span&gt;-&lt;/span&gt; &lt;span&gt;./data/mongo:/data/db&lt;/span&gt;

  &lt;span&gt;redis:&lt;/span&gt;
    &lt;span&gt;image:&lt;/span&gt; &lt;span&gt;redis:7&lt;/span&gt;
    &lt;span&gt;volumes:&lt;/span&gt;
      &lt;span&gt;-&lt;/span&gt; &lt;span&gt;./data/redis:/data&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;pre&gt;&lt;code&gt;docker compose up -d
docker compose ps        # 三个容器都是 Up 就对了&lt;/code&gt;&lt;/pre&gt;&lt;figure&gt;&lt;img src=&quot;https://huowenlong.com/trilium-assets/cyZ6AgNjauV8/17b012657d005592.png&quot; width=&quot;2000&quot; height=&quot;1125&quot; /&gt;&lt;/figure&gt;&lt;h3&gt;2.2 建三个账号（CE 版没有注册页）&lt;/h3&gt;&lt;p&gt;Overleaf 社区版的注册页是写死的，注册不了，只能直接往 MongoDB 里插用户（或者通过邀请别人进行注册）。这部分的完整脚本我在《Overleaf 部署实战指南》里写过。&lt;/p&gt;&lt;a href=&quot;https://huowenlong.com/blog/KgAdONn6xKlf/&quot;&gt;&lt;span&gt;相关文章&lt;/span&gt;&lt;span&gt;Overleaf部署实战指南&lt;/span&gt;&lt;span&gt;本文记录在 fnOS NAS 上自建 Overleaf 6.2.2 社区版的完整实战：用 overleaf-toolkit 部署，迁移数据目录到用户可见路径，持久化挂载 eX Live 并切换清华镜像安装 ctex 等宏包，直插 MongoDB 创建管理员账号，最后经 Lucky 反代实现域名 H PS 外网访问与多人协作，并总结 7 个关键踩坑点。&lt;/span&gt;&lt;/a&gt;&lt;p&gt;我们三个人是按人一人一个账号分配的，邮箱统一用队内的 &lt;code&gt;mcm.com&lt;/code&gt; 域名，密码用「姓名缩写 + 年份 + 用途」的格式，好记又不容易记混：&lt;/p&gt;&lt;figure&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;code&gt;hwl@mcm.com&lt;/code&gt;&lt;/td&gt;&lt;td&gt;&lt;code&gt;HWL2026latex&lt;/code&gt;&lt;/td&gt;&lt;td&gt;管理员&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;skq@mcm.com&lt;/code&gt; &lt;/td&gt;&lt;td&gt;&lt;code&gt;SKQ2026latex&lt;/code&gt;&lt;/td&gt;&lt;td&gt;管理员&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;wyh@mcm.com&lt;/code&gt;&lt;/td&gt;&lt;td&gt;&lt;code&gt;WYH2026latex&lt;/code&gt;&lt;/td&gt;&lt;td&gt;管理员&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/figure&gt;&lt;h2&gt;三、把 Overleaf 共享到校园网&lt;/h2&gt;&lt;p&gt;这一步才是这次和上次最大的区别——&lt;strong&gt;不需要域名，不需要证书，只需要一个局域网 IP&lt;/strong&gt;。&lt;/p&gt;&lt;figure&gt;&lt;img src=&quot;https://huowenlong.com/trilium-assets/cyZ6AgNjauV8/fb8e787d3fc58daa.png&quot; width=&quot;2000&quot; height=&quot;1125&quot; /&gt;&lt;/figure&gt;&lt;h3&gt;3.1 拿到本机在校园网里的 IP&lt;/h3&gt;&lt;pre&gt;&lt;code&gt;ipconfig
# 找到正在用的那块网卡，记下 IPv4 地址（我们这台是 10.198.119.0）&lt;/code&gt;&lt;/pre&gt;&lt;h3&gt;3.2 放行防火墙&lt;/h3&gt;&lt;p&gt;Windows 默认会拦掉入站的 80 端口，队友会看到&quot;无法访问此网站&quot;。加一条入站规则：&lt;/p&gt;&lt;pre&gt;&lt;code&gt;netsh advfirewall firewall add rule name=&quot;Overleaf-80&quot; dir=in action=allow protocol=TCP localport=80&lt;/code&gt;&lt;/pre&gt;&lt;h3&gt;3.3 队友访问&lt;/h3&gt;&lt;p&gt;把地址发给队友，浏览器打开：&lt;/p&gt;&lt;pre&gt;&lt;code&gt;http://10.198.119.0/&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;三个人各自登录自己的账号，进同一个项目——&lt;strong&gt;实时协作就成立了&lt;/strong&gt;。谁在改哪一段，光标都是可见的，公式一边写一边就能看到渲染结果，这比微信传文件强太多。&lt;/p&gt;&lt;h2&gt;四、为什么要再落一份到本地文件夹&lt;/h2&gt;&lt;p&gt;到这里，&quot;三个人同时编辑&quot;的问题已经解决了。但真正开始写论文之后，我发现&lt;strong&gt;光有 Overleaf 还不够&lt;/strong&gt;：&lt;/p&gt;&lt;ul&gt;&lt;li&gt;几十页的 &lt;code&gt;.tex&lt;/code&gt; 在网页编辑器里翻起来很累，跨文件改动更是折磨&lt;/li&gt;&lt;li&gt;比赛的图和表是脚本生成的，需要有人（或者有 AI）批量改&lt;/li&gt;&lt;li&gt;最关键的一点：&lt;strong&gt;AI 读不了 Overleaf 的网页&lt;/strong&gt;。它只能读磁盘上的文件&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;于是就有了这次真正的核心：&lt;strong&gt;在本地建一个文件夹，让两个 AI 代理都去读它。&lt;/strong&gt;&lt;/p&gt;&lt;p&gt;我们最后的工作区长这样：&lt;/p&gt;&lt;pre&gt;&lt;code&gt;E题工作区间/
├── E题/paper_local/          ← 论文文件夹（两个 AI 共同读写的唯一接口）
│   ├── main.tex              ← 入口
│   ├── sec_q1.tex            ← 问题一正文
│   ├── sec_q2.tex            ← 问题二正文
│   ├── sec_q3.tex            ← 问题三正文
│   ├── sec_dataset.tex / sec_symbols.tex / sec_validation.tex
│   ├── appendix_A.tex / appendix_B.tex
│   ├── reference.bib
│   ├── figures/              ← 图
│   ├── papers/               ← 参考论文 PDF
│   └── support_q1 ~ q3/      ← 支撑材料代码
│
└── overleaf_api/             ← DeepSeek Harness 逆向出来的接口层
    ├── overleaf_client.py    ← 核心库（curl 传输 + socket.io + CRUD）
    ├── overleaf_cli.py       ← 命令行
    ├── overleaf_server.py    ← 统一 REST 服务
    ├── probe/                ← 逆向过程的探针与证据
    └── README.md             ← 接口文档&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;注意这个布局的关键点：&lt;strong&gt;接口层的代码一行都不放进论文文件夹&lt;/strong&gt;，两个目录完全分开。论文文件夹里只有论文，AI 看它的时候不会被一堆脚本干扰。&lt;/p&gt;&lt;h2&gt;五、双 Agent 分工：一个管同步，一个管写作&lt;/h2&gt;&lt;p&gt;先看整体架构：&lt;/p&gt;&lt;pre&gt;&lt;code&gt;┌──────────────────────── Windows 电脑 ────────────────────────┐
│                                                              │
│   ┌──────────────┐    ┌────────┐    ┌────────┐               │
│   │  sharelatex  │◄──►│ mongo  │    │ redis  │               │
│   │   容器 :80   │    └────────┘    └────────┘               │
│   └──────┬───────┘                                           │
│          │ 80:80 端口映射                                     │
│   ┌──────▼────────────────────────────────┐                  │
│   │      paper_local/  论文文件夹          │                  │
│   │  main.tex  sec_q*.tex  figures/  ...  │                  │
│   └───┬───────────────────────────▲───────┘                  │
│       │ 读 / 写                    │ pull / push              │
│   ┌───▼────────┐             ┌─────┴──────────────┐          │
│   │   Codex    │             │  DeepSeek Harness  │          │
│   │  负责写作   │             │  负责同步 + 接口层   │          │
│   └────────────┘             └────────────────────┘          │
└──────────────────────────────────────────────────────────────┘
                    ▲ 校园网 80 端口
        ┌───────────┼───────────┐
    队员 A       队员 B      队员 C
   (浏览器)     (浏览器)    (浏览器)&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;两个 AI 各干一件事，互不干扰。&lt;/p&gt;&lt;h3&gt;5.1 DeepSeek Harness：把 Overleaf 逆向成一套 CLI&lt;/h3&gt;&lt;p&gt;开题第一天我就发现了一个问题：&lt;strong&gt;这台自建 Overleaf 根本没有官方 API&lt;/strong&gt;。&lt;/p&gt;&lt;p&gt;Overleaf 社区版不提供 SaaS/Pro 那套 &lt;code&gt;/api/v1/...&lt;/code&gt; 的 REST 接口，官方文档里的端点在自建实例上直接 404。也就是说，想用脚本操作项目，只有一条路：&lt;strong&gt;把它当成一个普通网站，从网页版的行为里把接口抠出来。&lt;/strong&gt;&lt;/p&gt;&lt;p&gt;所以我让 DeepSeek Harness 做了这件事，完整走完了四步：&lt;/p&gt;&lt;ol&gt;&lt;li&gt;&lt;strong&gt;拿会话&lt;/strong&gt;：走网页端登录流程，拿到 &lt;code&gt;overleaf.sid&lt;/code&gt; cookie 和 CSRF token&lt;/li&gt;&lt;li&gt;&lt;strong&gt;找端点&lt;/strong&gt;：把它自己下发的前端 JS bundle 扒下来读，从中还原出内部端点&lt;/li&gt;&lt;li&gt;&lt;strong&gt;逐个实测&lt;/strong&gt;：每个端点都在真实服务器上验证一遍，宁可不写也不能猜&lt;/li&gt;&lt;li&gt;&lt;strong&gt;统一封装&lt;/strong&gt;：把混杂的 JSON / multipart / socket.io 收敛成一套干净的接口&lt;/li&gt;&lt;/ol&gt;&lt;figure&gt;&lt;img src=&quot;https://huowenlong.com/trilium-assets/cyZ6AgNjauV8/4e22a5a0af609379.png&quot; width=&quot;1908&quot; height=&quot;1152&quot; /&gt;&lt;/figure&gt;&lt;h4&gt;三个真正花时间的点&lt;/h4&gt;&lt;p&gt;&lt;strong&gt;① 文件树必须走 socket.io&lt;/strong&gt;&lt;/p&gt;&lt;p&gt;&lt;code&gt;GET /project/:pid/entities&lt;/code&gt; 只返回&lt;strong&gt;路径&lt;/strong&gt;，没有 id：&lt;/p&gt;&lt;pre&gt;&lt;code&gt;{&quot;project_id&quot;:&quot;...&quot;,&quot;entities&quot;:[{&quot;path&quot;:&quot;/main.tex&quot;,&quot;type&quot;:&quot;doc&quot;}]}&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;但后续所有增删改查都要用 entity id。id 只能从实时协议里拿——这台实例用的是 &lt;strong&gt;socket.io 0.9&lt;/strong&gt;，WebSocket 升级被 nginx 挡掉了（502），好在 &lt;code&gt;xhr-polling&lt;/code&gt; 是纯 HTTP，可以完全用 curl 驱动：&lt;/p&gt;&lt;pre&gt;&lt;code&gt;GET  /socket.io/1/?t=&amp;lt;ms&amp;gt;&amp;amp;projectId=&amp;lt;pid&amp;gt;     →  &amp;lt;sid&amp;gt;:60:60:websocket,xhr-polling
GET  /socket.io/1/xhr-polling/&amp;lt;sid&amp;gt;?t=&amp;lt;ms&amp;gt;    →  &quot;1::&quot;
POST /socket.io/1/xhr-polling/&amp;lt;sid&amp;gt;?t=&amp;lt;ms&amp;gt;
     body: 5:1::{&quot;name&quot;:&quot;joinProject&quot;,&quot;args&quot;:[{&quot;project_id&quot;:&quot;&amp;lt;pid&amp;gt;&quot;}]}
GET  /socket.io/1/xhr-polling/&amp;lt;sid&amp;gt;?t=&amp;lt;ms&amp;gt;    →  joinProjectResponse（带完整目录树）&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;strong&gt;② 上传接口的两个反直觉设计&lt;/strong&gt;&lt;/p&gt;&lt;figure&gt;&lt;table&gt;&lt;colgroup&gt;&lt;col /&gt;&lt;col /&gt;&lt;/colgroup&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;folder_id&lt;/code&gt; 放哪&lt;/td&gt;&lt;td&gt;&lt;strong&gt;必须在 query string&lt;/strong&gt;。放 body 一律 &lt;code&gt;422 folder_not_found&lt;/code&gt;，而且对根目录、子目录报的错一模一样，极易误判成&quot;目录 id 错了&quot;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;name&lt;/code&gt; 字段&lt;/td&gt;&lt;td&gt;&lt;strong&gt;必需&lt;/strong&gt;。只给 &lt;code&gt;qqfile&lt;/code&gt; 不给 &lt;code&gt;name&lt;/code&gt; → &lt;code&gt;422 invalid_filename&lt;/code&gt;。文件名的权威来源是 multipart 的 &lt;code&gt;name&lt;/code&gt;，不是 part 的 filename&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/figure&gt;&lt;p&gt;&lt;strong&gt;③ 最大的坑：macOS 不让第三方解释器访问局域网&lt;/strong&gt;&lt;/p&gt;&lt;p&gt;这是整个项目里最折磨人的一条，而且跟代码完全无关：&lt;/p&gt;&lt;figure&gt;&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;程序&lt;/th&gt;&lt;th&gt;能否访问 &lt;code&gt;10.198.119.0&lt;/code&gt;&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;/usr/bin/curl&lt;/code&gt;（Apple 系统自带）&lt;/td&gt;&lt;td&gt;✅ 可以&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;/usr/bin/nc&lt;/code&gt;&lt;/td&gt;&lt;td&gt;✅ 可以&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;python3&lt;/code&gt;（python.org / Homebrew）&lt;/td&gt;&lt;td&gt;❌ &lt;code&gt;Errno 65 No route to host&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;node&lt;/code&gt;（Homebrew v22）&lt;/td&gt;&lt;td&gt;❌ &lt;code&gt;EHOSTUNREACH&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/figure&gt;&lt;p&gt;macOS 的&lt;strong&gt;本地网络隐私权限&lt;/strong&gt;只信任 Apple 签名的系统二进制，第三方解释器一律被拒。目标和本机明明在同一个内网段，&lt;code&gt;ping&lt;/code&gt; 也通，但 Python 就是连不上。&lt;/p&gt;&lt;p&gt;所以这个项目的 HTTP 传输层&lt;strong&gt;全部基于 &lt;/strong&gt;&lt;code&gt;&lt;strong&gt;/usr/bin/curl&lt;/strong&gt;&lt;/code&gt;&lt;strong&gt; 子进程&lt;/strong&gt;，会话存在磁盘上的 Netscape cookie jar 里。这是个刻意的架构决定，不能&quot;顺手改成 requests&quot;——改了直接连不上。&lt;/p&gt;&lt;h4&gt;最后沉淀下来的东西&lt;/h4&gt;&lt;p&gt;逆向的产出不是一个临时脚本，而是三件可以长期复用的东西（都在 &lt;code&gt;overleaf_api/&lt;/code&gt;）：&lt;/p&gt;&lt;figure&gt;&lt;table&gt;&lt;colgroup&gt;&lt;col /&gt;&lt;col /&gt;&lt;/colgroup&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;overleaf_client.py&lt;/code&gt;&lt;/td&gt;&lt;td&gt;核心库：curl 传输层、登录、socket.io 客户端、路径寻址的 CRUD&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;overleaf_cli.py&lt;/code&gt;&lt;/td&gt;&lt;td&gt;命令行工具：tree / cat / write / mkdir / rm / rename / move / grep / pull / push / compile&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;overleaf_server.py&lt;/code&gt;&lt;/td&gt;&lt;td&gt;统一 REST 服务：把零散端点收敛成&lt;strong&gt;路径寻址&lt;/strong&gt;的接口，调用方只用 &lt;code&gt;/main.tex&lt;/code&gt; 这种人类路径，&lt;strong&gt;完全不需要知道 entity id&lt;/strong&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/figure&gt;&lt;p&gt;命令行长这样：&lt;/p&gt;&lt;pre&gt;&lt;code&gt;python3 overleaf_cli.py tree -r                              # 看整棵树
python3 overleaf_cli.py cat /main.tex                        # 读
python3 overleaf_cli.py write /sec_q1.tex --file sec_q1.tex  # 写（同名即覆盖）
python3 overleaf_cli.py grep &apos;\cite\{&apos; --ext .tex            # 全项目检索
python3 overleaf_cli.py pull ./paper_local                   # 整包拉下来
python3 overleaf_cli.py push ./paper_local --prefix /        # 整个目录推上去
python3 overleaf_cli.py compile --pdf main.pdf               # 触发线上编译并取回 PDF&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;💡 &lt;strong&gt;这一步的价值在于&quot;可复现&quot;&lt;/strong&gt;。接口被固化成命令 + 文档之后，同步就不再依赖某个人的记忆，也不需要每次打开网页手动上传——后续所有上传修改都是一条命令的事。&lt;/p&gt;&lt;h4&gt;一个我很在意的细节：不污染源项目&lt;/h4&gt;&lt;p&gt;写接口最怕的事，是测试的时候把真实项目改坏了。所以三套测试（库层 25 项、REST 28 项、CLI 全流程）用的是&lt;strong&gt;指纹法&lt;/strong&gt;：&lt;/p&gt;&lt;ol&gt;&lt;li&gt;先对真实项目取指纹：&lt;code&gt;sha256(每个节点的 id|type|path|hash)&lt;/code&gt;&lt;/li&gt;&lt;li&gt;所有写操作只在临时项目 &lt;code&gt;__..._SELFTEST_DO_NOT_USE__&lt;/code&gt; 上做&lt;/li&gt;&lt;li&gt;跑完删掉临时项目&lt;/li&gt;&lt;li&gt;再取一次真实项目指纹，&lt;strong&gt;不一致就判定失败&lt;/strong&gt;&lt;/li&gt;&lt;/ol&gt;&lt;p&gt;最终结果：&lt;/p&gt;&lt;pre&gt;&lt;code&gt;selftest.py   ===== 25 passed, 0 failed =====   （真实项目指纹前后一致）
rest_test.py  ===== 28 passed, 0 failed =====   （真实项目指纹前后一致）&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;真实项目 19 个节点，指纹前后完全相同。&lt;/p&gt;&lt;h3&gt;5.2 Codex：只关心论文怎么写&lt;/h3&gt;&lt;p&gt;Codex 那边就简单了：&lt;strong&gt;它只读同一个文件夹&lt;/strong&gt;（&lt;code&gt;paper_local/&lt;/code&gt;）。&lt;/p&gt;&lt;p&gt;它的职责是纯粹的写作：&lt;/p&gt;&lt;ul&gt;&lt;li&gt;读 &lt;code&gt;main.tex&lt;/code&gt; 和 &lt;code&gt;sec_q*.tex&lt;/code&gt;，理解当前论文结构&lt;/li&gt;&lt;li&gt;补写、润色问题一到问题三的正文，统一符号与术语&lt;/li&gt;&lt;li&gt;生成三线表、改写摘要、检查交叉引用和公式编号是否断裂&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;它不需要知道 Overleaf 在哪、端口是多少、cookie 怎么带、socket.io 怎么握手——&lt;strong&gt;这些全被隔离在接口层了&lt;/strong&gt;。&lt;/p&gt;&lt;h3&gt;5.3 为什么不让一个 AI 全干&lt;/h3&gt;&lt;p&gt;这是我这次体会最深的一点。把&quot;同步&quot;和&quot;写作&quot;交给同一个代理，会出两个问题：&lt;/p&gt;&lt;ul&gt;&lt;li&gt;&lt;strong&gt;上下文被污染&lt;/strong&gt;：socket.io 的分帧格式、&lt;code&gt;422&lt;/code&gt; 报错、cookie 过期这些细节会挤占写作需要的长上下文，写到后面它已经忘了前面用过什么符号&lt;/li&gt;&lt;li&gt;&lt;strong&gt;职责边界模糊&lt;/strong&gt;：一旦论文出了问题，你分不清是&quot;文件没同步对&quot;还是&quot;内容写错了&quot;&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;拆开之后，两个代理各自有一个非常清晰的成功标准：同步代理的成功标准是&quot;&lt;strong&gt;本地与线上完全一致&lt;/strong&gt;&quot;，写作代理的成功标准是&quot;&lt;strong&gt;这一节写得好&lt;/strong&gt;&quot;。排查问题时，责任一目了然。&lt;/p&gt;&lt;h2&gt;六、这次协作真正的收获&lt;/h2&gt;&lt;p&gt;比赛结束后回头看，我觉得最有价值的不是&quot;搭了一个 Overleaf&quot;，而是&lt;strong&gt;把协作拆成了两层&lt;/strong&gt;：&lt;/p&gt;&lt;p&gt;&lt;strong&gt;第一层是人跟人的协作&lt;/strong&gt;。Overleaf 解决的是&quot;三个人同时写一篇 LaTeX&quot;这件事——不用合并文件、不用对齐版本、不用问&quot;你改到哪了&quot;。这一层的关键是把服务放在所有人都够得着的地方，所以&quot;校园网内网共享&quot;这个看起来最土的做法，反而是最合适的：&lt;strong&gt;不依赖公网、不依赖域名、不依赖任何第三方服务&lt;/strong&gt;。&lt;/p&gt;&lt;p&gt;&lt;strong&gt;第二层是人和 AI 的协作&lt;/strong&gt;。DeepSeek Harness 负责同步，Codex 负责写作，&lt;code&gt;paper_local/&lt;/code&gt; 这个文件夹是它们共同的接口。这一层的关键是&lt;strong&gt;职责单一&lt;/strong&gt;：一个代理只做一件事，成功标准清清楚楚。&lt;/p&gt;&lt;h2&gt;总结&lt;/h2&gt;&lt;p&gt;四天四小时，这套链路是这样的：&lt;/p&gt;&lt;ol&gt;&lt;li&gt;&lt;strong&gt;部署&lt;/strong&gt;：Windows + Docker 起 Overleaf，80 端口映射到校园网，三个人浏览器直接访问&lt;/li&gt;&lt;li&gt;&lt;strong&gt;协作&lt;/strong&gt;：实时共同编辑一份 LaTeX，谁改哪里一目了然&lt;/li&gt;&lt;li&gt;&lt;strong&gt;逆向&lt;/strong&gt;：DeepSeek Harness 把没有官方 API 的 Overleaf CE 逆向成一套 CLI 和 REST 服务&lt;/li&gt;&lt;li&gt;&lt;strong&gt;写作&lt;/strong&gt;：Codex 读本地 &lt;code&gt;paper_local/&lt;/code&gt;，专心写论文&lt;/li&gt;&lt;li&gt;&lt;strong&gt;回传&lt;/strong&gt;：本地文件一变，DeepSeek Harness 识别到变化并 push 回线上&lt;/li&gt;&lt;li&gt;&lt;strong&gt;收口&lt;/strong&gt;：线上编译出 PDF，交 MD5 码前把版本彻底对齐&lt;/li&gt;&lt;/ol&gt;&lt;p&gt;&lt;em&gt;本文由 DeepSeek Harness 根据本次华为杯数学建模的实践过程整理，环境：Windows + Docker Desktop 自建 Overleaf CE，macOS 客户端（笔者），三人小队协作。&lt;/em&gt;&lt;/p&gt;</content:encoded></item><item><title>从微信对话到线上博客：我用 Hermes Agent 打通 Trilium 自动化发布链路</title><link>https://huowenlong.com/blog/y6WtEPfNytSp/</link><guid isPermaLink="true">https://huowenlong.com/blog/y6WtEPfNytSp/</guid><description>把微信对话经 Hermes Agent 整理成 Trilium 笔记、再自动发布到博客的完整链路：MCP 与 ETAPI 的接入、图片处理，以及由 Trilium 标签驱动的自动化设计。</description><pubDate>Mon, 31 Aug 2026 14:12:58 GMT</pubDate><content:encoded>&lt;h2&gt;前言&lt;/h2&gt;&lt;p&gt;你有没有过这样的体验：灵感来的时候在微信里跟 AI 聊得火热，聊完却要手动整理成文章、找图、排版、上传、发布，一套流程走下来热情已经凉了一半。我一直在折腾自己的博客（longBlog，基于 Astro 搭建），最大的痛点就是&lt;strong&gt;写文章的成本&lt;/strong&gt;：内容生产、图片处理、发布流程，每一步都在消耗精力。&lt;/p&gt;&lt;p&gt;这篇文章记录的是我最近做的一件事：把「微信对话 → 文章整理 → Trilium 笔记 → 自动发布到互联网」整条链路打通。现在我在微信里跟 Hermes Agent 说一声&quot;把这篇发到博客&quot;，剩下的全部自动化完成。&lt;/p&gt;&lt;p&gt;先放一张全貌架构图，本文就是围绕这条链路展开的：&lt;/p&gt;&lt;img src=&quot;https://huowenlong.com/trilium-assets/y6WtEPfNytSp/da863793a07584bf.jpg&quot; width=&quot;2554&quot; height=&quot;1280&quot; /&gt;&lt;h2&gt;背景：我的博客架构&lt;/h2&gt;&lt;p&gt;在开始之前，先交代一下我的博客整体架构。longBlog 是一套三层分离的自动化系统：&lt;/p&gt;&lt;pre&gt;&lt;code&gt;本地 Trilium（NAS，写作后台）
  ↓ 自动同步
云端 Trilium（blog.ssaw.top）
  ↓ poller 轮询 + 同步脚本
GitHub 仓库
  ↓ EdgeOne Pages 自动部署
https://huowenlong.com/&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;这个架构里，Trilium 是文章编辑后台，也是标签状态管理中心。文章写在哪、打什么标签、是否发布，都由 Trilium 里的笔记属性控制。&lt;/p&gt;&lt;h2&gt;第一部分：把 Hermes 接进 Trilium&lt;/h2&gt;&lt;h3&gt;为什么需要 Hermes&lt;/h3&gt;&lt;p&gt;Trilium 本身是一个强大的笔记软件，支持脚本、标签、关系、附件，甚至可以通过 ETAPI（REST API）完全编程控制。但它没有一个&quot;智能代理&quot;来帮你完成端到端的任务——比如从微信对话里提取内容、整理成文章、处理好图片、再导入 Trilium。&lt;/p&gt;&lt;p&gt;Hermes Agent 是我运行在 NAS 上的 AI 代理，通过微信接入。它的价值在于：&lt;strong&gt;能理解上下文、能调用工具、能自主完成多步骤任务&lt;/strong&gt;。&lt;/p&gt;&lt;h3&gt;配置 MCP 连接&lt;/h3&gt;&lt;p&gt;让 Hermes 操作 Trilium，最优雅的方式是 MCP（Model Context Protocol）。Hermes 内置 MCP 客户端，我在 &lt;code&gt;config.yaml&lt;/code&gt; 里配置了 Trilium 的 MCP 服务器：&lt;/p&gt;&lt;pre&gt;&lt;code&gt;&lt;span&gt;mcp_servers:&lt;/span&gt;
  &lt;span&gt;trilium:&lt;/span&gt;
    &lt;span&gt;url:&lt;/span&gt; &lt;span&gt;&quot;http://192.168.10.100:25399/mcp&quot;&lt;/span&gt;
    &lt;span&gt;headers:&lt;/span&gt;
      &lt;span&gt;Authorization:&lt;/span&gt; &lt;span&gt;&quot;Bearer &amp;lt;ETAPI_TOKEN&amp;gt;&quot;&lt;/span&gt;
    &lt;span&gt;timeout:&lt;/span&gt; &lt;span&gt;180&lt;/span&gt;
    &lt;span&gt;connect_timeout:&lt;/span&gt; &lt;span&gt;60&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;配置完成后，Hermes 启动时会自动发现 Trilium 的 23 个工具：搜索笔记、读取内容、创建笔记、设置属性、移动笔记、删除笔记等。这些工具直接以 &lt;code&gt;mcp_trilium_*&lt;/code&gt; 的形式出现在 Hermes 的工具箱里，我可以在微信里直接指挥它操作 Trilium。&lt;/p&gt;&lt;h3&gt;连通性验证&lt;/h3&gt;&lt;p&gt;配置完成后第一件事是验证连通性。我在微信里让 Hermes 搜索 Trilium 里的笔记，它成功返回了我的博客文章列表，还能完整读取《Overleaf 部署实战指南》这样的长文。这证明了：&lt;strong&gt;微信 → Hermes → Trilium 的链路完全打通&lt;/strong&gt;。&lt;/p&gt;&lt;h2&gt;第二部分：图片处理（关键细节）&lt;/h2&gt;&lt;p&gt;博客文章通常需要配图。Trilium 的图片是作为&lt;strong&gt;笔记的附件&lt;/strong&gt;存储的，引用格式是 &lt;code&gt;api/attachments/{attachmentId}/image/{title}&lt;/code&gt;。&lt;/p&gt;&lt;h3&gt;三种图片来源&lt;/h3&gt;&lt;ol&gt;&lt;li&gt;&lt;strong&gt;微信发的图片&lt;/strong&gt;：用户直接发图给 Hermes，图片自动缓存在 NAS 上，Hermes 可以读取&lt;/li&gt;&lt;li&gt;&lt;strong&gt;自己寻找的图片&lt;/strong&gt;：截图、网页图片，Hermes 可以自行获取&lt;/li&gt;&lt;li&gt;&lt;strong&gt;Excalidraw 绘制&lt;/strong&gt;：通过 Docker 里的 Excalidraw 应用辅助绘制示意图&lt;/li&gt;&lt;/ol&gt;&lt;h3&gt;上传流程&lt;/h3&gt;&lt;p&gt;MCP 工具里没有&quot;上传附件&quot;的能力，所以图片上传要走 Trilium 的 ETAPI。实测正确的格式是 JSON body + base64 编码：&lt;/p&gt;&lt;pre&gt;&lt;code&gt;&lt;span&gt;import&lt;/span&gt; json, base64, urllib.request

payload = {
    &lt;span&gt;&quot;ownerId&quot;&lt;/span&gt;: &lt;span&gt;&quot;&amp;lt;笔记ID&amp;gt;&quot;&lt;/span&gt;,          &lt;span&gt;# 图片所属的笔记&lt;/span&gt;
    &lt;span&gt;&quot;role&quot;&lt;/span&gt;: &lt;span&gt;&quot;image&quot;&lt;/span&gt;,                 &lt;span&gt;# 角色：图片&lt;/span&gt;
    &lt;span&gt;&quot;mime&quot;&lt;/span&gt;: &lt;span&gt;&quot;image/png&quot;&lt;/span&gt;,
    &lt;span&gt;&quot;title&quot;&lt;/span&gt;: &lt;span&gt;&quot;image.png&quot;&lt;/span&gt;,
    &lt;span&gt;&quot;content&quot;&lt;/span&gt;: base64.b64encode(图片数据).decode(),
    &lt;span&gt;&quot;position&quot;&lt;/span&gt;: &lt;span&gt;1&lt;/span&gt;
}
req = urllib.request.Request(
    &lt;span&gt;&quot;http://192.168.10.100:25399/etapi/attachments&quot;&lt;/span&gt;,
    data=json.dumps(payload).encode(),
    headers={&lt;span&gt;&quot;Authorization&quot;&lt;/span&gt;: &lt;span&gt;f&quot;Bearer &lt;span&gt;{TOKEN}&lt;/span&gt;&quot;&lt;/span&gt;, &lt;span&gt;&quot;Content-Type&quot;&lt;/span&gt;: &lt;span&gt;&quot;application/json&quot;&lt;/span&gt;},
    method=&lt;span&gt;&quot;POST&quot;&lt;/span&gt;
)&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;上传成功返回 201 + attachmentId，然后在笔记正文里插入引用即可。&lt;strong&gt;注意不要创建子文件夹放图片&lt;/strong&gt;——Trilium 的附件机制天然把图片挂在笔记下，这正好符合博客的需求。&lt;/p&gt;&lt;h2&gt;第三部分：文章发布流程&lt;/h2&gt;&lt;h3&gt;创建笔记&lt;/h3&gt;&lt;p&gt;Hermes 通过 MCP 在 longBlog 目录下创建一篇新笔记：&lt;/p&gt;&lt;pre&gt;&lt;code&gt;parentNoteId: zB8WioyKlvOw   # longBlog 目录
title: 文章标题
content: Markdown 正文
type: text&lt;/code&gt;&lt;/pre&gt;&lt;h3&gt;设置模板&lt;/h3&gt;&lt;p&gt;longBlog 有一个 &lt;code&gt;longTemplate&lt;/code&gt; 模板笔记，定义了博客需要的所有属性：publish、tags、summary、slug、sync、publishedAt、updatedAt 等。新笔记通过 &lt;code&gt;~template&lt;/code&gt; 关系关联到模板：&lt;/p&gt;&lt;pre&gt;&lt;code&gt;type: relation
name: template
value: MC7PtiChdF5S   # longTemplate 笔记 ID&lt;/code&gt;&lt;/pre&gt;&lt;h3&gt;设置标签&lt;/h3&gt;&lt;p&gt;发布前需要设置几个关键标签：&lt;/p&gt;&lt;figure&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;publish&lt;/td&gt;
&lt;td&gt;false&lt;/td&gt;
&lt;td&gt;先不发布，等确认&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;tags&lt;/td&gt;
&lt;td&gt;逗号分隔&lt;/td&gt;
&lt;td&gt;文章标签&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;summary&lt;/td&gt;
&lt;td&gt;一句话摘要&lt;/td&gt;
&lt;td&gt;博客列表页显示&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;slug&lt;/td&gt;
&lt;td&gt;URL 标识&lt;/td&gt;
&lt;td&gt;通常 = 标题&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;&lt;/figure&gt;&lt;h3&gt;自动同步到云端&lt;/h3&gt;&lt;p&gt;Trilium 的同步是&lt;strong&gt;全自动的数据库级同步&lt;/strong&gt;：本地改动会自动推送到云端 Trilium（blog.ssaw.top），&lt;strong&gt;全程不需要手动点击任何按钮&lt;/strong&gt;。&lt;/p&gt;&lt;p&gt;发布流程中没有任何手动同步操作——用户只需要聊天和确认内容，剩下的全部自动化。&lt;/p&gt;&lt;h3&gt;勾选发布&lt;/h3&gt;&lt;p&gt;确认文章无误后，把 &lt;code&gt;publish&lt;/code&gt; 标签设为 &lt;code&gt;true&lt;/code&gt;，Trilium 自动同步到云端，云端 poller 检测到发布状态变化后自动完成后续所有工作。&lt;/p&gt;&lt;h2&gt;第四部分：云端自动化链路（重点）&lt;/h2&gt;&lt;p&gt;这里我要重点介绍一下&lt;strong&gt;云端服务器上的自动化&lt;/strong&gt;——这是整个系统最核心的部分，也是我踩过最多坑的地方。&lt;/p&gt;&lt;h3&gt;主链路：服务器轮询&lt;/h3&gt;&lt;p&gt;最初的设计是依赖 Trilium 内部的后端脚本（&lt;code&gt;runOnAttributeChange&lt;/code&gt;）在标签变化时主动发 webhook。但实际使用中发现，TriliumNext 新版的后端脚本事件触发不一定稳定，而且其他客户端同步来的属性变化不一定能触发服务器端脚本。&lt;/p&gt;&lt;p&gt;所以我把主链路改成了&lt;strong&gt;服务器端轮询&lt;/strong&gt;：&lt;/p&gt;&lt;pre&gt;&lt;code&gt;poll_trilium_changes.py
  ↓ 每分钟轮询 Trilium ETAPI，检测变化
run_sync_and_build.sh
  ↓ 调度器：并发锁 + 准备 Git 工作区 + 构建
sync_trilium_posts.py
  ↓ 同步文章/附件，生成 Astro 数据，push GitHub
GitHub 仓库 → EdgeOne Pages 自动部署&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;三个脚本各司其职：&lt;/p&gt;&lt;p&gt;&lt;strong&gt;poll_trilium_changes.py&lt;/strong&gt;（主触发器）：每分钟调用 Trilium ETAPI，扫描 longBlog 根节点下的文章，读取标题、标签、正文，与上一次快照对比。检测字段包括：title、publish、sync、pinned、aiRefresh、contentHash。&lt;/p&gt;&lt;p&gt;&lt;strong&gt;run_sync_and_build.sh&lt;/strong&gt;（调度器）：把触发事件转成一次完整同步任务。它做并发锁（避免多个任务同时操作 Git 工作区）、准备 Git 工作区、调用同步脚本、执行构建验证。&lt;/p&gt;&lt;p&gt;&lt;strong&gt;sync_trilium_posts.py&lt;/strong&gt;（核心同步）：真正把 Trilium 内容转成博客仓库数据的脚本。它读取文章树、正文、标签，下载并本地化附件，生成 Astro 前端数据文件（&lt;code&gt;trilium-posts.content.generated.ts&lt;/code&gt; 和 &lt;code&gt;trilium-posts.meta.generated.ts&lt;/code&gt;），判断 Git 是否有变化，然后自动 commit + push。&lt;/p&gt;&lt;h3&gt;关键的标签系统&lt;/h3&gt;&lt;p&gt;云端同步脚本处理这些 Trilium 标签：&lt;/p&gt;&lt;figure&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;publish&lt;/td&gt;
&lt;td&gt;控制文章是否发布到博客&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;sync&lt;/td&gt;
&lt;td&gt;手动请求同步&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;pinned&lt;/td&gt;
&lt;td&gt;控制是否置顶&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;aiRefresh&lt;/td&gt;
&lt;td&gt;请求刷新 AI 摘要 / 标签&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;syncStatus&lt;/td&gt;
&lt;td&gt;同步状态（publishing、published、error）&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;syncHash&lt;/td&gt;
&lt;td&gt;内容同步 hash，判断是否变化&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;publishedAt / updatedAt&lt;/td&gt;
&lt;td&gt;发布时间 / 更新时间&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;slug&lt;/td&gt;
&lt;td&gt;文章 URL 标识&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;summary&lt;/td&gt;
&lt;td&gt;摘要&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;tags&lt;/td&gt;
&lt;td&gt;标签&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;&lt;/figure&gt;&lt;p&gt;排序规则：置顶文章优先；非置顶文章按原始 publishedAt 排序。&lt;/p&gt;&lt;h3&gt;systemd 定时器&lt;/h3&gt;&lt;p&gt;云端通过 systemd 定时器实现每分钟轮询：&lt;/p&gt;&lt;pre&gt;&lt;code&gt;longblog-poller.timer  → 每分钟触发
longblog-poller.service → 执行一次 poll_trilium_changes.py&lt;/code&gt;&lt;/pre&gt;&lt;h3&gt;排查文件&lt;/h3&gt;&lt;p&gt;这套系统最实用的部分是完整的日志体系。出问题时按这个顺序查：&lt;/p&gt;&lt;pre&gt;&lt;code&gt;tail -80 /root/longblog-automation/runtime/logs/poller.log   # poller 是否发现变化
cat /root/longblog-automation/runtime/reports/last_report.json  # 同步/构建/push 结果
cd /root/longblog-automation/workspace/current &amp;amp;&amp;amp; git log -5 --oneline  # GitHub 提交&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;poller 日志的关键信息：&lt;/p&gt;&lt;pre&gt;&lt;code&gt;no changes notes=14          # 没有检测到变化
changes=1 event=pinned_changed noteId=xxx title=xxx changed=[&apos;pinned&apos;]  # 检测到变化
runner ok; snapshot updated  # runner 执行成功
ERROR runner failed; snapshot not updated  # 同步失败，快照不更新，下次继续尝试&lt;/code&gt;&lt;/pre&gt;&lt;h3&gt;为什么不用 webhook 作主链路&lt;/h3&gt;&lt;p&gt;旧的 webhook 服务（&lt;code&gt;trilium_sync_webhook.py&lt;/code&gt;）仍然在运行，但不再作为稳定主链路依赖。原因：&lt;/p&gt;&lt;ol&gt;&lt;li&gt;TriliumNext 新版本中，后端脚本事件触发不一定稳定&lt;/li&gt;&lt;li&gt;其他客户端同步来的属性变化，不一定触发服务器端 &lt;code&gt;runOnAttributeChange&lt;/code&gt;&lt;/li&gt;&lt;li&gt;后端脚本安全权限较高&lt;/li&gt;&lt;/ol&gt;&lt;p&gt;所以现在的理解是：&lt;strong&gt;poller 是主链路，webhook 是备用实时入口&lt;/strong&gt;。&lt;/p&gt;&lt;h2&gt;第五部分：数据一致性修复&lt;/h2&gt;&lt;p&gt;在整套系统跑起来的过程中，我遇到过一个很典型的问题：&lt;strong&gt;升级 Trilium 后同步失败&lt;/strong&gt;。&lt;/p&gt;&lt;h3&gt;现象&lt;/h3&gt;&lt;p&gt;升级 Trilium 到 v0.105 后，同步一直失败，日志里大量报错：&lt;/p&gt;&lt;pre&gt;&lt;code&gt;ERROR: Cannot find entity for entity change {...}
Sync failed: Cannot read properties of undefined (reading &apos;entityChange&apos;)&lt;/code&gt;&lt;/pre&gt;&lt;h3&gt;根因&lt;/h3&gt;&lt;p&gt;数据库的 &lt;code&gt;entity_changes&lt;/code&gt; 表里有大量&quot;悬空记录&quot;——指向已删除实体的变更记录。这些是升级时 Demo 内容、损坏主题的变更残留。同步时逐个查找这些记录对应的实体，发现不存在 → 报错 → 同步崩溃。&lt;/p&gt;&lt;h3&gt;解决&lt;/h3&gt;&lt;p&gt;清理 &lt;code&gt;entity_changes&lt;/code&gt; 表里的悬空记录（指向不存在实体的记录）：&lt;/p&gt;&lt;pre&gt;&lt;code&gt;&lt;span&gt;# 对 notes / attributes / branches 三类实体&lt;/span&gt;
DELETE FROM entity_changes
WHERE entityName = &lt;span&gt;&apos;xxx&apos;&lt;/span&gt;
AND entityId NOT IN (SELECT &lt;span&gt;id&lt;/span&gt; FROM xxx)&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;清理了 1489 条悬空记录后，同步恢复正常。这个坑提醒我：&lt;strong&gt;升级后如果同步异常，先检查 entity_changes 表的一致性&lt;/strong&gt;。&lt;/p&gt;&lt;h2&gt;总结&lt;/h2&gt;&lt;p&gt;现在我的写作流程变成了这样：&lt;/p&gt;&lt;ol&gt;&lt;li&gt;在微信里跟 Hermes 聊天，聊出内容&lt;/li&gt;&lt;li&gt;让 Hermes 整理成文章（自动处理图片）&lt;/li&gt;&lt;li&gt;Hermes 通过 MCP 创建 Trilium 笔记、设置模板和标签&lt;/li&gt;&lt;li&gt;云端自动完成：自动同步 → poller 轮询 → 构建 → 推送 GitHub → 部署上线&lt;/li&gt;&lt;/ol&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;Trilium 是内容中枢&lt;/strong&gt;：所有文章以笔记形式存在，标签控制状态&lt;/li&gt;&lt;li&gt;&lt;strong&gt;Hermes 是智能入口&lt;/strong&gt;：理解对话、调用工具、完成任务&lt;/li&gt;&lt;li&gt;&lt;strong&gt;云端 poller 是可靠触发器&lt;/strong&gt;：轮询代替 webhook，稳定可靠&lt;/li&gt;&lt;li&gt;&lt;strong&gt;GitHub + EdgeOne 是发布通道&lt;/strong&gt;：仓库更新即部署&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;如果你也在折腾个人博客，希望这篇文章能给你一些启发：&lt;strong&gt;写博客最贵的不是服务器，是你的注意力&lt;/strong&gt;。把重复劳动交给自动化，把精力留给真正重要的内容创作。&lt;/p&gt;&lt;p&gt;&lt;em&gt;本文由 Hermes Agent 根据微信对话与实践过程整理，从对话整理到同步上云的完整自动化流程记录。&lt;/em&gt;&lt;/p&gt;</content:encoded></item><item><title>Overleaf部署实战指南</title><link>https://huowenlong.com/blog/KgAdONn6xKlf/</link><guid isPermaLink="true">https://huowenlong.com/blog/KgAdONn6xKlf/</guid><description>在 fnOS NAS 上用 overleaf-toolkit 自建 Overleaf 6.2.2 社区版：端口规划、mongo 与 redis 内网互连、TeX Live 全量宏包、Lucky 反代与 HTTPS，以及国内拉镜像等踩坑处理。</description><pubDate>Sun, 30 Aug 2026 09:37:19 GMT</pubDate><content:encoded>&lt;p&gt; &lt;/p&gt;&lt;blockquote&gt;&lt;p&gt;&lt;strong&gt;部署环境&lt;/strong&gt;：fnOS（基于 Debian 深度定制）NAS · 2026-08-24 &lt;strong&gt;最终成果&lt;/strong&gt;：Overleaf 6.2.2 社区版 + TeX Live 2026 全量宏包 + 域名 HTTPS 外网访问 + 多人协作 &lt;strong&gt;阅读对象&lt;/strong&gt;：想在 NAS 上自建 Overleaf 的人&lt;/p&gt;&lt;/blockquote&gt;&lt;h3&gt;一、为什么要在 NAS 上自建 Overleaf&lt;/h3&gt;&lt;p&gt;Overleaf 是在线 LaTeX 编辑器，官方 SaaS 版有诸多限制（免费版编译慢、不能调宏包）。自建社区版（CE）的好处：&lt;/p&gt;&lt;ul&gt;&lt;li&gt;✅ 完全免费、无限项目、私有部署&lt;/li&gt;&lt;li&gt;✅ 可自由安装 TeX 宏包（ctex 中文支持等）&lt;/li&gt;&lt;li&gt;✅ 多人实时协作（类似 Google Docs 的 LaTeX 版）&lt;/li&gt;&lt;li&gt;✅ 数据完全掌握在自己手里&lt;/li&gt;&lt;/ul&gt;&lt;h3&gt;二、架构总览&lt;/h3&gt;&lt;pre&gt;&lt;code&gt;外网用户 (IPv6)
    │
    ▼
┌──────────────────────┐
│  Lucky 反代 (fnOS 应用)│  ← 443 端口，HTTPS 终止
│  overleaf.epochwl.top │  ← ZeroSSL 正式证书
└──────────┬───────────┘
           │ 反代转发
           ▼
┌──────────────────────┐
│   sharelatex 容器     │  ← 18080:80
│   (Overleaf 6.2.2)    │
└──────┬────────┬───────┘
       │        │
┌──────▼──┐ ┌───▼──────┐
│ mongo 8 │ │ redis 7  │
└─────────┘ └──────────┘&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;strong&gt;端口规划&lt;/strong&gt;（fnOS 上避免冲突）：&lt;/p&gt;&lt;ul&gt;&lt;li&gt;Overleaf Web：&lt;code&gt;18080&lt;/code&gt;（避让 8080/8081/8890 等）&lt;/li&gt;&lt;li&gt;Lucky 管理：&lt;code&gt;16666&lt;/code&gt;&lt;/li&gt;&lt;li&gt;Lucky 反代：&lt;code&gt;443&lt;/code&gt;&lt;/li&gt;&lt;li&gt;mongo/redis：容器内网互连，不暴露宿主机&lt;/li&gt;&lt;/ul&gt;&lt;h3&gt;三、第一步：拉取部署（overleaf-toolkit）&lt;/h3&gt;&lt;p&gt;官方推荐用 &lt;a href=&quot;https://github.com/overleaf/toolkit&quot;&gt;overleaf-toolkit&lt;/a&gt; 部署：&lt;/p&gt;&lt;pre&gt;&lt;code&gt;git clone https://github.com/overleaf/toolkit.git
cd toolkit
# 配置
cp config/overleaf.rc.example config/overleaf.rc
cp config/variables.env.example config/variables.env&lt;/code&gt;&lt;/pre&gt;&lt;h4&gt;关键配置项&lt;/h4&gt;&lt;p&gt;&lt;code&gt;&lt;strong&gt;config/overleaf.rc&lt;/strong&gt;&lt;/code&gt;：&lt;/p&gt;&lt;pre&gt;&lt;code&gt;OVERLEAF_DATA_PATH=/vol1/1000/docker/overleaf/overleaf
MONGO_DATA_PATH=/vol1/1000/docker/overleaf/mongo
REDIS_DATA_PATH=/vol1/1000/docker/overleaf/redis
GIT_BRIDGE_DATA_PATH=data/git-bridge&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;code&gt;&lt;strong&gt;config/variables.env&lt;/strong&gt;&lt;/code&gt;（核心）：&lt;/p&gt;&lt;pre&gt;&lt;code&gt;OVERLEAF_SITE_URL=https://overleaf.epochwl.top   # ← 最终访问地址
OVERLEAF_NAV_TITLE=Overleaf
OVERLEAF_ADMIN_EMAIL=minlonghuo@gmail.com
OVERLEAF_BEHIND_PROXY=true                        # ← 反代模式（必须）
# 注意：OVERLEAF_SECURE_COOKIE 绝对不能设！见踩坑 #7&lt;/code&gt;&lt;/pre&gt;&lt;h4&gt;启动&lt;/h4&gt;&lt;pre&gt;&lt;code&gt;bin/up -d&lt;/code&gt;&lt;/pre&gt;&lt;blockquote&gt;&lt;p&gt;⚠️ &lt;strong&gt;坑 #1&lt;/strong&gt;：国内网络拉 Docker 镜像 &lt;code&gt;sharelatex/sharelatex:6.2.2&lt;/code&gt;（约 3GB）可能很慢或超时。建议配置 Docker 国内镜像加速器（如 docker.m.daocloud.io、registry.cn-hangzhou.aliyuncs.com），或用 &lt;code&gt;docker pull&lt;/code&gt; 分步拉取。&lt;/p&gt;&lt;/blockquote&gt;&lt;h3&gt;四、第二步：数据目录迁移（关键！）&lt;/h3&gt;&lt;h4&gt;为什么必须迁移&lt;/h4&gt;&lt;p&gt;overleaf-toolkit 默认把数据放在 &lt;code&gt;toolkit/data/&lt;/code&gt; 下（也就是应用安装目录里）。但 fnOS 上：&lt;/p&gt;&lt;ol&gt;&lt;li&gt;&lt;code&gt;/vol1/docker&lt;/code&gt; 是系统路径，文件管理器不可见，备份/管理不便&lt;/li&gt;&lt;li&gt;用户可见的数据目录是 &lt;code&gt;/vol{N}/&amp;lt;uid&amp;gt;/&lt;/code&gt; 形式（如 &lt;code&gt;/vol1/1000/&lt;/code&gt;）&lt;/li&gt;&lt;li&gt;升级/卸载应用可能清掉数据目录&lt;/li&gt;&lt;/ol&gt;&lt;h4&gt;迁移步骤&lt;/h4&gt;&lt;pre&gt;&lt;code&gt;# 1. 先停止服务
cd overleaf-toolkit &amp;amp;&amp;amp; bash bin/stop

# 2. 建目标目录（用户可见）
mkdir -p /vol1/1000/docker/overleaf/

# 3. 移动各数据子目录（保留权限）
mv toolkit/data/overleaf   /vol1/1000/docker/overleaf/overleaf
mv toolkit/data/mongo      /vol1/1000/docker/overleaf/mongo
mv toolkit/data/redis      /vol1/1000/docker/overleaf/redis
mv toolkit/data/git-bridge /vol1/1000/docker/overleaf/git-bridge

# 4. 改 overleaf.rc 指向新路径（见上文配置）
# 5. 重新启动
bash bin/up -d&lt;/code&gt;&lt;/pre&gt;&lt;blockquote&gt;&lt;p&gt;⚠️ &lt;strong&gt;坑 #2&lt;/strong&gt;：迁移后&lt;strong&gt;必须删容器重建&lt;/strong&gt;（&lt;code&gt;docker rm -f sharelatex mongo redis &amp;amp;&amp;amp; bin/up -d&lt;/code&gt;），因为 docker volume 挂载关系在容器创建时就已经固定，改配置文件后 &lt;code&gt;bin/restart&lt;/code&gt; 不生效。&lt;/p&gt;&lt;/blockquote&gt;&lt;blockquote&gt;&lt;p&gt;💡 &lt;strong&gt;最终目录结构&lt;/strong&gt;（本机）：&lt;/p&gt;&lt;pre&gt;&lt;code&gt;/vol1/1000/docker/overleaf/
├── overleaf/    # Overleaf 应用数据（项目、用户上传）
├── mongo/       # 用户/项目数据库
├── redis/       # 缓存
└── texlive/     # TeX Live 宏包（持久化挂载，见踩坑 #4）&lt;/code&gt;&lt;/pre&gt;&lt;/blockquote&gt;&lt;h3&gt;五、第三步：TeX Live 宏包安装（最大的坑）&lt;/h3&gt;&lt;p&gt;Overleaf 社区版自带 TeX Live 2026，但&lt;strong&gt;只有基础包&lt;/strong&gt;，没有 ctex（中文）、float、booktabs、tikz 等常用宏包。装这些需要 tlmgr。&lt;/p&gt;&lt;h4&gt;5.1 踩坑：容器重建 = 宏包全丢&lt;/h4&gt;&lt;blockquote&gt;&lt;p&gt;⚠️ &lt;strong&gt;坑 #3（最重要的坑）&lt;/strong&gt;：sharelatex 容器是&lt;strong&gt;无状态&lt;/strong&gt;的，tlmgr 装的宏包写在容器可写层。&lt;strong&gt;只要容器被删除重建（docker rm -f），所有手动安装的宏包全部丢失&lt;/strong&gt;！表现就是：昨天还能编译的中文文档，今天报 &lt;code&gt;File &apos;ctexart.cls&apos; not found&lt;/code&gt;。&lt;/p&gt;&lt;/blockquote&gt;&lt;p&gt;&lt;strong&gt;解决方案：把 texlive 目录持久化挂载到宿主机&lt;/strong&gt;：&lt;/p&gt;&lt;pre&gt;&lt;code&gt;# 1. 先把容器内的 texlive 复制到宿主机（一次性）
mkdir -p /vol1/1000/docker/overleaf/texlive
docker cp sharelatex:/usr/local/texlive/. /vol1/1000/docker/overleaf/texlive/

# 2. 修改 docker-compose.base.yml，加一行挂载
# 文件：overleaf-toolkit/lib/docker-compose.base.yml
# 在 volumes 部分追加：
#   - &quot;${TEXLIVE_DATA_PATH:-/vol1/1000/docker/overleaf/texlive}:/usr/local/texlive&quot;

# 3. 重建容器生效
docker rm -f sharelatex &amp;amp;&amp;amp; bash bin/up -d&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;这样以后容器重建，宏包都还在。&lt;/p&gt;&lt;h4&gt;5.2 踩坑：tlmgr 卡死（默认源被墙）&lt;/h4&gt;&lt;blockquote&gt;&lt;p&gt;⚠️ &lt;strong&gt;坑 #4&lt;/strong&gt;：tlmgr 默认用 &lt;code&gt;https://mirror.ox.ac.uk/sites/ctan.org/...&lt;/code&gt;（英国镜像），国内网络访问&lt;strong&gt;极慢甚至卡死&lt;/strong&gt;——进程看着在跑（&lt;code&gt;ps&lt;/code&gt; 有 perl 进程），但 CPU 时间几乎不动，等 13 分钟毫无进展。&lt;/p&gt;&lt;/blockquote&gt;&lt;p&gt;&lt;strong&gt;解决方案：切换国内镜像源（清华/中科大）&lt;/strong&gt;：&lt;/p&gt;&lt;pre&gt;&lt;code&gt;# 清华镜像（推荐，实测 52 个包 13 秒装完）
tlmgr option repository https://mirrors.tuna.tsinghua.edu.cn/CTAN/systems/texlive/tlnet
# 或中科大
tlmgr option repository https://mirrors.ustc.edu.cn/CTAN/systems/texlive/tlnet&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;strong&gt;验证连通性的命令&lt;/strong&gt;（装之前先测）：&lt;/p&gt;&lt;pre&gt;&lt;code&gt;curl -s -o /dev/null -w &quot;%{http_code} %{time_total}s\n&quot; \
  https://mirrors.tuna.tsinghua.edu.cn/CTAN/systems/texlive/tlnet/tlpkg/texlive.tlpdb
# 期望：200 + 1秒内；如果超时或 FAIL 说明源不可用&lt;/code&gt;&lt;/pre&gt;&lt;h4&gt;5.3 中文支持宏包安装清单&lt;/h4&gt;&lt;pre&gt;&lt;code&gt;# 中文核心（ctex + xeCJK + fontspec）
tlmgr install ctex xecjk fontspec

# 常用补充
tlmgr install float needspace caption booktabs hyperref amsmath \
              geometry listings biblatex natbib tikz pgf

# 国标参考文献（注意：新版按年份分文件！见坑 #5）
tlmgr install gbt7714

# 刷新文件数据库
mktexlsr&lt;/code&gt;&lt;/pre&gt;&lt;h4&gt;5.4 验证&lt;/h4&gt;&lt;pre&gt;&lt;code&gt;kpsewhich ctexart.cls    # 应输出 /usr/local/texlive/.../ctexart.cls
kpsewhich xeCJK.sty
kpsewhich fontspec.sty&lt;/code&gt;&lt;/pre&gt;&lt;blockquote&gt;&lt;p&gt;⚠️ &lt;strong&gt;坑 #5&lt;/strong&gt;：新版 &lt;code&gt;gbt7714&lt;/code&gt; 宏包不再提供 &lt;code&gt;gbt7714.bst&lt;/code&gt;，而是按年份分文件：&lt;code&gt;gbt7714-2015-numeric.bst&lt;/code&gt;、&lt;code&gt;gbt7714-2015-authoryear.bst&lt;/code&gt;、&lt;code&gt;gbt7714-2025-numeric.bst&lt;/code&gt; 等。用 &lt;code&gt;\bibliographystyle{gbt7714-2015-numeric}&lt;/code&gt; 这种带年份的名字。&lt;/p&gt;&lt;/blockquote&gt;&lt;h4&gt;5.5 编译实测&lt;/h4&gt;&lt;pre&gt;&lt;code&gt;&lt;span&gt;\documentclass&lt;/span&gt;{ctexart}
&lt;span&gt;\usepackage&lt;/span&gt;{float, booktabs, amsmath, tikz}
&lt;span&gt;\begin&lt;/span&gt;{document}
中文测试 &lt;span&gt;$&lt;/span&gt;E=mc&lt;span&gt;^&lt;/span&gt;2&lt;span&gt;$&lt;/span&gt;
&lt;span&gt;\end&lt;/span&gt;{document}&lt;/code&gt;&lt;/pre&gt;&lt;pre&gt;&lt;code&gt;xelatex -interaction=nonstopmode test.tex   # 应生成 PDF 无致命错误&lt;/code&gt;&lt;/pre&gt;&lt;h3&gt;六、第四步：创建管理员账号&lt;/h3&gt;&lt;h4&gt;踩坑：CE 版没有注册页&lt;/h4&gt;&lt;blockquote&gt;&lt;p&gt;⚠️ &lt;strong&gt;坑 #6&lt;/strong&gt;：Overleaf 社区版 6.x 的注册页是&lt;strong&gt;写死&lt;/strong&gt;的（&lt;code&gt;register.pug&lt;/code&gt; 模板直接显示&quot;Please contact admin&quot;），&lt;strong&gt;没有自助注册功能&lt;/strong&gt;。只有 0 用户时才有注册表单。所以创建账号必须&lt;strong&gt;直接操作 MongoDB&lt;/strong&gt;。&lt;/p&gt;&lt;/blockquote&gt;&lt;h4&gt;用 Node 脚本直插 MongoDB&lt;/h4&gt;&lt;p&gt;在 sharelatex 容器内执行（容器里有 mongodb 和 bcrypt 库）：&lt;/p&gt;&lt;pre&gt;&lt;code&gt;const mongodb = require(&quot;mongodb&quot;);
const bcrypt = require(&quot;bcrypt&quot;);
const MongoClient = mongodb.MongoClient;
const ObjectId = mongodb.ObjectId;

(async () =&amp;gt; {
  const client = await MongoClient.connect(&quot;mongodb://mongo/sharelatex&quot;);
  const db = client.db(&quot;sharelatex&quot;);
  const users = db.collection(&quot;users&quot;);

  const email = &quot;minlonghuo@gmail.com&quot;;
  const password = &quot;你的密码&quot;;
  const now = new Date();
  const hash = bcrypt.hashSync(password, 12);   // ← Overleaf 要求 bcrypt 12 轮！

  await users.deleteMany({ email: email });     // 幂等：先删旧的

  const user = {
    _id: new ObjectId(),
    email: email,
    emailLower: email.toLowerCase(),
    hashedPassword: hash,                       // ← 字段名是 hashedPassword 不是 password！
    first_name: &quot;minlonghuo&quot;,
    last_name: &quot;&quot;,
    created: now,
    loginCount: 0,
    email_verification: { verified: true, sentAt: now },  // ← 必须 verified，否则登录被拦
    features: { collaborators: -1, versioning: true, trackChanges: true, compileTimeout: 60 },
    isAdmin: true,                              // ← 管理员标记
    holdingAccount: false,
    signUpDate: now,
    samlIdentifiers: [],
    must_reconfirm: false
  };

  await users.insertOne(user);
  console.log(&quot;SUCCESS:&quot;, email);
  await client.close();
})().catch(e =&amp;gt; { console.error(e); process.exit(1); });&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;执行方式：&lt;/p&gt;&lt;pre&gt;&lt;code&gt;docker cp create_user.js sharelatex:/overleaf/create_user.js
docker exec sharelatex bash -c &apos;cd /overleaf &amp;amp;&amp;amp; node create_user.js&apos;
docker exec sharelatex rm -f /overleaf/create_user.js   # 清理&lt;/code&gt;&lt;/pre&gt;&lt;h4&gt;验证登录&lt;/h4&gt;&lt;pre&gt;&lt;code&gt;# 容器内 curl 模拟登录
curl -s -X POST http://localhost:80/login -H &apos;Content-Type: application/json&apos; \
  -d &apos;{&quot;email&quot;:&quot;minlonghuo@gmail.com&quot;,&quot;password&quot;:&quot;你的密码&quot;}&apos; -D - -o /dev/null
# 期望返回 302，Location: /project&lt;/code&gt;&lt;/pre&gt;&lt;blockquote&gt;&lt;p&gt;💡 &lt;strong&gt;要点&lt;/strong&gt;：&lt;/p&gt;&lt;ul&gt;&lt;li&gt;密码字段必须是 &lt;code&gt;hashedPassword&lt;/code&gt;（不是 &lt;code&gt;password&lt;/code&gt;）&lt;/li&gt;&lt;li&gt;bcrypt 轮数必须是 12（Overleaf 默认）&lt;/li&gt;&lt;li&gt;&lt;code&gt;email_verification.verified&lt;/code&gt; 必须为 true&lt;/li&gt;&lt;li&gt;管理员设 &lt;code&gt;isAdmin: true&lt;/code&gt;，普通用户 false&lt;/li&gt;&lt;/ul&gt;&lt;/blockquote&gt;&lt;h3&gt;七、第五步：域名反代（Lucky）&lt;/h3&gt;&lt;h4&gt;架构&lt;/h4&gt;&lt;p&gt;fnOS 上装 Lucky 应用（&lt;code&gt;/vol2/@appcenter/Lucky&lt;/code&gt;），做反向代理：&lt;/p&gt;&lt;pre&gt;&lt;code&gt;overleaf.epochwl.top:443  →  http://192.168.10.100:18080&lt;/code&gt;&lt;/pre&gt;&lt;h4&gt;Lucky 配置要点&lt;/h4&gt;&lt;ol&gt;&lt;li&gt;打开 Lucky 管理后台：&lt;code&gt;http://NAS_IP:16666&lt;/code&gt;（默认路径 &lt;code&gt;/safety/&lt;/code&gt;）&lt;/li&gt;&lt;li&gt;反向代理模块 → 添加规则：&lt;ul&gt;&lt;li&gt;&lt;strong&gt;前端域名&lt;/strong&gt;：&lt;code&gt;overleaf.epochwl.top&lt;/code&gt;&lt;/li&gt;&lt;li&gt;&lt;strong&gt;前端端口&lt;/strong&gt;：&lt;code&gt;443&lt;/code&gt;&lt;/li&gt;&lt;li&gt;&lt;strong&gt;后端地址&lt;/strong&gt;：&lt;code&gt;http://192.168.10.100:18080&lt;/code&gt;&lt;/li&gt;&lt;li&gt;&lt;strong&gt;证书&lt;/strong&gt;：绑定 ZeroSSL/Let&apos;s Encrypt 证书&lt;/li&gt;&lt;/ul&gt;&lt;/li&gt;&lt;li&gt;保存后 443 端口立即生效&lt;/li&gt;&lt;/ol&gt;&lt;blockquote&gt;&lt;p&gt;⚠️ &lt;strong&gt;坑 #7（Lucky 配置导致 443 全挂）&lt;/strong&gt;：改反代规则时如果配置不当（比如证书绑定错误、监听端口配置冲突），&lt;strong&gt;443 端口会对所有域名 Connection reset&lt;/strong&gt;，表现为 &lt;code&gt;TLS handshake error: bad record MAC&lt;/code&gt;。排查方法：&lt;/p&gt;&lt;pre&gt;&lt;code&gt;# 看监听是否正常
ss -tlnp | grep 443
# 模拟带 Host 头的请求
curl -sk -H &quot;Host: overleaf.epochwl.top&quot; https://[::1]:443/
# 期望 302 跳转；如果 000/Connection reset 说明规则坏了&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;修复：在 Lucky 后台检查规则（证书是否绑定、端口是否正确），重新保存。&lt;/p&gt;&lt;/blockquote&gt;&lt;h4&gt;Lucky 配置文件说明&lt;/h4&gt;&lt;ul&gt;&lt;li&gt;配置存在 &lt;code&gt;/vol2/@appdata/Lucky/data/*.lkcf&lt;/code&gt;（&lt;strong&gt;AES 加密&lt;/strong&gt;，无法直接读）&lt;/li&gt;&lt;li&gt;管理 API 在 &lt;code&gt;http://127.0.0.1:16666/safety/api/&lt;/code&gt;（需要登录 token）&lt;/li&gt;&lt;li&gt;日志在 &lt;code&gt;/vol2/@appdata/Lucky/info.log&lt;/code&gt;&lt;/li&gt;&lt;/ul&gt;&lt;h3&gt;八、第六步：HTTPS 证书&lt;/h3&gt;&lt;h4&gt;为什么必须配证书&lt;/h4&gt;&lt;ul&gt;&lt;li&gt;Lucky 默认自签证书（CN=Lucky），浏览器会报&quot;您的连接不是私密连接&quot;&lt;/li&gt;&lt;li&gt;正式证书（ZeroSSL/Let&apos;s Encrypt）→ 地址栏绿锁，无警告&lt;/li&gt;&lt;/ul&gt;&lt;h4&gt;证书申请（Lucky 后台操作）&lt;/h4&gt;&lt;p&gt;Lucky 内置 ACME 客户端，支持 Let&apos;s Encrypt / ZeroSSL：&lt;/p&gt;&lt;ol&gt;&lt;li&gt;Lucky 后台 → SSL 证书模块&lt;/li&gt;&lt;li&gt;添加证书 → 选择 ACME → 填域名 &lt;code&gt;overleaf.epochwl.top&lt;/code&gt;&lt;/li&gt;&lt;li&gt;&lt;strong&gt;验证方式&lt;/strong&gt;：&lt;ul&gt;&lt;li&gt;&lt;strong&gt;HTTP-01&lt;/strong&gt;：需要 80 端口（Lucky 需开 80 监听 + 运营商允许 80 入站）——国内宽带 80 常被封，&lt;strong&gt;不推荐&lt;/strong&gt;&lt;/li&gt;&lt;li&gt;&lt;strong&gt;DNS-01&lt;/strong&gt;：通过 DNS 服务商 API 验证，&lt;strong&gt;推荐&lt;/strong&gt;。域名在阿里云就用阿里云 AccessKey，Lucky 的 DDNS 模块有现成凭据可复用&lt;/li&gt;&lt;/ul&gt;&lt;/li&gt;&lt;li&gt;申请成功后把证书绑定到反代规则&lt;/li&gt;&lt;/ol&gt;&lt;h4&gt;验证证书&lt;/h4&gt;&lt;pre&gt;&lt;code&gt;# 看证书信息
echo | openssl s_client -connect &quot;[你的IPv6地址]:443&quot; -servername overleaf.epochwl.top 2&amp;gt;/dev/null \
  | openssl x509 -noout -subject -issuer -dates

# 完整访问测试（-k 表示跳过校验，验证链路）
curl -skL https://overleaf.epochwl.top/ -o /dev/null -w &quot;%{http_code} %{url_effective}\n&quot;
# 期望：200 https://overleaf.epochwl.top/login

# 严格校验（验证证书真的有效）
curl -sL https://overleaf.epochwl.top/ -o /dev/null -w &quot;%{http_code}\n&quot;
# 期望：200 或 302（无证书错误）&lt;/code&gt;&lt;/pre&gt;&lt;blockquote&gt;&lt;p&gt;⚠️ &lt;strong&gt;坑 #8&lt;/strong&gt;：ZeroSSL/Let&apos;s Encrypt 证书有效期 &lt;strong&gt;90 天&lt;/strong&gt;，记得开自动续期或在到期前续期。&lt;/p&gt;&lt;/blockquote&gt;&lt;h3&gt;九、第七步：内外网登录问题修复&lt;/h3&gt;&lt;h4&gt;现象&lt;/h4&gt;&lt;ul&gt;&lt;li&gt;外网（HTTPS 域名）能登录&lt;/li&gt;&lt;li&gt;内网（&lt;a&gt;HTTP://NAS_IP:18080）登录失败，报&lt;/a&gt; &lt;strong&gt;&quot;Session error. Please check you have cookies enabled&quot;&lt;/strong&gt;&lt;/li&gt;&lt;/ul&gt;&lt;h4&gt;根因（非常隐蔽！）&lt;/h4&gt;&lt;p&gt;Overleaf 源码 &lt;code&gt;settings.js&lt;/code&gt; 里：&lt;/p&gt;&lt;pre&gt;&lt;code&gt;secureCookie: process.env.OVERLEAF_SECURE_COOKIE != null,&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;strong&gt;只要 &lt;/strong&gt;&lt;code&gt;&lt;strong&gt;OVERLEAF_SECURE_COOKIE&lt;/strong&gt;&lt;/code&gt;&lt;strong&gt; 环境变量存在（不管设 true 还是 false！），secureCookie 就是 true&lt;/strong&gt;！&lt;/p&gt;&lt;p&gt;secureCookie=true 时：&lt;/p&gt;&lt;ul&gt;&lt;li&gt;cookie 带 &lt;code&gt;Secure&lt;/code&gt; 标志 → 浏览器&lt;strong&gt;只在 HTTPS 下发送&lt;/strong&gt;&lt;/li&gt;&lt;li&gt;内网用 &lt;code&gt;http://192.168.10.100:18080&lt;/code&gt; 访问 → HTTP 不发送 Secure cookie → 每次请求都无 session → Session error&lt;/li&gt;&lt;/ul&gt;&lt;h4&gt;解决方案&lt;/h4&gt;&lt;p&gt;&lt;strong&gt;必须整行注释/删除 &lt;/strong&gt;&lt;code&gt;&lt;strong&gt;OVERLEAF_SECURE_COOKIE&lt;/strong&gt;&lt;/code&gt;&lt;strong&gt; 变量&lt;/strong&gt;，而不是设为 false：&lt;/p&gt;&lt;pre&gt;&lt;code&gt;# variables.env 中：
# OVERLEAF_SECURE_COOKIE=true    ← 删掉或注释整行
# 千万不能写成 OVERLEAF_SECURE_COOKIE=false（等于没改，还是会 Secure！）&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;改完删容器重建：&lt;/p&gt;&lt;pre&gt;&lt;code&gt;docker rm -f sharelatex &amp;amp;&amp;amp; bash bin/up -d&lt;/code&gt;&lt;/pre&gt;&lt;h4&gt;验证内外网都能登录&lt;/h4&gt;&lt;pre&gt;&lt;code&gt;# 容器内模拟内网（无代理头）访问
curl -s -c /tmp/c.txt http://localhost:80/login -D - -o /dev/null | grep -i set-cookie
# 期望：set-cookie 不含 Secure

# 模拟外网（带 X-Forwarded-Proto: https）访问
curl -s -c /tmp/c2.txt -H &quot;X-Forwarded-Proto: https&quot; http://localhost:80/login -D - -o /dev/null | grep -i set-cookie
# 期望：set-cookie 正常&lt;/code&gt;&lt;/pre&gt;&lt;h3&gt;十、踩坑全记录（速查表）&lt;/h3&gt;&lt;figure&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;1&lt;/td&gt;&lt;td&gt;镜像拉取慢&lt;/td&gt;&lt;td&gt;docker pull 超时&lt;/td&gt;&lt;td&gt;配国内镜像加速器&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;2&lt;/td&gt;&lt;td&gt;数据在默认目录&lt;/td&gt;&lt;td&gt;备份/管理不便&lt;/td&gt;&lt;td&gt;迁到 &lt;code&gt;/vol{N}/&amp;lt;uid&amp;gt;/docker/&lt;/code&gt;，改 overleaf.rc&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;3&lt;/td&gt;&lt;td&gt;改配置不生效&lt;/td&gt;&lt;td&gt;环境变量没变&lt;/td&gt;&lt;td&gt;&lt;strong&gt;必须删容器重建&lt;/strong&gt;，restart 不重读 env_file&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;4&lt;/td&gt;&lt;td&gt;&lt;strong&gt;宏包丢失&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;重建后 ctexart.cls not found&lt;/td&gt;&lt;td&gt;&lt;strong&gt;texlive 目录持久化挂载&lt;/strong&gt;到宿主机&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;5&lt;/td&gt;&lt;td&gt;&lt;strong&gt;tlmgr 卡死&lt;/strong&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;6&lt;/td&gt;&lt;td&gt;&lt;strong&gt;CE 无注册页&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;无法自助注册&lt;/td&gt;&lt;td&gt;MongoDB 直插 users 集合&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;7&lt;/td&gt;&lt;td&gt;&lt;strong&gt;SECURE_COOKIE 坑&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;内网 Session error&lt;/td&gt;&lt;td&gt;整行注释该变量（设 false 无效）&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;8&lt;/td&gt;&lt;td&gt;证书 90 天&lt;/td&gt;&lt;td&gt;到期访问失败&lt;/td&gt;&lt;td&gt;开自动续期&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;9&lt;/td&gt;&lt;td&gt;Lucky 443 全挂&lt;/td&gt;&lt;td&gt;bad record MAC&lt;/td&gt;&lt;td&gt;检查反代规则证书/端口配置&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;10&lt;/td&gt;&lt;td&gt;外网打不开&lt;/td&gt;&lt;td&gt;只能局域网访问&lt;/td&gt;&lt;td&gt;NAS 无公网 IPv4，域名只有 IPv6；用支持 IPv6 的网络或内网穿透&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/figure&gt;&lt;h3&gt;十一、日常运维要点&lt;/h3&gt;&lt;h4&gt;1. 备份&lt;/h4&gt;&lt;pre&gt;&lt;code&gt;# 数据目录（项目/用户/数据库）是重中之重
/vol1/1000/docker/overleaf/{overleaf,mongo,redis}
# texlive 宏包可备份可重装（重装用清华源很快）&lt;/code&gt;&lt;/pre&gt;&lt;h4&gt;2. 常用命令&lt;/h4&gt;&lt;pre&gt;&lt;code&gt;# 查看状态
docker ps | grep -E &quot;sharelatex|mongo|redis&quot;
docker logs sharelatex --tail 50

# 停止/启动（overleaf-toolkit）
cd overleaf-toolkit
bash bin/stop
bash bin/up -d

# 进容器
docker exec -it sharelatex bash&lt;/code&gt;&lt;/pre&gt;&lt;h4&gt;3. 容器重建恢复清单（重要！）&lt;/h4&gt;&lt;p&gt;如果需要重建 sharelatex 容器，按此顺序：&lt;/p&gt;&lt;pre&gt;&lt;code&gt;# 1. 确认挂载还在（texlive 持久化）
docker inspect sharelatex | grep -A2 texlive
# 期望：/vol1/1000/docker/overleaf/texlive -&amp;gt; /usr/local/texlive

# 2. 重建
docker rm -f sharelatex &amp;amp;&amp;amp; cd overleaf-toolkit &amp;amp;&amp;amp; bash bin/up -d

# 3. 验证宏包还在
docker exec sharelatex kpsewhich ctexart.cls
# 期望：有输出（说明持久化生效，宏包没丢）

# 4. 如果宏包丢了（早期没持久化的版本）：
bash /vol2/@apphome/hermes-agent/data/workspace/reinstall_tex_pkgs.sh&lt;/code&gt;&lt;/pre&gt;&lt;h4&gt;4. 新增用户（给协作者开账号）&lt;/h4&gt;&lt;p&gt;复用上面的 MongoDB 直插脚本，改 &lt;code&gt;email&lt;/code&gt;/&lt;code&gt;password&lt;/code&gt;/&lt;code&gt;isAdmin: false&lt;/code&gt; 即可。&lt;/p&gt;&lt;hr /&gt;&lt;h3&gt;附录：完整部署命令速览&lt;/h3&gt;&lt;pre&gt;&lt;code&gt;# ========== 1. 部署 ==========
git clone https://github.com/overleaf/toolkit.git
cd toolkit
cp config/overleaf.rc.example config/overleaf.rc
cp config/variables.env.example config/variables.env
# 编辑 config/overleaf.rc     → 数据路径
# 编辑 config/variables.env  → SITE_URL / ADMIN_EMAIL / BEHIND_PROXY=true
bash bin/up -d

# ========== 2. 数据迁移 ==========
bash bin/stop
mkdir -p /vol1/1000/docker/overleaf/
mv data/{overleaf,mongo,redis,git-bridge} /vol1/1000/docker/overleaf/
# 改 overleaf.rc 路径
docker rm -f sharelatex mongo redis &amp;amp;&amp;amp; bash bin/up -d

# ========== 3. texlive 持久化 ==========
mkdir -p /vol1/1000/docker/overleaf/texlive
docker cp sharelatex:/usr/local/texlive/. /vol1/1000/docker/overleaf/texlive/
# 改 docker-compose.base.yml 加挂载
docker rm -f sharelatex &amp;amp;&amp;amp; bash bin/up -d

# ========== 4. 宏包安装（清华源） ==========
tlmgr option repository https://mirrors.tuna.tsinghua.edu.cn/CTAN/systems/texlive/tlnet
tlmgr install ctex xecjk fontspec float needspace caption booktabs \
              hyperref amsmath geometry listings biblatex natbib tikz pgf gbt7714
mktexlsr

# ========== 5. 建账号（MongoDB 直插） ==========
# 见上文 Node 脚本

# ========== 6. Lucky 反代 + 证书 ==========
# Lucky 后台（16666）操作，见上文

# ========== 7. 验证 ==========
curl -skL https://overleaf.epochwl.top/ -o /dev/null -w &quot;%{http_code} %{url_effective}\n&quot;
# 期望：200 https://overleaf.epochwl.top/login&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;em&gt;本文档基于实际部署过程，由deepseek v4 flash根据聊天记录整理，环境：fnOS NAS，Overleaf 6.2.2 CE，TeX Live 2026。&lt;/em&gt;&lt;/p&gt;&lt;figure&gt;&lt;img src=&quot;https://huowenlong.com/trilium-assets/KgAdONn6xKlf/e30bfdabe1b4fc53.png&quot; width=&quot;2836&quot; height=&quot;1820&quot; /&gt;&lt;/figure&gt;&lt;figure&gt;&lt;img src=&quot;https://huowenlong.com/trilium-assets/KgAdONn6xKlf/d66c6ac8761cb981.png&quot; width=&quot;2000&quot; height=&quot;1290&quot; /&gt;&lt;/figure&gt;</content:encoded></item><item><title>擂辣椒烧排骨初尝</title><link>https://huowenlong.com/blog/n3ke85XE28js/</link><guid isPermaLink="true">https://huowenlong.com/blog/n3ke85XE28js/</guid><description>一次擂辣椒烧排骨的完整实操：食材用量、排骨焯水、螺丝椒处理、姜蒜豆豉煸香，再加黄酒与清水烧到软嫩，并附上火候判断和口味补救的细节，适合第一次下厨的人照做。</description><pubDate>Sun, 02 Aug 2026 06:22:00 GMT</pubDate><content:encoded>&lt;p&gt; &lt;/p&gt;&lt;h3&gt;一、食材准备&lt;/h3&gt;&lt;h4&gt;1. 主料&lt;/h4&gt;&lt;figure&gt;&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;食材&lt;/th&gt;&lt;th&gt;&lt;p&gt;用量&lt;/p&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;p&gt;500g&lt;/p&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;p&gt;400g&lt;/p&gt;&lt;/td&gt;&lt;td&gt;比普通青椒更香，适合做擂辣椒&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/figure&gt;&lt;h4&gt;2. 小料和香料&lt;/h4&gt;&lt;figure&gt;&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;食材&lt;/th&gt;&lt;th&gt;&lt;p&gt;用量&lt;/p&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;p&gt;10g&lt;/p&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;p&gt;25g&lt;/p&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;p&gt;40g&lt;/p&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;p&gt;20g&lt;/p&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;p&gt;1个&lt;/p&gt;&lt;/td&gt;&lt;td&gt;直接用&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/figure&gt;&lt;h4&gt;3. 调味料&lt;/h4&gt;&lt;figure&gt;&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;调味料&lt;/th&gt;&lt;th&gt;&lt;p&gt;用量&lt;/p&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;p&gt;20g&lt;/p&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;p&gt;20g&lt;/p&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;p&gt;6g&lt;/p&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;p&gt;3g&lt;/p&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;p&gt;3g&lt;/p&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;p&gt;适量&lt;/p&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;p&gt;约600ml左右&lt;/p&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;p&gt;少量/适量&lt;/p&gt;&lt;/td&gt;&lt;td&gt;给辣椒补底味，注意后面还有生抽蚝油，别太多&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/figure&gt;&lt;blockquote&gt;&lt;p&gt;如果家里没有厨房秤，可以粗略理解为：生抽约2汤勺、蚝油约1大勺、老抽半汤勺、白糖小半勺、鸡精小半勺。口味淡的人可以先少放，最后尝味再补。&lt;/p&gt;&lt;/blockquote&gt;&lt;h3&gt;二、开始制作&lt;/h3&gt;&lt;h3&gt;Step 1：排骨焯水，去掉血水&lt;/h3&gt;&lt;p&gt;排骨买回来后先用清水浸泡一会儿，之后进行焯水。&lt;/p&gt;&lt;figure&gt;&lt;img src=&quot;https://huowenlong.com/trilium-assets/n3ke85XE28js/b18c656bfe297ca2.jpg&quot; width=&quot;2000&quot; height=&quot;1500&quot; /&gt;&lt;/figure&gt;&lt;h3&gt;Step 2：处理螺丝椒和小料&lt;/h3&gt;&lt;p&gt;螺丝椒洗净后去蒂，用刀拍一下或者切成大段。不要切太碎，太碎炒完容易没口感。&lt;/p&gt;&lt;p&gt;姜切片，蒜切片（这里我用的葱），小米辣切圈，豆豉稍微剁几下，八角准备1个。&lt;/p&gt;&lt;figure&gt;&lt;img src=&quot;https://huowenlong.com/trilium-assets/n3ke85XE28js/b19856f78dcf4da2.jpg&quot; width=&quot;2000&quot; height=&quot;1500&quot; /&gt;&lt;/figure&gt;&lt;h3&gt;Step 3：锅中加油，煸香姜和八角&lt;/h3&gt;&lt;p&gt;锅烧热后倒入适量食用油，先放入姜和八角，小火到中火炒出香味。&lt;/p&gt;&lt;p&gt;&lt;strong&gt;做到什么状态？&lt;/strong&gt; 能闻到姜香，姜边缘微微变黄即可，不要炒糊。&lt;/p&gt;&lt;h3&gt;Step 4：下排骨，不用一直猛炒&lt;/h3&gt;&lt;figure&gt;&lt;img src=&quot;https://huowenlong.com/trilium-assets/n3ke85XE28js/a77154600b7ad28d.jpg&quot; width=&quot;1706&quot; height=&quot;1279&quot; /&gt;&lt;/figure&gt;&lt;p&gt;把沥干的排骨倒入锅中，简单翻动，让排骨表面受热。这里不用像炒肉片那样一直猛炒，主要是让排骨在锅里先定型、出香味。&lt;/p&gt;&lt;p&gt;&lt;strong&gt;提示：&lt;/strong&gt; 如果排骨刚下锅水汽比较大，别慌，这是正常的。保持中火，让水汽慢慢散掉。&lt;/p&gt;&lt;h3&gt;Step 5：加入黄酒和清水，烧到排骨软嫩&lt;/h3&gt;&lt;ul&gt;&lt;li&gt;黄酒约10g，可以用料酒代替&lt;/li&gt;&lt;li&gt;清水约600ml&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;沿锅边淋入黄酒去腥，再加入清水或开水。大火烧开后转中小火，把排骨烧到软嫩。&lt;/p&gt;&lt;p&gt;&lt;strong&gt;时间怎么判断？&lt;/strong&gt; 一般小块排骨烧20～30分钟左右，具体看排骨大小。能用筷子比较轻松插进肉里，就差不多了。&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;/ul&gt;&lt;figure&gt;&lt;img src=&quot;https://huowenlong.com/trilium-assets/n3ke85XE28js/8ed97efa711426ee.jpg&quot; width=&quot;2000&quot; height=&quot;1500&quot; /&gt;&lt;/figure&gt;&lt;h3&gt;Step 6：单独炒螺丝椒，做出“擂辣椒”的香味&lt;/h3&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;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;h3&gt;Step 7：加入豆豉、小米辣和蒜片&lt;/h3&gt;&lt;p&gt;把豆豉、小米辣和蒜片倒入锅中，和螺丝椒一起翻炒。&lt;/p&gt;&lt;p&gt;&lt;strong&gt;做到什么状态？&lt;/strong&gt; 蒜片香味出来，豆豉香味出来，小米辣变得油亮。注意火不要太猛，蒜片糊了会发苦。&lt;/p&gt;&lt;h3&gt;Step 8：倒回排骨和汤汁&lt;/h3&gt;&lt;p&gt;把刚才烧好的排骨连同汤汁倒回锅里，和辣椒一起翻炒。&lt;/p&gt;&lt;p&gt;这一步要让辣椒吸收排骨汤汁，排骨也裹上辣椒和豆豉的香味。&lt;/p&gt;&lt;h3&gt;Step 9：正式调味&lt;/h3&gt;&lt;p&gt;加入：&lt;/p&gt;&lt;ul&gt;&lt;li&gt;生抽20g&lt;/li&gt;&lt;li&gt;蚝油20g&lt;/li&gt;&lt;li&gt;老抽6g&lt;/li&gt;&lt;li&gt;白糖3g&lt;/li&gt;&lt;li&gt;鸡精3g&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;先沿锅边淋入生抽，这样香味会更明显。老抽主要是上色，别手抖放太多，不然颜色会太深。翻炒均匀后，开大火稍微收汁。&lt;/p&gt;&lt;p&gt;&lt;strong&gt;最终状态：&lt;/strong&gt; 汤汁变浓，能挂在排骨和辣椒上，但锅底仍保留一点点汁。&lt;/p&gt;&lt;figure&gt;&lt;img src=&quot;https://huowenlong.com/trilium-assets/n3ke85XE28js/48c171c6a28226d4.jpg&quot; width=&quot;2000&quot; height=&quot;1500&quot; /&gt;&lt;/figure&gt;&lt;h3&gt;三、经验总结&lt;/h3&gt;&lt;h4&gt;1. 排骨要切小&lt;/h4&gt;&lt;p&gt;小块排骨更容易熟，也更容易入味。大块排骨对新手不友好，容易外面有味、里面还淡。&lt;/p&gt;&lt;h4&gt;2. 螺丝椒要单独炒&lt;/h4&gt;&lt;p&gt;因为直接和排骨一起炖，辣椒会变软烂，香味也不够。先单独按压炒出虎皮焦香，最后再和排骨合炒，味道会更有层次。&lt;/p&gt;&lt;h4&gt;3. 盐少放或不放&lt;/h4&gt;&lt;p&gt;可以少量放在炒辣椒阶段，主要是给辣椒一点底味。但因为后面有生抽、蚝油、鸡精，所以盐一定要少。最终我做出来的感觉有一点点咸，没有放盐的。&lt;/p&gt;&lt;h4&gt;4. 放油要注意&lt;/h4&gt;&lt;p&gt;需要把锅中的水烧干，之后再放油，不然就像我一样当时油乱溅，有点吓人👻。&lt;/p&gt;&lt;h4&gt;5. 汤不要收干&lt;/h4&gt;&lt;p&gt;不要完全收干。留一点浓汤汁，拌米饭才是这道菜的精华。&lt;/p&gt;&lt;h3&gt;四、参考资料&lt;/h3&gt;&lt;p&gt;&lt;a href=&quot;https://v.douyin.com/xj1BOVEJV-Y/&quot;&gt;【村驴的作品】好吃到跺脚的擂辣椒烧排骨&lt;/a&gt;&lt;/p&gt;&lt;blockquote&gt;&lt;p&gt;总的来说，这次味道还可以，但因为第一次做，经验技巧不足，做不到吃了还想吃，下次再接再厉。😗&lt;/p&gt;&lt;/blockquote&gt;&lt;p&gt; &lt;/p&gt;</content:encoded></item><item><title>从数位板到Inkscape：零基础UI图标全流程</title><link>https://huowenlong.com/blog/ykDuL5tkBQnG/</link><guid isPermaLink="true">https://huowenlong.com/blog/ykDuL5tkBQnG/</guid><description>零基础 UI 图标全流程：在 Windows 上装好 Wacom CTL-472 驱动、调顺笔尖映射与压感，再用 Inkscape 的基础形状搭出“晴间多云”图标与天气卡片，导出可编辑 SVG 交给程序使用。</description><pubDate>Sat, 01 Aug 2026 08:00:53 GMT</pubDate><content:encoded>&lt;h2&gt;从数位板到 Inkscape：零基础画出第一套 UI 小组件&lt;/h2&gt;&lt;p&gt;如果你刚拿到数位板，最容易掉进两个坑：一是还没把驱动和压感调顺，就怀疑自己不会画；二是一打开 Inkscape，看到满屏工具便想一次学完。&lt;/p&gt;&lt;p&gt;其实我们只需要完成一条很短的工作流：&lt;/p&gt;&lt;ol&gt;&lt;li&gt;让 Windows 正确认出数位板；&lt;/li&gt;&lt;li&gt;把笔尖、映射和压感调到舒服；&lt;/li&gt;&lt;li&gt;在 Inkscape 中用基础形状搭出图标和卡片；&lt;/li&gt;&lt;li&gt;保存可编辑的 SVG，再导出给程序使用的 SVG 或 PNG；&lt;/li&gt;&lt;li&gt;把资源交给 Xcode，后续 Swift 编程只做简短衔接。&lt;/li&gt;&lt;/ol&gt;&lt;p&gt;本文会用一个“晴间多云”图标和天气卡片贯穿全程。完成后的目标大致如下：&lt;/p&gt;&lt;figure&gt;&lt;img src=&quot;https://huowenlong.com/trilium-assets/ykDuL5tkBQnG/d22088c3eb8a9fe5.png&quot; width=&quot;1760&quot; height=&quot;930&quot; /&gt;&lt;/figure&gt;&lt;h3&gt;一、开始前准备什么&lt;/h3&gt;&lt;h4&gt;硬件与账号&lt;/h4&gt;&lt;ul&gt;&lt;li&gt;一块 Wacom 数位板或数位屏；&lt;/li&gt;&lt;li&gt;原装或确认支持数据传输的 USB 线；&lt;/li&gt;&lt;li&gt;Windows 管理员权限；&lt;/li&gt;&lt;li&gt;稳定网络，用于下载驱动与 Inkscape；&lt;/li&gt;&lt;li&gt;最好准备鼠标。驱动异常时，鼠标能帮助你继续操作。&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;先翻到设备背面或查看包装，本数位板型号为&lt;strong&gt;CTL-472&lt;/strong&gt;。&lt;/p&gt;&lt;figure&gt;&lt;img src=&quot;https://huowenlong.com/trilium-assets/ykDuL5tkBQnG/e4795ae62a00e2eb.jpg&quot; width=&quot;940&quot; height=&quot;750&quot; /&gt;&lt;/figure&gt;&lt;h4&gt;软件&lt;/h4&gt;&lt;ul&gt;&lt;li&gt;Wacom Windows 驱动：&lt;a href=&quot;https://www.wacom.com/zh-cn/support/product-support/drivers&quot;&gt;Wacom 官方驱动页面&lt;/a&gt;&lt;/li&gt;&lt;li&gt;Inkscape：&lt;a href=&quot;https://inkscape.org/zh-hans/&quot;&gt;Inkscape 中文官网&lt;/a&gt;&lt;/li&gt;&lt;/ul&gt;&lt;h3&gt;二、在 Windows 安装 Wacom 驱动&lt;/h3&gt;&lt;h4&gt;下载正确的 Windows 版本&lt;/h4&gt;&lt;p&gt;打开 &lt;a href=&quot;https://www.wacom.com/zh-CN/support/product-support/drivers#%23&quot;&gt;Wacom驱动页面&lt;/a&gt;，搜索CTL-472，下载其对应的驱动。若你使用常见的 Intel 或 AMD 电脑，选择 Windows x64；如果是搭载 Snapdragon 等处理器的 Windows on ARM 电脑，则确认页面明确支持 ARM64。&lt;/p&gt;&lt;figure&gt;&lt;img src=&quot;https://huowenlong.com/trilium-assets/ykDuL5tkBQnG/fbac8cfedf4b4be1.png&quot; width=&quot;2000&quot; height=&quot;1495&quot; /&gt;&lt;/figure&gt;&lt;h4&gt;运行安装程序并重启&lt;/h4&gt;&lt;p&gt;双击下载的安装程序，看到 Windows 用户账户控制提示后确认发布者，再选择“是”。按照向导完成安装。安装器若提示重启，请重启，不要跳过。&lt;/p&gt;&lt;p&gt;重启看似麻烦，但驱动需要加载系统服务。很多“光标能动、压感却没有”的问题，只是因为安装后没有重启。&lt;/p&gt;&lt;h4&gt;打开 Wacom Center，确认设备在线&lt;/h4&gt;&lt;p&gt;在开始菜单搜索 &lt;code&gt;Wacom Center&lt;/code&gt;。正常情况下，左侧会出现你的设备，以及压感笔、快捷键、触摸或显示设置等入口。不同产品看到的项目不完全相同，这是正常的。&lt;/p&gt;&lt;figure&gt;&lt;img src=&quot;https://huowenlong.com/trilium-assets/ykDuL5tkBQnG/101dc8fa14360b86.png&quot; width=&quot;2027&quot; height=&quot;1133&quot; /&gt;&lt;/figure&gt;&lt;p&gt;先完成三个检查：&lt;/p&gt;&lt;ol&gt;&lt;li&gt;设备名称是否出现；&lt;/li&gt;&lt;li&gt;笔尖悬停在板面上方时，屏幕光标是否移动；&lt;/li&gt;&lt;li&gt;Wacom Center 的“设置 / 一般设置”里是否有驱动或固件更新。&lt;/li&gt;&lt;/ol&gt;&lt;p&gt;Wacom 官方说明中也强调：检查设备更新时，产品必须连接电脑。若有固件更新，保持线缆稳定，更新期间不要拔线。&lt;/p&gt;&lt;h4&gt;设置屏幕映射&lt;/h4&gt;&lt;p&gt;数位板通常采用“绝对定位”：板面左上角对应屏幕左上角，板面右下角对应屏幕右下角。它和鼠标不断累积位移的感觉不同，刚开始不习惯很正常。&lt;/p&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;li&gt;暂时不要给所有快捷键分配复杂命令。&lt;/li&gt;&lt;/ul&gt;&lt;h4&gt;设置笔尖与按键&lt;/h4&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;code&gt;Ctrl + Z&lt;/code&gt; 或中键拖动画布；&lt;/li&gt;&lt;li&gt;橡皮擦端：若设备支持，再设置为橡皮擦。&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;先求稳定，再求效率。等你真正发现“某个操作每天要按一百次”，再把它放到快捷键上。&lt;/p&gt;&lt;p&gt;Wacom Center 的具体布局会因设备而异，但基本结构都是：左侧选设备和压感笔，中间调笔尖灵敏度，右侧或高级面板调双击间距、压力曲线及 Windows Ink。&lt;/p&gt;&lt;h4&gt;用 3 分钟调好压感&lt;/h4&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;figure&gt;&lt;img src=&quot;https://huowenlong.com/trilium-assets/ykDuL5tkBQnG/09b5f96f8359ae88.png&quot; width=&quot;1788&quot; height=&quot;694&quot; /&gt;&lt;/figure&gt;&lt;p&gt;如果你手比较轻，把笔尖灵敏度稍微调“柔和”；如果还没碰到板面就容易误触，或轻微动作反应过度，则稍微调“坚硬”。每次只改一点，然后重新画线。&lt;/p&gt;&lt;p&gt;Wacom 官方帮助特别提到：双击间距过大，可能在部分图形应用中造成下笔或拖动延迟。出现延迟时，先减小双击间距。Windows Ink 在 Windows 驱动中默认开启，第一次测试建议保持开启；只有明确遇到兼容性问题时才切换它，而且一次只改一个选项，改完后重启 Inkscape 再比较。关闭 Windows Ink 可能让某些软件失去压感，所以不要把“关闭它”当成通用答案。&lt;/p&gt;&lt;figure&gt;&lt;img src=&quot;https://huowenlong.com/trilium-assets/ykDuL5tkBQnG/190a8ae0a05f1d67.png&quot; width=&quot;1130&quot; height=&quot;1322&quot; /&gt;&lt;/figure&gt;&lt;h4&gt;安装阶段的快速排错&lt;/h4&gt;&lt;p&gt;&lt;strong&gt;Wacom Center 找不到设备&lt;/strong&gt;&lt;/p&gt;&lt;ul&gt;&lt;li&gt;换一个电脑自带的 USB 接口，先不要经过扩展坞；&lt;/li&gt;&lt;li&gt;换一条确认能传数据的线；&lt;/li&gt;&lt;li&gt;打开 Windows“设备管理器”，看连接设备时列表是否有变化；&lt;/li&gt;&lt;li&gt;重启电脑后再试；&lt;/li&gt;&lt;li&gt;仍无效时，按 Wacom 官方支持流程重新安装驱动。&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;&lt;strong&gt;光标会动，但没有压感&lt;/strong&gt;&lt;/p&gt;&lt;ul&gt;&lt;li&gt;先在 Wacom Center 测试区确认是否有粗细变化；&lt;/li&gt;&lt;li&gt;Wacom Center 有压感、Inkscape 没有，通常是应用内工具设置问题；&lt;/li&gt;&lt;li&gt;两边都没有压感，优先检查驱动、线缆和笔，不要先折腾 Inkscape。&lt;/li&gt;&lt;/ul&gt;&lt;h3&gt;三、安装并认识 Inkscape&lt;/h3&gt;&lt;h4&gt;下载 Windows 安装包&lt;/h4&gt;&lt;p&gt;进入 Inkscape 官网的 Windows 下载页。普通 Intel / AMD 电脑选 64 位版本；Windows on ARM 设备选官方提供的 ARM 版本。零基础用户使用正常安装版即可，便携压缩包更适合知道自己为什么需要它的人。&lt;/p&gt;&lt;p&gt;安装时保留默认组件通常就够了。第一次启动后，如果界面不是中文，可以在 &lt;code&gt;Edit（编辑） → Preferences（首选项） → Interface（界面） → Language（语言）&lt;/code&gt; 中选择简体中文，然后重启 Inkscape。菜单名称可能随翻译版本略有变化。&lt;/p&gt;&lt;h4&gt;只认识六个区域&lt;/h4&gt;&lt;p&gt;初次打开，界面像驾驶舱。暂时只看这六处：&lt;/p&gt;&lt;ol&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;/ol&gt;&lt;figure&gt;&lt;img src=&quot;https://huowenlong.com/trilium-assets/ykDuL5tkBQnG/ba7439591ceff106.png&quot; width=&quot;1934&quot; height=&quot;1212&quot; /&gt;&lt;/figure&gt;&lt;p&gt;几个最常用的快捷键：&lt;/p&gt;&lt;figure&gt;&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;操作&lt;/th&gt;&lt;th&gt;&lt;p&gt;快捷键&lt;/p&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;p&gt;&lt;code&gt;S&lt;/code&gt; 或 &lt;code&gt;F1&lt;/code&gt;&lt;/p&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;节点工具&lt;/td&gt;&lt;td&gt;&lt;p&gt;&lt;code&gt;N&lt;/code&gt; 或 &lt;code&gt;F2&lt;/code&gt;&lt;/p&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;矩形工具&lt;/td&gt;&lt;td&gt;&lt;p&gt;&lt;code&gt;R&lt;/code&gt; 或 &lt;code&gt;F4&lt;/code&gt;&lt;/p&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;椭圆工具&lt;/td&gt;&lt;td&gt;&lt;p&gt;&lt;code&gt;E&lt;/code&gt; 或 &lt;code&gt;F5&lt;/code&gt;&lt;/p&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;钢笔 / 贝塞尔工具&lt;/td&gt;&lt;td&gt;&lt;p&gt;&lt;code&gt;B&lt;/code&gt; 或 &lt;code&gt;Shift + F6&lt;/code&gt;&lt;/p&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;文字工具&lt;/td&gt;&lt;td&gt;&lt;p&gt;&lt;code&gt;T&lt;/code&gt; 或 &lt;code&gt;F8&lt;/code&gt;&lt;/p&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;填充与描边&lt;/td&gt;&lt;td&gt;&lt;p&gt;&lt;code&gt;Shift + Ctrl + F&lt;/code&gt;&lt;/p&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;文档属性&lt;/td&gt;&lt;td&gt;&lt;p&gt;&lt;code&gt;Shift + Ctrl + D&lt;/code&gt;&lt;/p&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;导出&lt;/td&gt;&lt;td&gt;&lt;p&gt;&lt;code&gt;Shift + Ctrl + E&lt;/code&gt;&lt;/p&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;撤销&lt;/td&gt;&lt;td&gt;&lt;p&gt;&lt;code&gt;Ctrl + Z&lt;/code&gt;&lt;/p&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;复制 / 原位粘贴副本&lt;/td&gt;&lt;td&gt;&lt;p&gt;&lt;code&gt;Ctrl + D&lt;/code&gt;&lt;/p&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;编组 / 取消编组&lt;/td&gt;&lt;td&gt;&lt;p&gt;&lt;code&gt;Ctrl + G&lt;/code&gt; / &lt;code&gt;Shift + Ctrl + G&lt;/code&gt;&lt;/p&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/figure&gt;&lt;p&gt;快捷键不是考试内容。用到一次记一次即可。&lt;/p&gt;&lt;h4&gt;先保存，再开始画&lt;/h4&gt;&lt;p&gt;新建文档后立刻按 &lt;code&gt;Ctrl + Shift + S&lt;/code&gt;，保存为 &lt;code&gt;weather-card-master.svg&lt;/code&gt;。Inkscape 的 SVG 是可继续编辑的源文件，相当于设计稿母版。之后每完成一个小阶段就按 &lt;code&gt;Ctrl + S&lt;/code&gt;。&lt;/p&gt;&lt;p&gt;不要只留下 PNG。PNG 像一张照片，导出后很难再改节点、圆角和颜色；SVG 才保留对象结构。&lt;/p&gt;&lt;h4&gt;数位板在 Inkscape 里怎么测试&lt;/h4&gt;&lt;p&gt;选择“书法 / Calligraphy”工具，在上方控制栏开启压力选项，然后画几条从轻到重的线。Inkscape 本身支持利用数位笔压力和倾斜控制书法工具等工具的宽度与角度。&lt;/p&gt;&lt;p&gt;但要注意：做 UI 图标时，绝大多数形状应该用圆、矩形、路径和布尔运算精确搭建，而不是用压感笔徒手描最终边缘。数位板在这里更像一只自然、精细的鼠标：用来画草图、拉节点和调曲线；最终几何结构仍交给矢量工具。&lt;/p&gt;&lt;h3&gt;四、实战：画一个“晴间多云”图标&lt;/h3&gt;&lt;h4&gt;建立 64 × 64 像素页面&lt;/h4&gt;&lt;p&gt;打开 &lt;code&gt;文件 → 文档属性&lt;/code&gt;，把单位设为 &lt;code&gt;px&lt;/code&gt;，页面宽和高都设为 &lt;code&gt;64&lt;/code&gt;。这个 64 × 64 页面是练习画板；以后交付 SVG 时可以自由缩放，交付 PNG 时再按实际显示尺寸输出。&lt;/p&gt;&lt;p&gt;建议打开网格，或至少启用吸附到对象边界 / 节点。UI 图标很依赖对齐，但吸附过强也会妨碍微调。如果对象总被“吸走”，临时关闭吸附再移动。&lt;/p&gt;&lt;h4&gt; 用圆和短线搭太阳&lt;/h4&gt;&lt;p&gt;选择椭圆工具，按住 &lt;code&gt;Ctrl&lt;/code&gt; 拖出正圆。设置暖黄色填充，取消描边。再画一条短的圆头竖线作为光芒：&lt;/p&gt;&lt;ol&gt;&lt;li&gt;用钢笔工具点击起点和终点；&lt;/li&gt;&lt;li&gt;在“填充与描边”里取消填充，设置与太阳相同的描边色；&lt;/li&gt;&lt;li&gt;把描边端点设为圆头；&lt;/li&gt;&lt;li&gt;&lt;code&gt;Ctrl + D&lt;/code&gt; 复制，再旋转得到上下左右和四条斜向光芒。&lt;/li&gt;&lt;/ol&gt;&lt;p&gt;另一种更规整的方法，是使用细长圆角矩形做光芒，然后复制并旋转。对新手来说，两种都可以。&lt;/p&gt;&lt;h4&gt;用重叠形状搭云朵&lt;/h4&gt;&lt;p&gt;云朵可以由三个圆和一个圆角矩形组成：中间圆最大，两侧圆稍小，底部用圆角矩形托住。让这些形状稍微重叠。&lt;/p&gt;&lt;p&gt;全选云朵的几个形状，执行 &lt;code&gt;路径 → 并集&lt;/code&gt;（常用快捷键为 &lt;code&gt;Ctrl + +&lt;/code&gt;），它们会合并为一个路径。合并前最好先 &lt;code&gt;Ctrl + D&lt;/code&gt; 备份一份并移到页面外，避免之后想改比例却没有原始形状。&lt;/p&gt;&lt;p&gt;矢量图形中，“开放路径”和“闭合路径”很重要：闭合路径能稳定填色，开放路径更像一根线。云朵应该是闭合路径。&lt;/p&gt;&lt;figure&gt;&lt;img src=&quot;https://huowenlong.com/trilium-assets/ykDuL5tkBQnG/6774f7af13113485.png&quot; width=&quot;1002&quot; height=&quot;290&quot; /&gt;&lt;/figure&gt;&lt;h4&gt;用节点工具修轮廓&lt;/h4&gt;&lt;p&gt;选中云朵，切换节点工具。拖动节点会改变位置，拖动节点两侧的控制柄会改变曲线弧度。第一次调节点时遵守一个原则：能少加节点，就不要多加。节点越多，曲线越容易出现小鼓包。&lt;/p&gt;&lt;figure&gt;&lt;img src=&quot;https://huowenlong.com/trilium-assets/ykDuL5tkBQnG/908610e877fe0e72.png&quot; width=&quot;1184&quot; height=&quot;420&quot; /&gt;&lt;/figure&gt;&lt;h4&gt;叠放、对齐并编组&lt;/h4&gt;&lt;p&gt;把云朵放到太阳的右下方，让太阳露出一部分。选择太阳和云朵，打开 &lt;code&gt;对象 → 对齐与分布&lt;/code&gt;，检查它们的视觉位置。几何居中不一定等于看起来居中，小图标最终要靠眼睛做一点光学校正。&lt;/p&gt;&lt;p&gt;确认后按 &lt;code&gt;Ctrl + G&lt;/code&gt; 编组。编组不会把路径焊死，只是让多个对象作为整体移动。双击组或进入组内仍可编辑。&lt;/p&gt;&lt;figure&gt;&lt;img src=&quot;https://huowenlong.com/trilium-assets/ykDuL5tkBQnG/646f5274c1810f82.png&quot; width=&quot;364&quot; height=&quot;358&quot; /&gt;&lt;/figure&gt;&lt;figure&gt;&lt;img src=&quot;https://huowenlong.com/trilium-assets/ykDuL5tkBQnG/a786847b9b827fe0.png&quot; width=&quot;928&quot; height=&quot;410&quot; /&gt;&lt;/figure&gt;&lt;h4&gt;做小尺寸检查&lt;/h4&gt;&lt;p&gt;把缩放调到 100%，再把图标临时缩到 24 × 24 附近观察：&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;浅色背景和深色背景上是否都看得清。&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;UI 图标不是越细越高级。24 px 左右的小尺寸里，清楚比复杂重要。&lt;/p&gt;&lt;h3&gt;五、把图标放进天气卡片&lt;/h3&gt;&lt;p&gt;另存一个文件 &lt;code&gt;weather-card-master.svg&lt;/code&gt;，把页面改成 &lt;code&gt;320 × 180 px&lt;/code&gt;。&lt;/p&gt;&lt;h4&gt;画卡片底板&lt;/h4&gt;&lt;p&gt;用矩形工具画满页面，设置：&lt;/p&gt;&lt;ul&gt;&lt;li&gt;&lt;code&gt;X = 0&lt;/code&gt;，&lt;code&gt;Y = 0&lt;/code&gt;；&lt;/li&gt;&lt;li&gt;&lt;code&gt;W = 320&lt;/code&gt;，&lt;code&gt;H = 180&lt;/code&gt;；&lt;/li&gt;&lt;li&gt;圆角半径约 &lt;code&gt;24–28 px&lt;/code&gt;；&lt;/li&gt;&lt;li&gt;取消描边。&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;按 &lt;code&gt;G&lt;/code&gt; 使用渐变工具，从左上拖到右下，设置蓝紫到天蓝的渐变。渐变不要堆太多色标，两种颜色已经足够。&lt;/p&gt;&lt;h4&gt;放入天气图标&lt;/h4&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;li&gt;不要立刻加阴影，先靠颜色和尺寸建立层级。&lt;/li&gt;&lt;/ul&gt;&lt;h4&gt;添加设计稿文字&lt;/h4&gt;&lt;p&gt;用文字工具添加城市、日期、温度和天气状态。设计稿中可以使用系统常见字体，比如微软雅黑或思源黑体。&lt;/p&gt;&lt;p&gt;不过准备给 SwiftUI 使用时，建议把“24°”“上海”等动态文字留给代码，而不是烘焙进图片。原因很实际：&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;因此最终交付通常是：一个天气图标 SVG，必要时再加一张纯装饰背景；文字、圆角卡片和布局由 SwiftUI 完成。&lt;/p&gt;&lt;h3&gt;六、保存与导出：把设计稿变成开发资源&lt;/h3&gt;&lt;h4&gt;永远保留 SVG 母版&lt;/h4&gt;&lt;p&gt;先保存 Inkscape SVG 源文件，例如：&lt;/p&gt;&lt;pre&gt;&lt;code&gt;design/
├─ source/
│  ├─ weather-icon-master.svg
│  └─ weather-card-master.svg
└─ export/
   ├─ weather-partly-cloudy.svg
   ├─ weather-partly-cloudy.png
   ├─ weather-partly-cloudy@2x.png
   └─ weather-partly-cloudy@3x.png&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;交付给开发者的 SVG 可使用 &lt;code&gt;文件 → 另存副本&lt;/code&gt;，格式选择“纯 SVG / Plain SVG”。纯 SVG 会减少 Inkscape 专用元数据，更适合进入代码仓库。母版仍保留为 Inkscape SVG。&lt;/p&gt;&lt;p&gt;如果 SVG 含文字，而你确实要把文字作为图形交付，先复制一份，再执行 &lt;code&gt;路径 → 对象转路径&lt;/code&gt;。转路径后文字不依赖字体，但也不能再像普通文本那样编辑。动态 UI 文案仍不建议这样做。&lt;/p&gt;&lt;h4&gt;导出 PNG&lt;/h4&gt;&lt;p&gt;按 &lt;code&gt;Shift + Ctrl + E&lt;/code&gt; 打开导出面板。&lt;/p&gt;&lt;figure&gt;&lt;img src=&quot;https://huowenlong.com/trilium-assets/ykDuL5tkBQnG/8b22d893d53c1eee.png&quot; width=&quot;762&quot; height=&quot;1014&quot; /&gt;&lt;/figure&gt;&lt;p&gt;导出区域常见四种选择：&lt;/p&gt;&lt;ul&gt;&lt;li&gt;&lt;strong&gt;页面 Page&lt;/strong&gt;：严格按页面尺寸导出，最适合需要固定留白的 UI 图标；&lt;/li&gt;&lt;li&gt;&lt;strong&gt;绘图 Drawing&lt;/strong&gt;：包住文档里的所有对象；&lt;/li&gt;&lt;li&gt;&lt;strong&gt;选择 Selection&lt;/strong&gt;：只导出当前选中对象；&lt;/li&gt;&lt;li&gt;&lt;strong&gt;自定义 Custom&lt;/strong&gt;：手动输入区域。&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;这次图标选择“页面”，这样四周留白不会丢失。然后设置图片宽度。若应用中希望图标占 &lt;code&gt;24 pt&lt;/code&gt;：&lt;/p&gt;&lt;ul&gt;&lt;li&gt;1×：&lt;code&gt;24 × 24 px&lt;/code&gt;；&lt;/li&gt;&lt;li&gt;2×：&lt;code&gt;48 × 48 px&lt;/code&gt;；&lt;/li&gt;&lt;li&gt;3×：&lt;code&gt;72 × 72 px&lt;/code&gt;。&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;文件名可用：&lt;/p&gt;&lt;pre&gt;&lt;code&gt;weather-partly-cloudy.png
weather-partly-cloudy@2x.png
weather-partly-cloudy@3x.png&lt;/code&gt;&lt;/pre&gt;&lt;figure&gt;&lt;img src=&quot;https://huowenlong.com/trilium-assets/ykDuL5tkBQnG/36ba216d855b1f67.png&quot; width=&quot;940&quot; height=&quot;396&quot; /&gt;&lt;/figure&gt;&lt;h4&gt;透明背景还是白色背景&lt;/h4&gt;&lt;p&gt;图标通常需要透明背景。Inkscape 导出 PNG 时，页面空白区域默认可能是透明的；这不是导出失败。&lt;/p&gt;&lt;p&gt;如果确实需要白底，打开文档属性，修改页面背景色，并把 Alpha / A 值设为完全不透明。注意：给页面“看起来是白色”和“导出为不透明白底”不是同一件事。&lt;/p&gt;&lt;figure&gt;&lt;img src=&quot;https://huowenlong.com/trilium-assets/ykDuL5tkBQnG/35fe6441e4ec63ba.png&quot; width=&quot;962&quot; height=&quot;1094&quot; /&gt;&lt;/figure&gt;&lt;h4&gt;导出前的 30 秒检查&lt;/h4&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;SVG 中是否使用了不必要的模糊、复杂滤镜或外链图片；&lt;/li&gt;&lt;li&gt;PNG 的 1× / 2× / 3× 尺寸是否正确；&lt;/li&gt;&lt;li&gt;文件名是否稳定、可读；&lt;/li&gt;&lt;li&gt;母版和导出文件是否分开放置。&lt;/li&gt;&lt;/ul&gt;&lt;h3&gt;七、常见问题&lt;/h3&gt;&lt;h4&gt;导出的图标被裁掉&lt;/h4&gt;&lt;p&gt;通常是导出区域选成了“选择”或“绘图”，对象边缘又刚好贴住边界。UI 图标优先使用“页面”导出，并在页面内预留安全边距。&lt;/p&gt;&lt;h4&gt;导出的 PNG 很糊&lt;/h4&gt;&lt;p&gt;检查是否把 1× 图片放到了 2× 或 3× 屏幕位置。优先使用 SVG；必须使用 PNG 时，按实际显示尺寸提供对应倍率，不要在代码里把小图强行放大。&lt;/p&gt;&lt;h4&gt;SVG 到 Xcode 后显示异常&lt;/h4&gt;&lt;p&gt;先尝试：&lt;/p&gt;&lt;ol&gt;&lt;li&gt;另存为 Plain SVG；&lt;/li&gt;&lt;li&gt;把必须保留的文字转路径；&lt;/li&gt;&lt;li&gt;减少滤镜、遮罩、复杂混合模式；&lt;/li&gt;&lt;li&gt;确认 &lt;code&gt;viewBox&lt;/code&gt; 与页面边界正确；&lt;/li&gt;&lt;li&gt;仍不兼容时，改交单页矢量 PDF 或 PNG 多倍率资源。&lt;/li&gt;&lt;/ol&gt;&lt;h4&gt;数位笔在 Inkscape 里有延迟&lt;/h4&gt;&lt;p&gt;先在 Wacom Center 测试。若 Wacom Center 正常，再逐项检查：&lt;/p&gt;&lt;ul&gt;&lt;li&gt;双击间距是否过大；&lt;/li&gt;&lt;li&gt;Inkscape 是否打开了过重的滤镜或超复杂文件；&lt;/li&gt;&lt;li&gt;是否同时安装了会争夺输入设备的旧驱动；&lt;/li&gt;&lt;li&gt;切换 Windows Ink 前是否记录了原设置；&lt;/li&gt;&lt;li&gt;改完驱动选项后是否重启了 Inkscape。&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;不要同时改五个设置。一次改一个，才知道是哪一步有效。&lt;/p&gt;&lt;h3&gt;八、交给 Swift / SwiftUI：到这里一笔带过&lt;/h3&gt;&lt;p&gt;设计资源到这里已经完成。后续只需要把导出的 SVG 或 PNG 拖入 Xcode 的 &lt;code&gt;Assets.xcassets&lt;/code&gt;，把资源命名为 &lt;code&gt;weather-partly-cloudy&lt;/code&gt;，再在 SwiftUI 中加载：&lt;/p&gt;&lt;pre&gt;&lt;code&gt;Image(&quot;weather-partly-cloudy&quot;)
    .resizable()
    .scaledToFit()
    .frame(width: 24, height: 24)
    .accessibilityLabel(&quot;晴间多云&quot;)&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Apple 从 Xcode 12 起支持 SVG 图像资源；在符合部署版本要求的系统上可保留矢量表示。实际项目仍应在目标设备和深色模式下检查显示。若图标需要跟随代码改变颜色，可在资源属性中考虑模板渲染，或制作适合模板渲染的单色图标。&lt;/p&gt;&lt;p&gt;天气卡片更建议用 SwiftUI 的圆角矩形、渐变和文字重建，只把天气图标作为资源：&lt;/p&gt;&lt;pre&gt;&lt;code&gt;HStack {
    VStack(alignment: .leading) {
        Text(&quot;上海 · 周六&quot;)
        Text(&quot;24°&quot;).font(.system(size: 48, weight: .bold))
        Text(&quot;晴间多云&quot;)
    }

    Spacer()

    Image(&quot;weather-partly-cloudy&quot;)
        .resizable()
        .scaledToFit()
        .frame(width: 72, height: 72)
}
.padding(24)
.background(
    LinearGradient(
        colors: [.indigo, .cyan],
        startPoint: .topLeading,
        endPoint: .bottomTrailing
    )
)
.clipShape(RoundedRectangle(cornerRadius: 28))&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;编程部分并非本文重点。只需要记住交接原则：&lt;strong&gt;图标交资源，动态文字交代码，布局和圆角尽量交 SwiftUI。&lt;/strong&gt;&lt;/p&gt;&lt;h3&gt;参考资料与图片来源&lt;/h3&gt;&lt;ul&gt;&lt;li&gt;&lt;a href=&quot;https://www.wacom.com/zh-cn/support/product-support/drivers&quot;&gt;Wacom：官方驱动下载&lt;/a&gt;&lt;/li&gt;&lt;li&gt;&lt;a href=&quot;https://101.wacom.com/userhelp/zh_cn/WacomCenter.htm&quot;&gt;Wacom：Wacom Center 使用说明&lt;/a&gt;&lt;/li&gt;&lt;li&gt;&lt;a href=&quot;https://101.wacom.com/userhelp/zh_cn/AdvancedPenEraser_Con_Full.htm&quot;&gt;Wacom：压感笔的高级设置&lt;/a&gt;&lt;/li&gt;&lt;li&gt;&lt;a href=&quot;https://inkscape.org/zh-hans/&quot;&gt;Inkscape 中文官网&lt;/a&gt;&lt;/li&gt;&lt;li&gt;&lt;a href=&quot;https://lists.inkscape.org/hyperkitty/list/inkscape-announce%40lists.inkscape.org/thread/OCEYDBJCKK2ZF65O3FNKZOSTL7JBTZND/&quot;&gt;Inkscape 1.4.4 发布信息&lt;/a&gt;&lt;/li&gt;&lt;li&gt;&lt;a href=&quot;https://inkscape-manuals.readthedocs.io/&quot;&gt;Inkscape Beginners’ Guide&lt;/a&gt;&lt;/li&gt;&lt;li&gt;&lt;a href=&quot;https://inkscape-manuals.readthedocs.io/en/latest/export-png.html&quot;&gt;Inkscape Beginners’ Guide：导出 PNG&lt;/a&gt;&lt;/li&gt;&lt;li&gt;&lt;a href=&quot;https://gitlab.com/inkscape/inkscape-docs/manuals/-/tree/master/Inkscape-Beginners-Guide&quot;&gt;Inkscape 官方文档仓库&lt;/a&gt;&lt;/li&gt;&lt;li&gt;&lt;a href=&quot;https://developer.apple.com/documentation/xcode/adding-images-to-your-xcode-project&quot;&gt;Apple：Adding images to your Xcode project&lt;/a&gt;&lt;/li&gt;&lt;li&gt;&lt;a href=&quot;https://developer.apple.com/documentation/xcode-release-notes/xcode-12-release-notes/&quot;&gt;Apple：Xcode 12 Release Notes（SVG 图像资源支持）&lt;/a&gt;&lt;/li&gt;&lt;/ul&gt;</content:encoded></item><item><title>详细拆解longBlog自动化流程</title><link>https://huowenlong.com/blog/HuEZ8IbXjuPl/</link><guid isPermaLink="true">https://huowenlong.com/blog/HuEZ8IbXjuPl/</guid><description>保姆级拆解 longBlog 自动化链路：在 Trilium 里打标签触发、服务器轮询取正文、生成博客数据、构建并推送 GitHub，最后由部署平台自动上线，逐层说明每个角色的分工。</description><pubDate>Thu, 02 Apr 2026 16:01:47 GMT</pubDate><content:encoded>&lt;aside&gt;&lt;p&gt;部分内容已过期&lt;/p&gt;&lt;/aside&gt;&lt;h2&gt;这篇文章的目标非常直接：&lt;/h2&gt;&lt;blockquote&gt;&lt;p&gt;把 &lt;code&gt;longBlog&lt;/code&gt; 这套自动化流，从“它能跑”讲到“你能看懂”，再讲到“你能在新服务器上复现”。&lt;/p&gt;&lt;/blockquote&gt;&lt;p&gt;如果你对 webhook、runner、systemd、Git 自动推送、Trilium 脚本这些概念还比较模糊，也没关系。我会尽量用一种接近“保姆级拆解”的方式来讲。&lt;/p&gt;&lt;h2&gt;一、先别急着看代码，先理解它到底在做什么&lt;/h2&gt;&lt;p&gt;这套自动化流，其实就是在解决一个问题：&lt;/p&gt;&lt;p&gt;“我在 Trilium 里改了文章，博客为什么能自动更新？”&lt;/p&gt;&lt;p&gt;把这个问题翻译成流程，就是：&lt;/p&gt;&lt;ol&gt;&lt;li&gt;你在 Trilium 里改了一篇文章&lt;/li&gt;&lt;li&gt;Trilium 发现这篇文章需要发布或同步&lt;/li&gt;&lt;li&gt;Trilium 主动通知服务器&lt;/li&gt;&lt;li&gt;服务器收到通知后，开始拉内容、生成数据、提交 Git&lt;/li&gt;&lt;li&gt;GitHub 仓库有了新提交&lt;/li&gt;&lt;li&gt;部署平台检测到仓库变化，自动发布新版本&lt;/li&gt;&lt;/ol&gt;&lt;p&gt;如果只看一句话，这条链路就是：&lt;/p&gt;&lt;pre&gt;&lt;code&gt;Trilium → Webhook → 服务器 Runner → Git Push → 自动部署&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;这就是 &lt;code&gt;longBlog&lt;/code&gt; 自动化的骨架。&lt;/p&gt;&lt;h2&gt;二、整条链路里到底有哪些角色&lt;/h2&gt;&lt;p&gt;为了避免一上来就被脚本吓到，我们先把参与者列出来。&lt;/p&gt;&lt;h3&gt;1. Trilium&lt;/h3&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;ul&gt;&lt;li&gt;&lt;code&gt;publish=true&lt;/code&gt;&lt;/li&gt;&lt;li&gt;&lt;code&gt;sync=true&lt;/code&gt;&lt;/li&gt;&lt;li&gt;&lt;code&gt;pinned=true&lt;/code&gt;&lt;/li&gt;&lt;li&gt;&lt;code&gt;aiRefresh=true&lt;/code&gt;&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;这些标签不是装饰，它们直接决定自动化流怎么走。&lt;/p&gt;&lt;figure&gt;&lt;img src=&quot;https://huowenlong.com/trilium-assets/HuEZ8IbXjuPl/54b36b2a370f0ee6.png&quot; width=&quot;2000&quot; height=&quot;1303&quot; /&gt;&lt;/figure&gt;&lt;figure&gt;&lt;img src=&quot;https://huowenlong.com/trilium-assets/HuEZ8IbXjuPl/0622278bdbc9e37e.png&quot; width=&quot;2000&quot; height=&quot;1604&quot; /&gt;&lt;/figure&gt;&lt;h3&gt;2. Trilium 内部 JS 脚本&lt;/h3&gt;&lt;p&gt;这是放在 Trilium 里的属性变更脚本，作用是：&lt;/p&gt;&lt;ul&gt;&lt;li&gt;监听某些标签变化&lt;/li&gt;&lt;li&gt;发现变化后，向服务器发送 webhook&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;也就是说，它相当于“前哨”。&lt;/p&gt;&lt;p&gt;它不负责真正同步内容，只负责说一句：&lt;/p&gt;&lt;blockquote&gt;&lt;p&gt;“服务器，你该干活了。”&lt;/p&gt;&lt;/blockquote&gt;&lt;figure&gt;&lt;img src=&quot;https://huowenlong.com/trilium-assets/HuEZ8IbXjuPl/0b5e7fe50c55324d.png&quot; width=&quot;2000&quot; height=&quot;1303&quot; /&gt;&lt;/figure&gt;&lt;h3&gt;3. 服务器上的 webhook 服务&lt;/h3&gt;&lt;p&gt;服务器侧真正接收请求的是一个 Python 脚本服务。&lt;/p&gt;&lt;p&gt;它的作用是：&lt;/p&gt;&lt;ul&gt;&lt;li&gt;接收来自 Trilium 的 webhook&lt;/li&gt;&lt;li&gt;校验签名，防止伪造请求&lt;/li&gt;&lt;li&gt;把本次事件记下来&lt;/li&gt;&lt;li&gt;拉起 runner&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;你可以把它理解成“门卫 + 调度通知员”。&lt;/p&gt;&lt;h3&gt;4. Runner&lt;/h3&gt;&lt;p&gt;Runner 是真正的调度入口。&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;判断有没有 Git 变化&lt;/li&gt;&lt;li&gt;决定是否构建验证&lt;/li&gt;&lt;li&gt;记录日志和报告&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;如果 webhook 服务是“通知员”，那 runner 就是“总调度”。&lt;/p&gt;&lt;h3&gt;5. 同步脚本&lt;/h3&gt;&lt;p&gt;同步脚本才是真正做内容工作的地方。&lt;/p&gt;&lt;p&gt;它负责：&lt;/p&gt;&lt;ul&gt;&lt;li&gt;从 Trilium ETAPI 拉文章&lt;/li&gt;&lt;li&gt;拉附件&lt;/li&gt;&lt;li&gt;生成前端数据文件&lt;/li&gt;&lt;li&gt;回写文章状态标签&lt;/li&gt;&lt;li&gt;在需要时自动 commit / push&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;这一层才是真正把“知识库内容”翻译成“博客前台可消费数据”的地方。&lt;/p&gt;&lt;h3&gt;6. GitHub 仓库&lt;/h3&gt;&lt;p&gt;它在这条链路中的作用不是“写文章”，而是：&lt;/p&gt;&lt;ul&gt;&lt;li&gt;作为前端项目的版本中心&lt;/li&gt;&lt;li&gt;作为部署平台的触发源&lt;/li&gt;&lt;/ul&gt;&lt;h3&gt;7. 部署平台&lt;/h3&gt;&lt;p&gt;比如 EdgeOne Pages。&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;也就是说，部署平台并不知道 Trilium、webhook、runner，它只知道：&lt;/p&gt;&lt;blockquote&gt;&lt;p&gt;GitHub 仓库更新了。&lt;/p&gt;&lt;/blockquote&gt;&lt;h2&gt;三、真正的目录结构应该怎么理解&lt;/h2&gt;&lt;p&gt;现在推荐的服务器目录结构是：&lt;/p&gt;&lt;pre&gt;&lt;code&gt;/root/longblog-automation/
├── service/
├── runtime/
├── workspace/
│   └── current/
└── check.sh&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;这个结构非常重要，因为很多人第一次做自动化时，最容易犯的错就是：&lt;/p&gt;&lt;blockquote&gt;&lt;p&gt;把服务脚本、运行态、项目源码全部混在一个目录里。&lt;/p&gt;&lt;/blockquote&gt;&lt;p&gt;这样后期一定会越来越乱。&lt;/p&gt;&lt;p&gt;所以这里一定要记住三件事：&lt;/p&gt;&lt;h3&gt;1. &lt;code&gt;service/&lt;/code&gt;&lt;/h3&gt;&lt;p&gt;只放服务脚本：&lt;/p&gt;&lt;ul&gt;&lt;li&gt;webhook&lt;/li&gt;&lt;li&gt;runner&lt;/li&gt;&lt;li&gt;sync 脚本&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;它的职责是“执行自动化逻辑”。&lt;/p&gt;&lt;h3&gt;2. &lt;code&gt;runtime/&lt;/code&gt;&lt;/h3&gt;&lt;p&gt;只放运行态：&lt;/p&gt;&lt;ul&gt;&lt;li&gt;&lt;code&gt;env.sh&lt;/code&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;h3&gt;3. &lt;code&gt;workspace/current/&lt;/code&gt;&lt;/h3&gt;&lt;p&gt;这是一个独立项目工作区。&lt;/p&gt;&lt;p&gt;它的职责是：&lt;/p&gt;&lt;ul&gt;&lt;li&gt;clone 前端仓库&lt;/li&gt;&lt;li&gt;生成数据文件&lt;/li&gt;&lt;li&gt;本地化附件&lt;/li&gt;&lt;li&gt;构建验证&lt;/li&gt;&lt;li&gt;Git push&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;它不是服务本体，而是“runner 干活的地方”。&lt;/p&gt;&lt;p&gt;这一点一定要分清。&lt;/p&gt;&lt;h2&gt;四、具体流程：从 Trilium 改一篇文章开始&lt;/h2&gt;&lt;p&gt;我用一个最常见的例子：&lt;/p&gt;&lt;p&gt;场景：你给某篇文章打上 &lt;code&gt;sync=true&lt;/code&gt;&lt;/p&gt;&lt;p&gt;下面是这条链路实际发生的事情。&lt;/p&gt;&lt;h3&gt;第一步：Trilium 内部脚本监听到标签变化&lt;/h3&gt;&lt;p&gt;Trilium 脚本发现：&lt;/p&gt;&lt;ul&gt;&lt;li&gt;某篇 note 的 &lt;code&gt;sync&lt;/code&gt; 标签变成了 &lt;code&gt;true&lt;/code&gt;&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;于是它会构造一个 webhook 请求体，大概长这样：&lt;/p&gt;&lt;pre&gt;&lt;code&gt;{
  &quot;event&quot;: &quot;sync_requested&quot;,
  &quot;requestId&quot;: &quot;noteId-sync-时间戳&quot;,
  &quot;noteId&quot;: &quot;某篇文章的 noteId&quot;,
  &quot;noteTitle&quot;: &quot;文章标题&quot;,
  &quot;sync&quot;: &quot;true&quot;,
  &quot;triggeredAt&quot;: &quot;触发时间&quot;
}&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;然后脚本会：&lt;/p&gt;&lt;ul&gt;&lt;li&gt;用 secret 计算 HMAC-SHA256 签名&lt;/li&gt;&lt;li&gt;发到服务器的 &lt;code&gt;/trilium-sync-webhook&lt;/code&gt;&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;也就是说：&lt;/p&gt;&lt;blockquote&gt;&lt;p&gt;Trilium 不是“等待服务器轮询”，而是“主动通知服务器”。&lt;/p&gt;&lt;/blockquote&gt;&lt;h3&gt;第二步：服务器 webhook 服务接收请求&lt;/h3&gt;&lt;p&gt;服务器入口收到请求后，会先做两件事：&lt;/p&gt;&lt;h4&gt;1. 校验签名&lt;/h4&gt;&lt;p&gt;确保这不是别人伪造的 webhook。&lt;/p&gt;&lt;h4&gt;2. 校验 payload&lt;/h4&gt;&lt;p&gt;确保：&lt;/p&gt;&lt;ul&gt;&lt;li&gt;&lt;code&gt;event&lt;/code&gt; 是合法事件&lt;/li&gt;&lt;li&gt;&lt;code&gt;noteId&lt;/code&gt; 存在&lt;/li&gt;&lt;li&gt;&lt;code&gt;sync=true&lt;/code&gt; 这种字段值也合理&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;如果这些都没问题，它才会继续。&lt;/p&gt;&lt;p&gt;然后它会把这次请求记下来，再拉起 runner。&lt;/p&gt;&lt;h3&gt;第三步：Runner 被启动&lt;/h3&gt;&lt;p&gt;Runner 启动后先做一件非常关键的事：加锁&lt;/p&gt;&lt;p&gt;为什么要加锁？&lt;/p&gt;&lt;p&gt;因为 webhook 有可能很密集，比如：&lt;/p&gt;&lt;ul&gt;&lt;li&gt;你刚改完 &lt;code&gt;publish&lt;/code&gt;&lt;/li&gt;&lt;li&gt;又改了 &lt;code&gt;pinned&lt;/code&gt;&lt;/li&gt;&lt;li&gt;又手动点了 &lt;code&gt;sync&lt;/code&gt;&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;如果这些请求同时跑，很容易把工作区搞乱。&lt;/p&gt;&lt;p&gt;所以 runner 会先建立一个锁：&lt;/p&gt;&lt;ul&gt;&lt;li&gt;如果当前没有人在跑，就开始执行&lt;/li&gt;&lt;li&gt;如果已经有实例在跑，就记一个“补跑标记”&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;这一步就是为了避免并发踩踏。&lt;/p&gt;&lt;h3&gt;第四步：Runner 准备工作区&lt;/h3&gt;&lt;p&gt;Runner 会去准备：&lt;/p&gt;&lt;ul&gt;&lt;li&gt;&lt;code&gt;/root/longblog-automation/workspace/current&lt;/code&gt;&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;这里会做：&lt;/p&gt;&lt;ul&gt;&lt;li&gt;clone 仓库（如果第一次运行）&lt;/li&gt;&lt;li&gt;或 fetch 最新远端&lt;/li&gt;&lt;li&gt;reset 到远端最新状态&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;h3&gt;第五步：调用同步脚本&lt;/h3&gt;&lt;p&gt;同步脚本启动后，会做很多真正的业务动作。它会先扫描 Trilium 根节点&lt;/p&gt;&lt;p&gt;根节点下面不一定全是文章，所以它会过滤：&lt;/p&gt;&lt;ul&gt;&lt;li&gt;根节点本身&lt;/li&gt;&lt;li&gt;模板 note&lt;/li&gt;&lt;li&gt;非文章 note&lt;/li&gt;&lt;li&gt;特定应跳过的 note&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;然后识别哪些文章应该发布&lt;/p&gt;&lt;p&gt;如果某篇文章：&lt;/p&gt;&lt;ul&gt;&lt;li&gt;&lt;code&gt;publish=true&lt;/code&gt;&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;它就会进入发布候选。&lt;/p&gt;&lt;p&gt;如果某篇文章：&lt;/p&gt;&lt;ul&gt;&lt;li&gt;&lt;code&gt;publish=false&lt;/code&gt;&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;而之前又已经发布过，那它会进入“撤下”逻辑。&lt;/p&gt;&lt;h3&gt;第六步：读取文章正文与附件&lt;/h3&gt;&lt;p&gt;对每篇候选文章，同步脚本会：&lt;/p&gt;&lt;ul&gt;&lt;li&gt;拉取 HTML 正文&lt;/li&gt;&lt;li&gt;提取里面引用的附件 URL&lt;/li&gt;&lt;li&gt;下载附件&lt;/li&gt;&lt;li&gt;保存到：&lt;ul&gt;&lt;li&gt;&lt;code&gt;public/trilium-assets/&amp;lt;noteId&amp;gt;/...&lt;/code&gt;&lt;/li&gt;&lt;/ul&gt;&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;这样前端页面就不必直接依赖 Trilium 附件地址，而是能用本地化后的静态资源。&lt;/p&gt;&lt;h3&gt;第七步：生成前端数据文件&lt;/h3&gt;&lt;p&gt;这一步是整个自动化流最核心的一步。&lt;/p&gt;&lt;p&gt;同步脚本最终会生成：&lt;/p&gt;&lt;p&gt;1. &lt;code&gt;trilium-posts.content.generated.ts&lt;/code&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;HTML 正文&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;2. &lt;code&gt;trilium-posts.meta.generated.ts&lt;/code&gt;&lt;/p&gt;&lt;p&gt;里面放：&lt;/p&gt;&lt;ul&gt;&lt;li&gt;slug&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;syncHash&lt;/li&gt;&lt;li&gt;syncStatus&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;前端页面并不会直接调 Trilium ETAPI，而是消费这两份生成文件。&lt;/p&gt;&lt;p&gt;这就是“知识库内容 → 前端静态数据”的桥梁。&lt;/p&gt;&lt;h3&gt;第八步：回写 Trilium 标签&lt;/h3&gt;&lt;p&gt;同步脚本不仅会读 Trilium，也会回写状态。&lt;/p&gt;&lt;p&gt;例如：&lt;/p&gt;&lt;ul&gt;&lt;li&gt;&lt;code&gt;slug&lt;/code&gt;&lt;/li&gt;&lt;li&gt;&lt;code&gt;summary&lt;/code&gt;&lt;/li&gt;&lt;li&gt;&lt;code&gt;tags&lt;/code&gt;&lt;/li&gt;&lt;li&gt;&lt;code&gt;publishedAt&lt;/code&gt;&lt;/li&gt;&lt;li&gt;&lt;code&gt;updatedAt&lt;/code&gt;&lt;/li&gt;&lt;li&gt;&lt;code&gt;syncHash&lt;/code&gt;&lt;/li&gt;&lt;li&gt;&lt;code&gt;syncStatus&lt;/code&gt;&lt;/li&gt;&lt;li&gt;&lt;code&gt;sync=false&lt;/code&gt;&lt;/li&gt;&lt;li&gt;&lt;code&gt;aiRefresh=false&lt;/code&gt;&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;为什么要回写？&lt;/p&gt;&lt;p&gt;因为你希望 Trilium 本身也知道：&lt;/p&gt;&lt;ul&gt;&lt;li&gt;这篇文章现在是什么状态&lt;/li&gt;&lt;li&gt;它有没有发布成功&lt;/li&gt;&lt;li&gt;它的摘要 / slug 是什么&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;否则状态就会分裂：&lt;/p&gt;&lt;ul&gt;&lt;li&gt;博客一套&lt;/li&gt;&lt;li&gt;知识库一套&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;那后面一定会越来越难维护。&lt;/p&gt;&lt;h3&gt;第九步：判断 Git 是否真的有变化&lt;/h3&gt;&lt;p&gt;这一步很重要。&lt;/p&gt;&lt;p&gt;自动化流不是每次都应该 push。&lt;/p&gt;&lt;p&gt;如果这次同步结果和现有文件完全一样，那就应该：&lt;/p&gt;&lt;ul&gt;&lt;li&gt;&lt;code&gt;gitChanged=false&lt;/code&gt;&lt;/li&gt;&lt;li&gt;&lt;code&gt;gitPushed=false&lt;/code&gt;&lt;/li&gt;&lt;li&gt;&lt;code&gt;buildRan=false&lt;/code&gt;&lt;/li&gt;&lt;/ul&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;h3&gt;第十步：有变化才 commit / push&lt;/h3&gt;&lt;p&gt;如果确实有变化，才会：&lt;/p&gt;&lt;ul&gt;&lt;li&gt;&lt;code&gt;git add&lt;/code&gt;&lt;/li&gt;&lt;li&gt;&lt;code&gt;git commit&lt;/code&gt;&lt;/li&gt;&lt;li&gt;&lt;code&gt;git push origin main&lt;/code&gt;&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;这一步推上 GitHub 后，部署平台就会开始感知到仓库更新。&lt;/p&gt;&lt;h3&gt;第十一步：可选 build 验证&lt;/h3&gt;&lt;p&gt;如果 &lt;code&gt;gitChanged=true&lt;/code&gt;，runner 还会视情况执行：&lt;/p&gt;&lt;ul&gt;&lt;li&gt;&lt;code&gt;npm install&lt;/code&gt;&lt;/li&gt;&lt;li&gt;&lt;code&gt;npm run build&lt;/code&gt;&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;但这里一定要理解清楚：&lt;/p&gt;&lt;blockquote&gt;&lt;p&gt;服务器上的 build 不是正式发布源，而是“构建验证”。&lt;/p&gt;&lt;/blockquote&gt;&lt;p&gt;真正正式发布的仍然是：&lt;/p&gt;&lt;ul&gt;&lt;li&gt;GitHub 仓库&lt;/li&gt;&lt;li&gt;部署平台自动拉取并构建&lt;/li&gt;&lt;/ul&gt;&lt;h2&gt;五、为什么要把服务目录和工作区分开&lt;/h2&gt;&lt;p&gt;这是整个系统最容易理解错的地方，我单独讲一次。&lt;/p&gt;&lt;p&gt;&lt;strong&gt;旧思路&lt;/strong&gt;：所有东西都塞进一个 &lt;code&gt;/root/longBlog&lt;/code&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;哪些是 runtime 日志&lt;/li&gt;&lt;li&gt;哪些只是构建缓存&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;时间一长一定乱。&lt;/p&gt;&lt;p&gt;&lt;strong&gt;新思路&lt;/strong&gt;：三层分离&lt;/p&gt;&lt;p&gt;&lt;code&gt;service/&lt;/code&gt;负责执行逻辑。&lt;/p&gt;&lt;p&gt;&lt;code&gt;runtime/&lt;/code&gt;负责记录运行态。&lt;/p&gt;&lt;p&gt;&lt;code&gt;workspace/current/&lt;/code&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;h2&gt;六、如何在新服务器上重新部署这套自动化&lt;/h2&gt;&lt;p&gt;下面这一段，是你真的可以照着做的部分。&lt;/p&gt;&lt;h3&gt;第一步：装基础环境&lt;/h3&gt;&lt;p&gt;如果是 Debian / Ubuntu，先装：&lt;/p&gt;&lt;pre&gt;&lt;code&gt;apt update
apt install -y git python3 python3-pip nodejs npm nginx curl&lt;/code&gt;&lt;/pre&gt;&lt;h3&gt;第二步：创建目录&lt;/h3&gt;&lt;pre&gt;&lt;code&gt;mkdir -p /root/longblog-automation/service
mkdir -p /root/longblog-automation/runtime/logs
mkdir -p /root/longblog-automation/runtime/reports
mkdir -p /root/longblog-automation/runtime/state
mkdir -p /root/longblog-automation/workspace&lt;/code&gt;&lt;/pre&gt;&lt;h3&gt;第三步：放入服务脚本&lt;/h3&gt;&lt;p&gt;把这些文件放到：&lt;/p&gt;&lt;ul&gt;&lt;li&gt;&lt;code&gt;service/trilium_sync_webhook.py&lt;/code&gt;&lt;/li&gt;&lt;li&gt;&lt;code&gt;service/run_sync_and_build.sh&lt;/code&gt;&lt;/li&gt;&lt;li&gt;&lt;code&gt;service/sync_trilium_posts.py&lt;/code&gt;&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;如果你已经有对应模板仓库，直接从模板仓库复制即可。&lt;/p&gt;&lt;h3&gt;第四步：写 &lt;code&gt;runtime/env.sh&lt;/code&gt;&lt;/h3&gt;&lt;p&gt;这个文件最重要，因为它决定自动化能否真正运行。&lt;/p&gt;&lt;p&gt;你至少要配置：&lt;/p&gt;&lt;ul&gt;&lt;li&gt;&lt;code&gt;TRILIUM_BASE_URL&lt;/code&gt;&lt;/li&gt;&lt;li&gt;&lt;code&gt;TRILIUM_ETAPI_TOKEN&lt;/code&gt;&lt;/li&gt;&lt;li&gt;&lt;code&gt;TRILIUM_BLOG_ROOT_NOTE_ID&lt;/code&gt;&lt;/li&gt;&lt;li&gt;&lt;code&gt;TRILIUM_PUBLISH_WEBHOOK_SECRET&lt;/code&gt;&lt;/li&gt;&lt;li&gt;&lt;code&gt;DEEPSEEK_API_KEY&lt;/code&gt;&lt;/li&gt;&lt;li&gt;&lt;code&gt;LONGBLOG_RUNTIME_DIR&lt;/code&gt;&lt;/li&gt;&lt;li&gt;&lt;code&gt;LONGBLOG_REPO_DIR&lt;/code&gt;&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;然后：&lt;/p&gt;&lt;pre&gt;&lt;code&gt;chmod 600 /root/longblog-automation/runtime/env.sh&lt;/code&gt;&lt;/pre&gt;&lt;h3&gt;第五步：准备工作区&lt;/h3&gt;&lt;pre&gt;&lt;code&gt;cd /root/longblog-automation/workspace
git clone git@github.com:YOUR_ORG/YOUR_REPO.git current&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;并确保服务器 SSH key 有权限访问这个仓库。&lt;/p&gt;&lt;h3&gt;第六步：配置 systemd&lt;/h3&gt;&lt;p&gt;把 &lt;code&gt;longblog-webhook.service&lt;/code&gt; 放到：&lt;/p&gt;&lt;ul&gt;&lt;li&gt;&lt;code&gt;/etc/systemd/system/longblog-webhook.service&lt;/code&gt;&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;然后执行：&lt;/p&gt;&lt;pre&gt;&lt;code&gt;systemctl daemon-reload
systemctl enable longblog-webhook.service
systemctl start longblog-webhook.service&lt;/code&gt;&lt;/pre&gt;&lt;h3&gt;第七步：配置 Nginx&lt;/h3&gt;&lt;p&gt;为 &lt;code&gt;/trilium-sync-webhook&lt;/code&gt; 配一条反代：&lt;/p&gt;&lt;pre&gt;&lt;code&gt;location /trilium-sync-webhook {
    proxy_pass http://127.0.0.1:8787/trilium-sync-webhook;
    proxy_set_header Host $host;
    proxy_set_header X-Real-IP $remote_addr;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;然后：&lt;/p&gt;&lt;pre&gt;&lt;code&gt;nginx -t &amp;amp;&amp;amp; systemctl reload nginx&lt;/code&gt;&lt;/pre&gt;&lt;h3&gt;第八步：恢复 Trilium 里的 JS 脚本&lt;/h3&gt;&lt;p&gt;这是很多人迁移时最容易漏掉的一步。&lt;/p&gt;&lt;p&gt;如果你只恢复了服务器，却没恢复 Trilium 里的属性变更脚本，那么：&lt;/p&gt;&lt;ul&gt;&lt;li&gt;服务器永远收不到 webhook&lt;/li&gt;&lt;li&gt;自动化也就永远不会触发&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;所以一定要把 Trilium 侧脚本也恢复。&lt;/p&gt;&lt;p&gt;并且替换：&lt;/p&gt;&lt;ul&gt;&lt;li&gt;&lt;code&gt;WEBHOOK_URL&lt;/code&gt;&lt;/li&gt;&lt;li&gt;&lt;code&gt;WEBHOOK_SECRET&lt;/code&gt;&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;确保和服务器一致。&lt;/p&gt;&lt;h3&gt;第九步：先做巡检，再做真实测试&lt;/h3&gt;&lt;p&gt;如果你有巡检脚本，比如：&lt;/p&gt;&lt;pre&gt;&lt;code&gt;/root/longblog-automation/check.sh&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;那先跑它。&lt;/p&gt;&lt;p&gt;你至少要看到：&lt;/p&gt;&lt;ul&gt;&lt;li&gt;systemd 服务是 active&lt;/li&gt;&lt;li&gt;8787 在监听&lt;/li&gt;&lt;li&gt;runtime 路径正常&lt;/li&gt;&lt;li&gt;workspace 路径正常&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;然后再在 Trilium 里手动测试一篇文章：&lt;/p&gt;&lt;ul&gt;&lt;li&gt;&lt;code&gt;sync=true&lt;/code&gt;&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;最后观察：&lt;/p&gt;&lt;ul&gt;&lt;li&gt;&lt;code&gt;trilium_sync_webhook.log&lt;/code&gt;&lt;/li&gt;&lt;li&gt;&lt;code&gt;last_report.json&lt;/code&gt;&lt;/li&gt;&lt;li&gt;&lt;code&gt;sync.log&lt;/code&gt;&lt;/li&gt;&lt;li&gt;&lt;code&gt;build.log&lt;/code&gt;&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;如果这里都通了，整条链路基本就通了。&lt;/p&gt;&lt;h2&gt;七、这套自动化最容易踩的坑&lt;/h2&gt;&lt;p&gt;1. 只恢复服务器，不恢复 Trilium 脚本&lt;/p&gt;&lt;p&gt;结果：根本不会触发自动化。&lt;/p&gt;&lt;p&gt;2. 服务目录、工作区、runtime 混在一起&lt;/p&gt;&lt;p&gt;结果：越跑越乱，迁移时根本不知道该搬什么。&lt;/p&gt;&lt;p&gt;3. 没有做并发锁&lt;/p&gt;&lt;p&gt;结果：多个 webhook 同时跑，把工作区搞乱。&lt;/p&gt;&lt;p&gt;4. 无变化也 commit / push&lt;/p&gt;&lt;p&gt;结果：部署平台反复无意义触发。&lt;/p&gt;&lt;p&gt;5. 把真实 secret 提交到 Git&lt;/p&gt;&lt;p&gt;结果：后患无穷。&lt;/p&gt;&lt;h2&gt;八、总结&lt;/h2&gt;&lt;p&gt;如果要把这篇文章最后压缩成一句话，我会这么说：&lt;/p&gt;&lt;blockquote&gt;&lt;p&gt;&lt;strong&gt;这套自动化流，就是让 Trilium 里的标签变化，自动变成博客仓库里的可部署更新。&lt;/strong&gt;&lt;/p&gt;&lt;/blockquote&gt;&lt;p&gt;而实现它的关键，并不只是脚本本身，而是你要把下面这几层一起理顺：&lt;/p&gt;&lt;ul&gt;&lt;li&gt;Trilium 触发脚本&lt;/li&gt;&lt;li&gt;服务器 webhook 服务&lt;/li&gt;&lt;li&gt;runner 调度&lt;/li&gt;&lt;li&gt;工作区同步&lt;/li&gt;&lt;li&gt;Git push&lt;/li&gt;&lt;li&gt;部署平台发布&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;当你真正把这几层分清楚之后，你会发现它没有看起来那么神秘。甚至你完全可以在一台新服务器上，把它重新搭起来。这就是这套自动化流最有价值的地方。&lt;/p&gt;</content:encoded></item><item><title>个人博客longBlog介绍</title><link>https://huowenlong.com/blog/dpIN2qR0Q8lb/</link><guid isPermaLink="true">https://huowenlong.com/blog/dpIN2qR0Q8lb/</guid><description>介绍 longBlog 的定位与设计：以 Trilium 作为内容与状态后台、Astro 作为展示层、自动化层负责发布的三层结构，以及这个项目想解决的真问题与名字的由来。</description><pubDate>Thu, 02 Apr 2026 15:28:06 GMT</pubDate><content:encoded>&lt;blockquote&gt;&lt;p&gt;关于我的博客为什么叫longBlog：并不是指”长篇博客“的意思，而是 long 是我最后一个名字的拼音；为什么是驼峰式写法：可能是习惯了 Java 的编程风格。&lt;/p&gt;&lt;/blockquote&gt;&lt;aside&gt;&lt;p&gt;部分内容已过期&lt;/p&gt;&lt;/aside&gt;&lt;h2&gt;一、我为什么做 longBlog&lt;/h2&gt;&lt;p&gt;如果只把 &lt;code&gt;longBlog&lt;/code&gt; 理解成一个 Astro 博客项目，其实是低估了它。&lt;/p&gt;&lt;p&gt;对我来说，&lt;code&gt;longBlog&lt;/code&gt; 更像是一套围绕 &lt;strong&gt;Trilium 知识库&lt;/strong&gt; 搭建出来的个人内容系统：&lt;/p&gt;&lt;ul&gt;&lt;li&gt;我继续在自己熟悉的知识库里写东西&lt;/li&gt;&lt;li&gt;不需要为了发博客切换到另一个后台&lt;/li&gt;&lt;li&gt;文章、标签、置顶、同步、AI 补全这些状态，都可以在 Trilium 里控制&lt;/li&gt;&lt;li&gt;前台则交给 Astro 做成一个更轻、更美观、更适合公开阅读的博客站点&lt;/li&gt;&lt;/ul&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;code&gt;longBlog&lt;/code&gt; 最核心的设计理念。&lt;/p&gt;&lt;h2&gt;二、longBlog 的整体定位&lt;/h2&gt;&lt;p&gt;&lt;code&gt;longBlog&lt;/code&gt; 可以拆成三层来看：&lt;/p&gt;&lt;h3&gt;1. 内容层&lt;/h3&gt;&lt;p&gt;内容主要来自两个地方：&lt;/p&gt;&lt;ul&gt;&lt;li&gt;&lt;strong&gt;Trilium&lt;/strong&gt;：负责正式文章、标签、摘要、同步状态&lt;/li&gt;&lt;/ul&gt;&lt;figure&gt;&lt;img src=&quot;https://huowenlong.com/trilium-assets/dpIN2qR0Q8lb/a8490b690c03a7bd.png&quot; width=&quot;2000&quot; height=&quot;1303&quot; /&gt;&lt;/figure&gt;&lt;ul&gt;&lt;li&gt;&lt;strong&gt;Memos&lt;/strong&gt;：负责“说说 / 动态”这类轻量内容&lt;/li&gt;&lt;/ul&gt;&lt;figure&gt;&lt;img src=&quot;https://huowenlong.com/trilium-assets/dpIN2qR0Q8lb/d63a9b3db6965fe4.png&quot; width=&quot;977&quot; height=&quot;2000&quot; /&gt;&lt;/figure&gt;&lt;h3&gt;2. 展示层&lt;/h3&gt;&lt;p&gt;展示层是一个基于 &lt;strong&gt;Astro&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;友链页&lt;/li&gt;&lt;li&gt;关于页 / 知识地图页&lt;/li&gt;&lt;/ul&gt;&lt;h3&gt;3. 自动化层&lt;/h3&gt;&lt;p&gt;自动化层负责把 Trilium 中的内容变化转化为：&lt;/p&gt;&lt;ul&gt;&lt;li&gt;数据生成&lt;/li&gt;&lt;li&gt;Git 提交&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;blockquote&gt;&lt;p&gt;&lt;strong&gt;知识库驱动的静态博客系统。&lt;/strong&gt;&lt;/p&gt;&lt;/blockquote&gt;&lt;h2&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;/ul&gt;&lt;p&gt;而 &lt;code&gt;longBlog&lt;/code&gt; 的思路完全不同：&lt;/p&gt;&lt;ul&gt;&lt;li&gt;文章写在 Trilium 里&lt;/li&gt;&lt;li&gt;发布状态也在 Trilium 里&lt;/li&gt;&lt;li&gt;同步动作也由 Trilium 里的标签触发&lt;/li&gt;&lt;li&gt;博客前台只负责“展示已经整理好的内容”&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;这带来的最大好处是：&lt;/p&gt;&lt;h3&gt;1. 写作体验统一&lt;/h3&gt;&lt;p&gt;我不用在“知识库”和“博客后台”之间反复横跳。&lt;/p&gt;&lt;h3&gt;2. 内容管理更自然&lt;/h3&gt;&lt;p&gt;文章本来就是知识体系的一部分，不是被单独拎出来的“博客条目”。&lt;/p&gt;&lt;h3&gt;3. 自动化程度更高&lt;/h3&gt;&lt;p&gt;只要内容状态变了，就可以自动进入发布链路，而不是再人工做重复操作。&lt;/p&gt;&lt;h2&gt;四、前台站点做了什么&lt;/h2&gt;&lt;p&gt;虽然 &lt;code&gt;longBlog&lt;/code&gt; 的亮点在自动化，但它前台也并不是一个“只有功能，没有体验”的站。&lt;/p&gt;&lt;h3&gt;1. 首页&lt;/h3&gt;&lt;p&gt;首页主要承担两件事：&lt;/p&gt;&lt;ul&gt;&lt;li&gt;对外展示作者信息和站点定位&lt;/li&gt;&lt;li&gt;展示最新文章与站内搜索入口&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;同时首页也有：&lt;/p&gt;&lt;ul&gt;&lt;li&gt;GitHub / Email 社交入口&lt;/li&gt;&lt;li&gt;深浅色模式切换&lt;/li&gt;&lt;li&gt;本地搜索&lt;/li&gt;&lt;/ul&gt;&lt;h3&gt;2. 文章列表页&lt;/h3&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;h3&gt;3. 文章详情页&lt;/h3&gt;&lt;p&gt;详情页是整个前台体验最完整的地方，包含：&lt;/p&gt;&lt;ul&gt;&lt;li&gt;自动目录（TOC）&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;/li&gt;&lt;/ul&gt;&lt;h3&gt;4. 说说页&lt;/h3&gt;&lt;p&gt;说说页通过 Memos 数据源提供一个更轻量的内容出口，适合放：&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;h3&gt;5. 关于页&lt;/h3&gt;&lt;p&gt;关于页不是一页普通自我介绍，而是一个 &lt;strong&gt;Trilium 知识地图&lt;/strong&gt; 页面。&lt;/p&gt;&lt;figure&gt;&lt;img src=&quot;https://huowenlong.com/trilium-assets/dpIN2qR0Q8lb/8597fd3786af3bae.png&quot; width=&quot;2000&quot; height=&quot;1303&quot; /&gt;&lt;/figure&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;h2&gt;五、longBlog 的技术栈为什么这样选&lt;/h2&gt;&lt;h3&gt;1. 为什么前台选 Astro&lt;/h3&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;h3&gt;2. 为什么内容源选 Trilium&lt;/h3&gt;&lt;p&gt;因为 Trilium 对我来说不是“外部 CMS”，而是我的知识中枢。&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;h3&gt;3. 为什么把自动化单独拆出来&lt;/h3&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;自动化仓库负责 webhook / runner / sync / build&lt;/li&gt;&lt;li&gt;代理仓库负责 Memos / Trilium Tree 这类中间层&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;这会让整个系统更容易理解，也更容易迁移。&lt;/p&gt;&lt;h2&gt;六、项目最终演化成了怎样的结构&lt;/h2&gt;&lt;p&gt;随着不断整理，现在这套体系已经不再只是一个仓库，而是一个更完整的项目矩阵：&lt;/p&gt;&lt;p&gt;1. &lt;code&gt;longBlog&lt;/code&gt;&lt;/p&gt;&lt;p&gt;当前个人博客项目本体。&lt;/p&gt;&lt;p&gt;2. &lt;code&gt;longblog-template&lt;/code&gt;&lt;/p&gt;&lt;p&gt;面向公开复用的前端模板仓库。&lt;/p&gt;&lt;p&gt;3. &lt;code&gt;longblog-server-automation&lt;/code&gt;&lt;/p&gt;&lt;p&gt;服务器自动化链路模板仓库，负责：&lt;/p&gt;&lt;ul&gt;&lt;li&gt;webhook&lt;/li&gt;&lt;li&gt;runner&lt;/li&gt;&lt;li&gt;Trilium 同步脚本&lt;/li&gt;&lt;li&gt;systemd 托管&lt;/li&gt;&lt;li&gt;巡检脚本&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;4. &lt;code&gt;memos-proxy-template&lt;/code&gt;&lt;/p&gt;&lt;p&gt;Memos API 代理模板。&lt;/p&gt;&lt;p&gt;5. &lt;code&gt;trilium-tree-proxy-template&lt;/code&gt;&lt;/p&gt;&lt;p&gt;Trilium 树结构代理模板。&lt;/p&gt;&lt;p&gt;这意味着 &lt;code&gt;longBlog&lt;/code&gt; 不再只是“一个博客仓库”，而是发展成了：&lt;/p&gt;&lt;blockquote&gt;&lt;p&gt;&lt;strong&gt;一个由前端模板、自动化服务、内容代理、知识地图代理共同组成的个人博客系统。&lt;/strong&gt;&lt;/p&gt;&lt;/blockquote&gt;&lt;h2&gt;七、这个项目最让我满意的地方&lt;/h2&gt;&lt;p&gt;如果一定要总结，我最满意的不是某一个页面，也不是某一个动效，而是下面这件事：&lt;/p&gt;&lt;h3&gt;“写作”和“发布”终于被接起来了&lt;/h3&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;code&gt;longBlog&lt;/code&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;h2&gt;八、它并不是什么&lt;/h2&gt;&lt;p&gt;这个项目虽然做了不少东西，但它并不追求成为一个“大而全”的通用博客平台。&lt;/p&gt;&lt;p&gt;它不是：&lt;/p&gt;&lt;ul&gt;&lt;li&gt;通用 CMS 产品&lt;/li&gt;&lt;li&gt;面向所有用户的博客 SaaS&lt;/li&gt;&lt;li&gt;开箱即用的一站式后台系统&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;它更像：&lt;/p&gt;&lt;ul&gt;&lt;li&gt;一个高度贴合 Trilium 工作流的博客方案&lt;/li&gt;&lt;li&gt;一个把个人知识体系和公开输出串起来的实践&lt;/li&gt;&lt;li&gt;一个适合继续生长、不断拆分和迭代的系统&lt;/li&gt;&lt;/ul&gt;&lt;h2&gt;九、适合什么样的人参考&lt;/h2&gt;&lt;p&gt;如果你符合下面几种情况，这个项目会比较值得参考：&lt;/p&gt;&lt;h3&gt;1. 你已经在用 Trilium 写作&lt;/h3&gt;&lt;p&gt;那你会很容易理解这套链路的价值。&lt;/p&gt;&lt;h3&gt;2. 你不想再维护一个传统博客后台&lt;/h3&gt;&lt;p&gt;你只想继续在自己的内容系统里写，博客只是公开展示层。&lt;/p&gt;&lt;h3&gt;3. 你愿意把自动化拆开维护&lt;/h3&gt;&lt;p&gt;你不希望把前端、代理、同步脚本、运行态全塞进一个仓库里。&lt;/p&gt;&lt;h3&gt;4. 你喜欢“模块化组合”而不是“单仓大一统”&lt;/h3&gt;&lt;p&gt;这套项目矩阵会更适合你。&lt;/p&gt;&lt;h2&gt;十、总结&lt;/h2&gt;&lt;p&gt;如果要用一句话来介绍 &lt;code&gt;longBlog&lt;/code&gt;，我会这样说：&lt;/p&gt;&lt;blockquote&gt;&lt;p&gt;&lt;strong&gt;longBlog 是一个由 Trilium 驱动内容、由 Astro 负责展示、由独立自动化服务负责发布的个人博客系统。&lt;/strong&gt;&lt;/p&gt;&lt;/blockquote&gt;&lt;p&gt;它的重点不是“又做了一个博客主题”，而是把“知识库写作”真正接进了“公开发布”这条链路。&lt;/p&gt;&lt;p&gt;而这，正是这个项目最有意思的地方。&lt;/p&gt;</content:encoded></item><item><title>我的建站史</title><link>https://huowenlong.com/blog/vvEmuVooTDii/</link><guid isPermaLink="true">https://huowenlong.com/blog/vvEmuVooTDii/</guid><description>从二〇二五年买下域名、浅尝 WordPress，到用 Hexo 配 Redefine 主题，再到折腾 Trilium 分享博客，最终选定 Astro 重建站点——一年换框架、踩坑之后的定稿记录。</description><pubDate>Thu, 02 Apr 2026 09:34:02 GMT</pubDate><content:encoded>&lt;h2&gt;前言&lt;/h2&gt;&lt;p&gt;当我真正开始写这篇文章时，大概率已经代表我不会在挑选博客框架、迁移博客上再大费周折了，从最早开始折腾博客到现在已经时隔一年了，中间换过很多框架，也踩过很多坑。现在，我正式确定了我的博客框架 —— &lt;a href=&quot;https://github.com/withastro/astro&quot;&gt;Astro&lt;/a&gt;。&lt;/p&gt;&lt;h2&gt;2025年 2 月 13 日：购入域名 epochwl.top&lt;/h2&gt;&lt;p&gt;一次性购入了十年域名。&lt;/p&gt;&lt;figure&gt;&lt;img src=&quot;https://huowenlong.com/trilium-assets/vvEmuVooTDii/a0e2cfc95196fc96.png&quot; width=&quot;1026&quot; height=&quot;1568&quot; /&gt;&lt;/figure&gt;&lt;h2&gt;2025 年 2 月 16 日：购入域名 epochwl.xyz&lt;/h2&gt;&lt;p&gt;购入了一年域名，于 2026 年 2 月 16 日正式释放。&lt;/p&gt;&lt;figure&gt;&lt;img src=&quot;https://huowenlong.com/trilium-assets/vvEmuVooTDii/cf6a4d4e6cd4a711.png&quot; width=&quot;1212&quot; height=&quot;1656&quot; /&gt;&lt;/figure&gt;&lt;h2&gt;2025 年 3 月：Hexo 框架初尝&lt;/h2&gt;&lt;p&gt;目前的博客框架多种多样，像Jekyll（Ruby）、Hugo（Go）、Hexo（Node.js）等的静态站点，也有诸如WordPress（PHP + MySQL）、Ghost（Node.js）等的动态平台。最初我用的是 WordPress 部署在自己本地服务器，浅尝辄止。功能确实很多，插件丰富，但给我一种很重的感觉，并不适合我。之后我发现了 Hexo 框架，感觉非常符合我的需求：轻量化、部署方便、直接使用 markdown 编写博客。&lt;/p&gt;&lt;figure&gt;&lt;img src=&quot;https://huowenlong.com/trilium-assets/vvEmuVooTDii/f4db441ef69b6e6f.png&quot; width=&quot;2000&quot; height=&quot;1172&quot; /&gt;&lt;/figure&gt;&lt;p&gt;那个时候用的是&lt;a href=&quot;https://github.com/EvanNotFound/hexo-theme-redefine&quot;&gt;主题Redefine v2.8.2&lt;/a&gt;，整体来说确实不错，但是使用时间长了就有了一个痛点：写文章不方便，一个博客站最灵魂的东西还是内容，博客框架要为内容服务。于是，在短暂使用一段时间，产出了一点点内容后，就一直搁置了，这个博客部署在 Cloudflare Pages，到现在还能访问，没有删除，算是留个纪念吧。&lt;/p&gt;&lt;figure&gt;&lt;img src=&quot;https://huowenlong.com/trilium-assets/vvEmuVooTDii/641c31f61bb37628.png&quot; width=&quot;2000&quot; height=&quot;1179&quot; /&gt;&lt;/figure&gt;&lt;h2&gt;2025 年 5 月 3 日：购入域名 ssaw.top/wenlong.asia&lt;/h2&gt;&lt;p&gt;一次购入了十年域名 ssaw.top，购入了一年域名 wenlong.asia，这个 wenlong.asia 我应该不会再续费了，一时心血来潮买的。（截止26年9月，这个wenlong.asia域名已经过期啦，不续费了。）&lt;/p&gt;&lt;figure&gt;&lt;img src=&quot;https://huowenlong.com/trilium-assets/vvEmuVooTDii/04bba8ab22299020.png&quot; width=&quot;1026&quot; height=&quot;1566&quot; /&gt;&lt;/figure&gt;&lt;figure&gt;&lt;img src=&quot;https://huowenlong.com/trilium-assets/vvEmuVooTDii/d03c77d634855679.png&quot; width=&quot;1032&quot; height=&quot;1574&quot; /&gt;&lt;/figure&gt;&lt;h2&gt;2025 年 7 月：Trilium 分享博客&lt;/h2&gt;&lt;p&gt;这段时间我发现了一个很有意思的网站：&lt;a href=&quot;https://ankia.top/Index&quot;&gt;东东的小黑盒&lt;/a&gt;，得益于 Trilium 高自由度的脚本能力和标签关系功能，他把笔记软件 Trilium 的分享功能玩出了花，他的博客整体设计感也不错，最主要的是写文章特别方便，随写随发布，这种博客的设计理念还是很吸引我的。&lt;/p&gt;&lt;figure&gt;&lt;img src=&quot;https://huowenlong.com/trilium-assets/vvEmuVooTDii/3008aebb43590bb1.png&quot; width=&quot;2000&quot; height=&quot;1180&quot; /&gt;&lt;/figure&gt;&lt;p&gt;但我最终没选择它的原因首先还是觉得学习成本有些高，想要真的玩明白真的需要不少时间，并且我发现目前已知使用这个博客分享功能建博客的 Trilium 版本都是老版本，我用的是新版本，有些方面不兼容，想要使用博客模版的话以后扩展又不方便，而且由于这种博客设计的特殊性，Vibe Coding 不能很好的施展出来，所以还是放弃了，但又没有完全放弃。😝&lt;/p&gt;&lt;h2&gt;2025 年 7 月 23 日：购入域名 huowenlong.com&lt;/h2&gt;&lt;p&gt;购入了一年的域名，com 域名一次性买十年还是有点承担不起。🙂‍↔️&lt;/p&gt;&lt;figure&gt;&lt;img src=&quot;https://huowenlong.com/trilium-assets/vvEmuVooTDii/198965d39340d126.png&quot; width=&quot;1030&quot; height=&quot;1568&quot; /&gt;&lt;/figure&gt;&lt;h2&gt;2026 年 2 月：Flare 全栈博客&lt;/h2&gt;&lt;p&gt;这是我在 bilibili 偶然刷到的，博客叫&lt;a href=&quot;https://github.com/du2333/flare-stack-blog&quot;&gt;Flare Stack Blog&lt;/a&gt;，可谓是把白嫖玩到了极致，用到了 Cloudflare 很多服务。&lt;/p&gt;&lt;figure&gt;&lt;table&gt;&lt;colgroup&gt;&lt;col /&gt;&lt;col /&gt;&lt;/colgroup&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;服务&lt;/td&gt;&lt;td&gt;用途&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Workers&lt;/td&gt;&lt;td&gt;边缘计算与托管&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;D1&lt;/td&gt;&lt;td&gt;SQLite 数据库&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;R2&lt;/td&gt;&lt;td&gt;对象存储（媒体文件）&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;KV&lt;/td&gt;&lt;td&gt;缓存层&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Durable Objects&lt;/td&gt;&lt;td&gt;分布式限流&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Workflows&lt;/td&gt;&lt;td&gt;异步任务（内容审核、定时发布）&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Queues&lt;/td&gt;&lt;td&gt;消息队列（邮件通知）&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Workers AI&lt;/td&gt;&lt;td&gt;AI 能力&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Images&lt;/td&gt;&lt;td&gt;图片优化&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/figure&gt;&lt;p&gt;这个博客的默认主题还是很吸引我的，这种简约的风格让我眼前一亮，这也为我后续的博客 UI 打下了一定的基础。&lt;/p&gt;&lt;figure&gt;&lt;img src=&quot;https://huowenlong.com/trilium-assets/vvEmuVooTDii/f06765993e01a6ad.png&quot; width=&quot;2000&quot; height=&quot;1290&quot; /&gt;&lt;/figure&gt;&lt;p&gt;这个项目写文章很方便，有自己的后台系统，甚至还接入了 MCP 服务了，我放弃它的理由只有一点：不利于我后续的扩展。我当时就是想打通一套自动化流程，以 Open Minis 为中转，又不想完全放弃掉 Trilium，并且当时我为这个博客集成了 Memos 说说功能，解耦性强，对这个博客是非侵入式的，但是渐渐的我发现我想扩展的功能越来越多，不得不对源项目进行大量的修改，所以我放弃了这个博客，决定借助 Vibe Coding 开始搭建博客。&lt;/p&gt;&lt;h2&gt;2026 年 3 月：Astro 博客项目&lt;/h2&gt;&lt;p&gt;最近时间，我突然就迷上了 Vibe Coding了，可能是以 Open Minis 为契机，这种编码真是太方便了，我有些编程基础，会基础的 debug，配合着编码真是太方便了，越用越上头，在我做别的事情的功夫，就可以把功能给实现，而且实现的比较完美。这个 Astro 博客项目 longBlog 是我全程指导 AI（使用 ChatGPT 5.4/5.3 Codex） 完成的，包括整个自动化流，整体架构比较复杂。之后我单开一篇专门介绍 longBlog。&lt;/p&gt;&lt;figure&gt;&lt;img src=&quot;https://huowenlong.com/trilium-assets/vvEmuVooTDii/3decb01936900361.png&quot; width=&quot;2000&quot; height=&quot;1305&quot; /&gt;&lt;/figure&gt;&lt;figure&gt;&lt;img src=&quot;https://huowenlong.com/trilium-assets/vvEmuVooTDii/b493bf5b3b6753e0.png&quot; width=&quot;2000&quot; height=&quot;1296&quot; /&gt;&lt;/figure&gt;</content:encoded></item><item><title>基于tlock和cryptomator的时间胶囊</title><link>https://huowenlong.com/blog/dDI26xYqquzY/</link><guid isPermaLink="true">https://huowenlong.com/blog/dDI26xYqquzY/</guid><description>把“现在就加密、到期才能解密”的 tlock 与 Cryptomator 组合起来做一颗数字时间胶囊：邮件加加密存储、延迟开启，让今天写下的内容只能在约定的未来被读到。</description><pubDate>Sun, 29 Mar 2026 10:14:50 GMT</pubDate><content:encoded>&lt;h2&gt;前言&lt;/h2&gt;&lt;blockquote&gt;&lt;p&gt;“时间胶囊”不是封存过去，而是把现在寄往未来。&lt;/p&gt;&lt;/blockquote&gt;&lt;p&gt;有想过给未来的自己留下一份特别的礼物吗？这一份礼物跨越时间，承载着时间的厚重感。那么不妨尝试制作一颗属于自己独特回忆的“时间胶囊”。&lt;/p&gt;&lt;h2&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;li&gt;数字时间胶囊&lt;/li&gt;&lt;li&gt;…&lt;/li&gt;&lt;/ul&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;strong&gt;邮件 + 加密存储 + 延迟打开&lt;/strong&gt;&lt;/p&gt;&lt;figure&gt;&lt;img src=&quot;https://huowenlong.com/trilium-assets/dDI26xYqquzY/0a16ebd17afa74a1.png&quot; width=&quot;1369&quot; height=&quot;1042&quot; /&gt;&lt;/figure&gt;&lt;p&gt;在介绍这个之前，我需要先补充一些前置知识。&lt;/p&gt;&lt;h2&gt;前置知识&lt;/h2&gt;&lt;h3&gt;&lt;a href=&quot;https://github.com/drand/tlock&quot;&gt;tlock&lt;/a&gt;&lt;/h3&gt;&lt;h4&gt;项目介绍&lt;/h4&gt;&lt;p&gt;tlock 是 drand 团队开源的一个 时间锁定加密（Timelock Encryption）工具库和命令行工具 。它的核心理念是：&lt;/p&gt;&lt;blockquote&gt;&lt;p&gt;现在就加密，未来才能解密 -- 你可以指定一个未来的时间点（或 round 轮次），只有到达那个时间点之后，数据才能被解密。简单来说，就像把秘密锁进一个「​时间保险箱​」，设定好时间，到期才能打开 。&lt;/p&gt;&lt;/blockquote&gt;&lt;p&gt;简单来说，就像把秘密锁进一个「​时间保险箱​」，设定好时间，到期才能打开。&lt;/p&gt;&lt;h4&gt;核心技术原理&lt;/h4&gt;&lt;p&gt;tlock 的安全型建立在四层密码学机制之上：&lt;/p&gt;&lt;figure&gt;&lt;table&gt;&lt;colgroup&gt;&lt;col /&gt;&lt;/colgroup&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;tlock 四层安全架构  &lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;第4层: drand 阈值签名网络 (League of Entropy)               &lt;br /&gt;- 定期生成不可预测的随机数                            &lt;br /&gt;- BLS 阈值签名，每轮由多个节点协作产生                 &lt;br /&gt;- 3秒产生一轮签名    &lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;第3层: IBE 身份加密 (基于 Boneh-Franklin 2001方案)         &lt;br /&gt;- 用未来 round 的签名作为&quot;身份标识&quot;                 &lt;br /&gt;- 只有知道该轮签名的才能解密  &lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt; 第2层: age 混合加密                                         &lt;br /&gt;- 生成随机 DEK (数据加密密钥)                        &lt;br /&gt;- 用 IBE 只&quot;时间锁定&quot;这个 DEK                        &lt;br /&gt;- 用 DEK + ChaCha20Poly1305 实际加密数据   &lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;第1层: BLS 签名方案 (Boneh-Lynn-Shacham 2003)              - G1/G2 椭圆曲线群                                   &lt;br /&gt;- 支持多种签名方案 (ShortSig, Unchained, SigsOnG1)&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/figure&gt;&lt;p&gt;通俗的加密解密流程：&lt;/p&gt;&lt;figure&gt;&lt;img src=&quot;https://huowenlong.com/trilium-assets/dDI26xYqquzY/524e46edab65dc13.png&quot; alt=&quot;https://mermaid.ink/img/pako:eNqdVF1P21YY_itHB1VttZDZOf7WhESgF9OkXUy92rILJz4maE6MTKKOUaTCSsO6kgTISGFoha2wbi2EdROo6Rr-C8pxkrv9hB372A7ucjER5cLv8fu8H8_zHC_CnG1gqEHTsu_l8rpTAnenM0VAfzdugElrNocBebxPWo96L1vsfJL_IgP_edbYBP3zVrfd7jVeg_7RMsv6KOt8OOE-rbrbFQ3czGPLssE927GMmxn4ZYBP-fjqa9BrPHPX6oPdmrvXHmwekic7YPrOJ36JWygFyHGz_3jldgQM4IjCL3cPKPqFlw4-AFN5nf5TnI9kc7g_nLrrJ-Dy0Sbwou0KmOKHEwj-BFsHgNQ3gOHoRQP0W-fuHytk9RUdZJgoer1qp8B9UiEnu70fT9z9CvjMLlPAp2xOt9UgRyuA5wB5_hupV3u7D_vvTtxXB8O5JyVGWN0fmbWgU_tlWJWP03cCliMCvMkHjeXuuxqt6J2-T4PMdjiiO6yRN2f9zhaptDVGgb_xFE-bRCUYU6zncDKFFXkOSG1j8GC593YnELX7ZifYr7k_2Hvgfr9KLr7t__zSXfu9Xz1nu0U26f_6y1WDpJlBtg6HtYDbOCNrp4E225Xu27NoiHTKJ_lP0Dv-jnRW3ebZoPkXTe53OslkcpiGGIvroFc9JXsv3KetUAnQbTPjMRFDRavnpLbdO-6Q-vqwCpO-seVpwd6x6SPiqRbJaZxzFuZKt1hCIsbi7Uge0mnSMWPapEVWfiNyJ6vO7OgjQ7NGTWhWgrrzP3Vjtwdc7q2-5wCP-Iuf3Ophr30R3kwwPj5BL1h40ViI4qEQXgIWSqHV4yFioRxaOB7KLFRijlTAeHJ84n4GUiGDC5GB96m-oc4-KM2HLmEhCuVloRDqxELxis3c_XPyd42F86UFC3sLm7OWpY3JQlY1zUTOtmxHGzPp43zJsb_C2hinKgJGMVAqAJlGLivhEMRxXATCvMyJYgyEApCeUrMmHtVJyok5LMdAQgBSeJzDuVGdOI6-UGMgMRzPlCVZHNXJkBCH-BhIinaSVV0ZBcKKgNQ4EXIAShlIQNLInZCE5VQMpAQgjssqqjCSck5XpOxVazBo-jpqpSO1TKzr8igOmY4xELoG8WnhGhymxf9JB0zAGWfWgFrJKeMELGCnoHshXPTKZWApjws4AzX6aGBTL1ulDMwUlyhsTi9-btuFEOnY5Zk81EzdmqdRec7QS3h6Vp9x9EJ06uCigZ0p-nUsQU1UkF8Eaovwa6jxipqUOBkJoqjwnCjICbhAT0UpiTie5yVVQEhUkbKUgN_4bbmkqIi8iAReFWRVkUV-6V9cIQYI?type=png&quot; width=&quot;433&quot; height=&quot;909&quot; /&gt;&lt;/figure&gt;&lt;h4&gt;drand 网络端点&lt;/h4&gt;&lt;figure&gt;&lt;table&gt;&lt;colgroup&gt;&lt;col /&gt;&lt;col /&gt;&lt;/colgroup&gt;&lt;tbody&gt;&lt;tr&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;https://api.drand.sh/&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;🇪🇺 欧洲&lt;/td&gt;&lt;td&gt;https://api2.drand.sh/&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;🌏 亚洲&lt;/td&gt;&lt;td&gt;https://api3.drand.sh/&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;🌐 全球负载均衡&lt;/td&gt;&lt;td&gt;https://drand.cloudflare.com/&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/figure&gt;&lt;blockquote&gt;&lt;p&gt;⚠️ 目前主网唯一支持时间锁定的链：​ Chainhash：52db9ba70e0cc0f6eaf7803dd07447a1f5477735fd3f661792ba94600c84e971  (quicknet，3秒/轮，G1签名)&lt;/p&gt;&lt;p&gt;这是一个已准备好投入生产环境且具备高可用性保障的网络。它被 drand 团队认定为完全安全，并由自 2019 年以来一直负责 drand 生产环境运行的同一家联盟 Entropy 运营。&lt;/p&gt;&lt;/blockquote&gt;&lt;p&gt;关于更多的数学理论相关的介绍，请参照这篇论文：&lt;a href=&quot;https://eprint.iacr.org/2023/189&quot;&gt;《tlock: Practical Timelock Encryption from Threshold BLS》&lt;/a&gt;&lt;/p&gt;&lt;h3&gt;&lt;a href=&quot;https://github.com/cryptomator&quot;&gt;Cryptomator &lt;/a&gt;&lt;/h3&gt;&lt;p&gt;这个在我之前的博客中已经介绍过了。请参照这篇《使用 Cryptomator：为云存储提供零知识加密》。&lt;/p&gt;&lt;a href=&quot;https://huowenlong.com/blog/2epdE8sgosX8/&quot;&gt;&lt;span&gt;相关文章&lt;/span&gt;&lt;span&gt;使用 Cryptomator：为云存储提供零知识加密&lt;/span&gt;&lt;span&gt;随着云存储服务的普及，个人数据被存储在云端，带来了便利，但也引发了数据安全问题。云服务商理论上可以访问用户数据，即使进行了加密，服务商也可能拥有解密密钥。为了实现真正的零知识存储，需要在上传前进行本地加密。Cryptomator是一款开源的客户端加密软件，用于在上传到云存储之前对文件进行加密，支持主流云存储和多平台。它采用客户端加密、零知识架构和开源透明的方式，确保云服务商只能看到加密后的文件。Cryptomator使用现代加密算法保护数据，包括AES-256、Scrypt和GCM模式，并对文件名进行加密。用户可以创建Vault，设置密码，解锁Vault，并自动同步加密后的文件。&lt;/span&gt;&lt;/a&gt;&lt;h2&gt;使用教程&lt;/h2&gt;&lt;h3&gt;创建一个文件夹存储你的文字或图片或视频&lt;/h3&gt;&lt;figure&gt;&lt;img src=&quot;https://huowenlong.com/trilium-assets/dDI26xYqquzY/3d88934b7b03ce93.png&quot; width=&quot;1840&quot; height=&quot;984&quot; /&gt;&lt;/figure&gt;&lt;h3&gt;下载 Cryptomator 保险箱并加密文件&lt;/h3&gt;&lt;p&gt;最好是存入一个单独的硬盘或者网盘，我以 iCloud 为例，Cryptomator支持的存储方式很多，按需选取。&lt;/p&gt;&lt;figure&gt;&lt;img src=&quot;https://huowenlong.com/trilium-assets/dDI26xYqquzY/880e06e1df2506cb.png&quot; width=&quot;1864&quot; height=&quot;1110&quot; /&gt;&lt;/figure&gt;&lt;figure&gt;&lt;img src=&quot;https://huowenlong.com/trilium-assets/dDI26xYqquzY/a86b70b1df857564.png&quot; width=&quot;900&quot; height=&quot;956&quot; /&gt;&lt;/figure&gt;&lt;p&gt;之后一直下一步，在设置密码界面，可以通过密码生成器去设置一个自己都无法记住的强密码（这个强密码之后需托管到 drand 网络）。之后点击创建保险库，这样，你的内容将被端到端加密，除了密码所有者，任何人无法查看。&lt;/p&gt;&lt;figure&gt;&lt;img src=&quot;https://huowenlong.com/trilium-assets/dDI26xYqquzY/fd03972a89b6a01e.png&quot; width=&quot;1826&quot; height=&quot;1326&quot; /&gt;&lt;/figure&gt;&lt;h3&gt;将密码托管到 drand 网络&lt;/h3&gt;&lt;h4&gt;安装 CLI 工具 tle&lt;/h4&gt;&lt;pre&gt;&lt;code&gt;# 方式一：Go 安装（推荐）
go install github.com/drand/tlock/cmd/tle@latest

# 方式二：源码编译
git clone https://github.com/drand/tlock
cd tlock
go build cmd/tle/tle.go&lt;/code&gt;&lt;/pre&gt;&lt;pre&gt;&lt;code&gt;    短参数 长参数    说明
    -e  --encrypt  加密模式（默认，不写也生效） 
	-d  --decrypt  解密模式
	-n  --network  drand 网络地址
	-c  --chain    链哈希值（验证公钥完整性）  
	-r  --round    指定具体的轮次号 
	-f  --force    强制加密到过去的轮次
	-D  --duration 等待时长（多久后才能解密） 
	-o  --output   输出文件路径
	-a  --armor    PEM 文本格式输出&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;基础加密：&lt;/p&gt;&lt;pre&gt;&lt;code&gt;# 语法
tle -D &amp;lt;时间&amp;gt; -o &amp;lt;输出文件&amp;gt; &amp;lt;输入文件&amp;gt;

# 示例：加密文件，10天后才能解密
tle -D 10d -o secret secret.txt&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;基础解密：&lt;/p&gt;&lt;pre&gt;&lt;code&gt;# 语法
tle -d -o &amp;lt;输出文件&amp;gt; &amp;lt;加密文件&amp;gt;

# 示例：解密文件
tle -d -o original.txt secret.pem&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;详细见官方 Github 仓库：https://github.com/drand/tlock&lt;/p&gt;&lt;h4&gt;加密/解密文件演示&lt;/h4&gt;&lt;p&gt;在下载的本地的文件夹以终端的形式打开，举个最简单的例子：&lt;/p&gt;&lt;p&gt;我有一个内容为 Hello world 的 test.txt 文件，我进行加密（-a 为选择以 pem 格式输出，因为方便文本传输、兼容性更好）：&lt;/p&gt;&lt;pre&gt;&lt;code&gt;# 将 test.txt 文件加密成 .pem 格式的密文，30s后密文才能解密，否则报错
tle -a -e -D 30s -o secret.pem test.txt
# 将 secret.pem 文件进行解密
tle -d secret.pem&lt;/code&gt;&lt;/pre&gt;&lt;figure&gt;&lt;img src=&quot;https://huowenlong.com/trilium-assets/dDI26xYqquzY/224e57094941b1a9.png&quot; width=&quot;1098&quot; height=&quot;1030&quot; /&gt;&lt;/figure&gt;&lt;p&gt;如果没有到时间就解锁，会显示 too early to decrypt: expected round 27240614 &amp;gt; 27240610 current round 。&lt;/p&gt;&lt;p&gt;我们将解锁 Cryptomator 的强密码放进 TXT 文本中，进行加密，然后将原来的密码和 TXT 文件全部彻底删除，那么这个“时间胶囊”也就真正封存住，不到指定时间打不开。&lt;strong&gt;如果只是存些文本语言的话，tlock 就足够了，就不需要 Cryptomator ，如果涉及到图片视频等，建议结合使用。&lt;/strong&gt;&lt;/p&gt;&lt;h2&gt;设置定时发送邮件用于提醒&lt;/h2&gt;&lt;p&gt;选择邮件商比如 QQ 、 Gmail 等等，邮件选择发送时间，随便输入文本，只起到一个提醒即可。到那个时候，你就通过命令输入，得到密码，就可以打开保险箱了（如果怕忘了怎么操作，可以写个简短的教程）。&lt;/p&gt;&lt;figure&gt;&lt;img src=&quot;https://huowenlong.com/trilium-assets/dDI26xYqquzY/8cae771801b8e5ae.png&quot; width=&quot;1130&quot; height=&quot;206&quot; /&gt;&lt;/figure&gt;</content:encoded></item><item><title>近期让我眼前一亮的软件</title><link>https://huowenlong.com/blog/KyFrU3uItyRq/</link><guid isPermaLink="true">https://huowenlong.com/blog/KyFrU3uItyRq/</guid><description>介绍 iOS 本地 AI Agent 应用 Open Minis：模型跑在手机本地、无需额外注册云端账号，自带 Shell 与浏览器工具，作者用它完成 NAS 文件传输、语音合成等实际任务。</description><pubDate>Wed, 11 Mar 2026 11:00:21 GMT</pubDate><content:encoded>&lt;h2&gt;Open Minis：iOS上的本地AI Agent应用&lt;/h2&gt;&lt;blockquote&gt;&lt;p&gt;一款可以在iPhone上本地运行的AI Agent应用，无需注册额外云端账号，无需添置额外设备&lt;/p&gt;&lt;/blockquote&gt;&lt;h2&gt;应用简介&lt;/h2&gt;&lt;p&gt;&lt;strong&gt;Open Minis&lt;/strong&gt; 是一款创新的iOS本地AI Agent应用，它将人工智能助手从单纯的聊天工具提升为能够真正执行任务的智能代理。不同于传统的AI聊天机器人，Minis为AI模型提供了本地工具——包括Shell环境和Web浏览器，使其能够自主完成多步骤任务。&lt;/p&gt;&lt;h3&gt;核心定位&lt;/h3&gt;&lt;ul&gt;&lt;li&gt;&lt;strong&gt;本地运行&lt;/strong&gt;：所有AI处理在iPhone本地完成，保护用户隐私&lt;/li&gt;&lt;li&gt;&lt;strong&gt;无需注册&lt;/strong&gt;：不需要额外的云端账号，直接在设备上使用&lt;/li&gt;&lt;li&gt;&lt;strong&gt;真正常驻&lt;/strong&gt;：不像传统AI需要每次重新输入上下文&lt;/li&gt;&lt;/ul&gt;&lt;h3&gt;内置工具能力&lt;/h3&gt;&lt;p&gt;根据应用提供的能力，Minis集成了丰富的系统功能：&lt;/p&gt;&lt;figure&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;Shell&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;执行本地Linux命令，管理文件，运行脚本&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;strong&gt;Browser&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;发送电子邮件（支持Resend等SMTP服务）&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;HomeKit&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;tr&gt;&lt;td&gt;&lt;strong&gt;NLP&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;/figure&gt;&lt;hr /&gt;&lt;h2&gt;真实使用场景&lt;/h2&gt;&lt;p&gt;以下是我与用户在实际对话中使用Minis完成的各种任务，这些场景充分展示了Open Minis作为本地AI Agent的强大能力：&lt;/p&gt;&lt;h3&gt;场景一：NAS文件传输与管理&lt;/h3&gt;&lt;p&gt;&lt;strong&gt;需求&lt;/strong&gt;：用户需要将文件发送到NAS（网络附加存储）&lt;/p&gt;&lt;p&gt;&lt;strong&gt;执行方式&lt;/strong&gt;：&lt;/p&gt;&lt;ul&gt;&lt;li&gt;通过WebDAV协议连接用户的NAS&lt;/li&gt;&lt;li&gt;自动将文件上传到指定的同步目录（默认为&lt;code&gt;sync&lt;/code&gt;文件夹）&lt;/li&gt;&lt;li&gt;支持自定义上传路径&lt;/li&gt;&lt;/ul&gt;&lt;pre&gt;&lt;code&gt;&lt;span&gt;# 示例：将本地文件上传到NAS&lt;/span&gt;
curl -T localfile.txt https://nas.example.com/sync/&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;strong&gt;用户偏好&lt;/strong&gt;：默认存入&lt;code&gt;sync&lt;/code&gt;文件夹，除非用户指定其他路径&lt;/p&gt;&lt;hr /&gt;&lt;h3&gt;场景二：智能语音合成&lt;/h3&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;使用edge-tts引擎进行语音合成&lt;/li&gt;&lt;li&gt;支持多种语音音色&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;&lt;strong&gt;用户偏好&lt;/strong&gt;：必须使用&lt;code&gt;zh-CN-XiaoyiNeural&lt;/code&gt;音色（中文女声），这是用户指定的默认语音&lt;/p&gt;&lt;hr /&gt;&lt;h3&gt;场景三：邮件自动发送&lt;/h3&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;集成Resend邮件服务&lt;/li&gt;&lt;li&gt;自动配置发送方地址&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;&lt;strong&gt;用户偏好&lt;/strong&gt;：发送邮件时使用&lt;code&gt;minlonghuo@ssaw.top&lt;/code&gt;作为发件人（如已验证）&lt;/p&gt;&lt;hr /&gt;&lt;h3&gt;场景四：智能闹钟管理&lt;/h3&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;调用iOS AlarmKit API&lt;/li&gt;&lt;li&gt;支持一次性或重复闹钟&lt;/li&gt;&lt;li&gt;可设置懒人闹钟（需要解题才能关闭）&lt;/li&gt;&lt;/ul&gt;&lt;pre&gt;&lt;code&gt;// 示例：通过Apple AlarmKit设置闹钟
apple-alarm set --time &quot;07:00&quot; --label &quot;起床&quot; --repeat weekdays&lt;/code&gt;&lt;/pre&gt;&lt;hr /&gt;&lt;h3&gt;场景五：健康数据记录&lt;/h3&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;集成Apple HealthKit&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;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;pre&gt;&lt;code&gt;&lt;span&gt;# 示例：记录今日步数&lt;/span&gt;
apple-healthkit &lt;span&gt;log&lt;/span&gt; --&lt;span&gt;type&lt;/span&gt; stepCount --value 8000&lt;/code&gt;&lt;/pre&gt;&lt;hr /&gt;&lt;h3&gt;场景六：智能家居控制&lt;/h3&gt;&lt;p&gt;&lt;strong&gt;需求&lt;/strong&gt;：通过语音或文字控制HomeKit设备&lt;/p&gt;&lt;p&gt;&lt;strong&gt;执行方式&lt;/strong&gt;：&lt;/p&gt;&lt;ul&gt;&lt;li&gt;扫描并连接HomeKit设备&lt;/li&gt;&lt;li&gt;支持控制灯光、空调、门锁、摄像头等&lt;/li&gt;&lt;/ul&gt;&lt;pre&gt;&lt;code&gt;&lt;span&gt;# 示例：查看可用设备&lt;/span&gt;
apple-homekit list

&lt;span&gt;# 示例：控制灯光开关&lt;/span&gt;
apple-homekit &lt;span&gt;set&lt;/span&gt; --name &lt;span&gt;&quot;客厅灯&quot;&lt;/span&gt; --characteristic powerState --value &lt;span&gt;true&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;hr /&gt;&lt;h3&gt;场景七：地理位置与地图服务&lt;/h3&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;集成Apple Maps&lt;/li&gt;&lt;li&gt;支持POI搜索、路线规划、ETA计算&lt;/li&gt;&lt;/ul&gt;&lt;pre&gt;&lt;code&gt;&lt;span&gt;# 示例：查找附近的咖啡厅&lt;/span&gt;
apple-maps find &lt;span&gt;&quot;咖啡厅&quot;&lt;/span&gt; near &lt;span&gt;&quot;current&quot;&lt;/span&gt;

&lt;span&gt;# 示例：计算到某地的驾车时间&lt;/span&gt;
apple-maps eta --from &lt;span&gt;&quot;current&quot;&lt;/span&gt; --to &lt;span&gt;&quot;公司地址&quot;&lt;/span&gt; --mode driving&lt;/code&gt;&lt;/pre&gt;&lt;hr /&gt;&lt;h3&gt;场景八：浏览器自动化&lt;/h3&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;/ul&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;/ul&gt;&lt;hr /&gt;&lt;h3&gt;场景九：文件与脚本处理&lt;/h3&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;通过Shell环境执行命令&lt;/li&gt;&lt;li&gt;支持Python、Node.js等多种脚本语言&lt;/li&gt;&lt;li&gt;可处理JSON、CSV、文本等数据格式&lt;/li&gt;&lt;/ul&gt;&lt;hr /&gt;&lt;h3&gt;场景十：提醒事项与日历管理&lt;/h3&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;/ul&gt;&lt;hr /&gt;&lt;h2&gt;与传统AI助手的对比&lt;/h2&gt;&lt;figure&gt;&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;特性&lt;/th&gt;&lt;th&gt;传统AI聊天机器人&lt;/th&gt;&lt;th&gt;Open Minis&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;云端存储&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;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;可控制iPhone&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;/figure&gt;&lt;hr /&gt;&lt;h2&gt;如何安装&lt;/h2&gt;&lt;h3&gt;安装前准备&lt;/h3&gt;&lt;ol&gt;&lt;li&gt;&lt;strong&gt;设备要求&lt;/strong&gt;：iPhone或iPad运行iOS 16或更高版本&lt;/li&gt;&lt;li&gt;&lt;strong&gt;安装TestFlight&lt;/strong&gt;：需要在App Store下载TestFlight应用&lt;/li&gt;&lt;/ol&gt;&lt;h3&gt;安装步骤&lt;/h3&gt;&lt;ol&gt;&lt;li&gt;点击TestFlight邀请链接：&lt;a href=&quot;https://testflight.apple.com/join/3BdkA5c3&quot;&gt;Open Minis TestFlight&lt;/a&gt;&lt;/li&gt;&lt;li&gt;在Safari中打开链接，点击「接受」或「安装」&lt;/li&gt;&lt;li&gt;打开TestFlight应用，找到「Open Minis」&lt;/li&gt;&lt;li&gt;点击「安装」下载并安装应用&lt;/li&gt;&lt;/ol&gt;&lt;h3&gt;开始使用&lt;/h3&gt;&lt;p&gt;安装完成后，你可以：&lt;/p&gt;&lt;ul&gt;&lt;li&gt;直接在应用中与AI助手对话&lt;/li&gt;&lt;li&gt;授权需要的系统权限（健康、位置、通知等）&lt;/li&gt;&lt;li&gt;开始体验本地AI Agent的强大能力&lt;/li&gt;&lt;/ul&gt;&lt;hr /&gt;&lt;h2&gt;总结&lt;/h2&gt;&lt;p&gt;Open Minis代表了移动AI助手的未来方向——&lt;strong&gt;不仅能说能做&lt;/strong&gt;。它将AI的思考能力与iOS系统的丰富功能相结合，让用户能够通过自然语言完成各种复杂任务，同时确保数据隐私安全。&lt;/p&gt;&lt;p&gt;无论是管理NAS文件、发送邮件、设置闹钟，还是控制智能家居、记录健康数据，Open Minis都能成为你iPhone上的得力AI助手。&lt;/p&gt;&lt;hr /&gt;&lt;blockquote&gt;&lt;p&gt;📝 本文基于实际使用场景编写，记录了Open Minis的多种应用方式。&lt;/p&gt;&lt;p&gt;如果你有更多使用场景想要分享，欢迎与我交流！&lt;/p&gt;&lt;/blockquote&gt;</content:encoded></item><item><title>我的保研之旅</title><link>https://huowenlong.com/blog/4t7DdbbFdP02/</link><guid isPermaLink="true">https://huowenlong.com/blog/4t7DdbbFdP02/</guid><description>双非统计学专业、排名 1/35 的作者完整记录保研之路：夏令营全部落空，预推免辗转苏州大学、南京理工与东南大学，在候补和被拒之间最终落定，也写下对选择与未知的思考。</description><pubDate>Mon, 09 Mar 2026 11:04:33 GMT</pubDate><content:encoded>&lt;h2&gt;前言&lt;/h2&gt;&lt;p&gt;昨天同学找我聊了会天，他正处于人生道路的分叉口，目前考研失利。想着这么一条路：考教资然后辅导班老师，接着私立学校老师，最后到南方大城市私立学校老师；还想着这么一条路：直接参军，还能在兵营里锻炼锻炼身体和意志；又想着这么一条路：再二战研究生。他甚至还没真正做出选择，就开始看衰这一条道路，看好另一条道路。可是哪条路美好呢，都是未知罢了。聊着聊着，我就想到了我自己在保研路上的选择，我也总是在美化自己没走过的道路。。。借此机会，我打算把我整个保研过程的经历回忆整理并记录下来，再过十年甚至是二十年再看，会是什么想法呢？&lt;/p&gt;&lt;h2&gt;个人背景&lt;/h2&gt;&lt;p&gt;某双非统计学专业，平均绩点4.02/5，专业和综合排名1/35。四六级通过，四级算是高分通过。一国奖、若干省奖校奖（大多是数学建模竞赛）、有一个计算机软著还有其它的奖学金、荣誉称号啥的杂七杂八。&lt;/p&gt;&lt;h2&gt;2025.05 - 2025.08（夏令营）&lt;/h2&gt;&lt;p&gt;大部分学校会在这段时间发布夏令营通知。我的夏令营的结果可以说是很惨败，一个都没入。也许是自己有点好高骛远了，报的学校都太好了，并且都是线下的，没有线上的（线上大海营更容易进）。&lt;/p&gt;&lt;figure&gt;&lt;img src=&quot;https://huowenlong.com/trilium-assets/4t7DdbbFdP02/729c8a0fd5cf4581.png&quot; width=&quot;1906&quot; height=&quot;502&quot; /&gt;&lt;/figure&gt;&lt;h2&gt;2025.08 - 2025.09（预推免）&lt;/h2&gt;&lt;p&gt;这段时间，大部分学校会开放预推免，有少部分学校没有预推免，可能夏令营就已经确定招满了。预推免相当于在夏令营的基础上再次招生了，整体要求会放宽，再加上到时鸽子满天飞，学校招生办可能就达不到最低的招生指标了。我的预推免结果整体也符合预期了。&lt;/p&gt;&lt;figure&gt;&lt;img src=&quot;https://huowenlong.com/trilium-assets/4t7DdbbFdP02/d56eeabb9befbd48.png&quot; width=&quot;2000&quot; height=&quot;1262&quot; /&gt;&lt;/figure&gt;&lt;p&gt;其中绿色代表通过，红色代表拒绝，无色代表默拒。 &lt;/p&gt;&lt;p&gt;接下来，我会按照时间顺序娓娓道来我的预推免经历。&lt;/p&gt;&lt;h3&gt;苏州大学（2025.09.10 - 2025.09.12）&lt;/h3&gt;&lt;p&gt;我对这个学校有莫名的好感，因为它是第一个给我发邮件、让我去线下参加面试的学校。&lt;/p&gt;&lt;figure&gt;&lt;img src=&quot;https://huowenlong.com/trilium-assets/4t7DdbbFdP02/8d90b1c7e716ba97.png&quot; width=&quot;1140&quot; height=&quot;1044&quot; /&gt;&lt;/figure&gt;&lt;p&gt;当时，和我一块保研的同学，他们都没有收到参加预推免面试的邮件，所以当时收到邮件的第一反应是特别惊喜，我之前甚至一度认为是不是我的邮箱填错了，怎么没有收到邮件🥲。由于是第一次去这个陌生的城市，我开始规划路线，通过车票、机票、时间、距离进行综合分析。&lt;/p&gt;&lt;figure&gt;&lt;img src=&quot;https://huowenlong.com/trilium-assets/4t7DdbbFdP02/5a34bfd5732d2b18.jpg&quot; width=&quot;1080&quot; height=&quot;1397&quot; /&gt;&lt;/figure&gt;&lt;p&gt;得到了我自己认为的最佳路线，最后和 ChatGPT 进行比对，验证了我的想法（所以当时为什么不直接问 ChatGPT 呢😳）。路线确定好了，开始复习专业课和英语面试。&lt;/p&gt;&lt;figure&gt;&lt;img src=&quot;https://huowenlong.com/trilium-assets/4t7DdbbFdP02/2d1bef8e99810900.jpg&quot; width=&quot;1920&quot; height=&quot;1080&quot; /&gt;&lt;/figure&gt;&lt;p&gt;在走之前，和同学吃的最后一顿好的。这烤鱼是真豪赤啊😋。&lt;/p&gt;&lt;figure&gt;&lt;img src=&quot;https://huowenlong.com/trilium-assets/4t7DdbbFdP02/741377c8a8618598.jpg&quot; width=&quot;1440&quot; height=&quot;810&quot; /&gt;&lt;/figure&gt;&lt;p&gt;上图是在走之前吃的最后一顿晚饭😭。&lt;/p&gt;&lt;p&gt;还记得当时一大早七点多就出发了，我的室友都还在睡觉😴，整个时间还是比较赶的。下面是途中的一些照片。&lt;/p&gt;&lt;figure&gt;&lt;img src=&quot;https://huowenlong.com/trilium-assets/4t7DdbbFdP02/3819bf86403930e8.png&quot; width=&quot;948&quot; height=&quot;1606&quot; /&gt;&lt;/figure&gt;&lt;figure&gt;&lt;img src=&quot;https://huowenlong.com/trilium-assets/4t7DdbbFdP02/dfbc0b2782192c86.jpg&quot; width=&quot;1920&quot; height=&quot;1080&quot; /&gt;&lt;/figure&gt;&lt;figure&gt;&lt;img src=&quot;https://huowenlong.com/trilium-assets/4t7DdbbFdP02/821aeeac9a3c2568.jpg&quot; width=&quot;1707&quot; height=&quot;1280&quot; /&gt;&lt;/figure&gt;&lt;figure&gt;&lt;img src=&quot;https://huowenlong.com/trilium-assets/4t7DdbbFdP02/7277b9290e084889.jpg&quot; width=&quot;1920&quot; height=&quot;1080&quot; /&gt;&lt;/figure&gt;&lt;figure&gt;&lt;img src=&quot;https://huowenlong.com/trilium-assets/4t7DdbbFdP02/6af1933503b5e262.jpg&quot; width=&quot;1920&quot; height=&quot;1080&quot; /&gt;&lt;/figure&gt;&lt;figure&gt;&lt;img src=&quot;https://huowenlong.com/trilium-assets/4t7DdbbFdP02/ef5714354358ec4c.jpg&quot; width=&quot;1702&quot; height=&quot;1276&quot; /&gt;&lt;/figure&gt;&lt;p&gt;在路途中发生了一件事，我还真是喜欢看热闹。&lt;/p&gt;&lt;figure&gt;&lt;img src=&quot;https://huowenlong.com/trilium-assets/4t7DdbbFdP02/75d65064d9e48bea.png&quot; width=&quot;923&quot; height=&quot;1322&quot; /&gt;&lt;/figure&gt;&lt;p&gt;一路颠簸，终于到了苏大，苏大旁边就有地铁，真好。一出来，感觉好热啊🫠，都九月份了，果然从东北到南方跨度还是有点大的。&lt;/p&gt;&lt;figure&gt;&lt;img src=&quot;https://huowenlong.com/trilium-assets/4t7DdbbFdP02/26b2e014448f0f63.jpg&quot; width=&quot;1920&quot; height=&quot;1080&quot; /&gt;&lt;/figure&gt;&lt;figure&gt;&lt;img src=&quot;https://huowenlong.com/trilium-assets/4t7DdbbFdP02/1a969ca2ed11b588.jpg&quot; width=&quot;1920&quot; height=&quot;1080&quot; /&gt;&lt;/figure&gt;&lt;p&gt;（刚到那的时候，还遇到了一件特别巧的事情：居然在校门口遇到了同一协会的学长，他是考研到苏大的计算机学院。有时感觉，世界真小。）&lt;/p&gt;&lt;p&gt;面试的时间也不算长，主要就是等待的时间长。有三场面试：专业面、综合面、英语面。整体还是比较轻松的，自我感觉良好哈哈。&lt;/p&gt;&lt;blockquote&gt;&lt;p&gt;最后结果，只是第二批候补🤡，但是按照往年的惯例的话，这候补很大概率就是能进，毕竟，最后鸽子真的是满天飞。&lt;/p&gt;&lt;/blockquote&gt;&lt;p&gt;之后面试完就没有什么事了，稍微逛了逛周边，拍了一些照片，苏州还是很美的。&lt;/p&gt;&lt;figure&gt;&lt;img src=&quot;https://huowenlong.com/trilium-assets/4t7DdbbFdP02/fc559e18c75ed13c.jpg&quot; width=&quot;1920&quot; height=&quot;1080&quot; /&gt;&lt;/figure&gt;&lt;figure&gt;&lt;img src=&quot;https://huowenlong.com/trilium-assets/4t7DdbbFdP02/d8ca1449583929f8.jpg&quot; width=&quot;1280&quot; height=&quot;1707&quot; /&gt;&lt;/figure&gt;&lt;figure&gt;&lt;img src=&quot;https://huowenlong.com/trilium-assets/4t7DdbbFdP02/9df0e44532cb06dd.jpg&quot; width=&quot;1280&quot; height=&quot;1707&quot; /&gt;&lt;/figure&gt;&lt;p&gt;那天晚上和同学（他当时去了哈工程面试）开始聊起了人生，突然看不清读研这条路真正何去何从，这大抵是每个保研人都会经历的情感波折。&lt;/p&gt;&lt;figure&gt;&lt;img src=&quot;https://huowenlong.com/trilium-assets/4t7DdbbFdP02/85b128d93aa6b76a.png&quot; width=&quot;936&quot; height=&quot;1892&quot; /&gt;&lt;/figure&gt;&lt;p&gt;&lt;strong&gt;苏州大学（完结）&lt;/strong&gt;&lt;/p&gt;&lt;h3&gt;南京理工大学（2025.09.13 中午）&lt;/h3&gt;&lt;p&gt;这个是线上面试，特别匆忙，撞上了拍学信网大头照这件事，当时急急忙忙去老校区拍照，拍完后又急急忙忙回新校区找一个合适的地方准备线上面试。&lt;/p&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;h3&gt;东北大学（2025.09.13 晚 - 2025.09.15 早）&lt;/h3&gt;&lt;p&gt;中午参加完南理的面试，下午紧接着坐高铁去沈阳参加东北大学的面试，这次是和另外两位同学一块去面试的。当时到那已经是半下午了，我们先去学校熟悉了一下就回到民宿休息准备第二天面试了。&lt;/p&gt;&lt;figure&gt;&lt;img src=&quot;https://huowenlong.com/trilium-assets/4t7DdbbFdP02/d658f58c0387816a.jpg&quot; width=&quot;2000&quot; height=&quot;1500&quot; /&gt;&lt;/figure&gt;&lt;blockquote&gt;&lt;p&gt;整个面试算是群面，不同去年，一组一组进行面试的。之后面试结果只给了一个分数，没有具体排名。最后录取的时候，按照成绩和招生人数依次录取，很遗憾，没有录取到我。&lt;/p&gt;&lt;/blockquote&gt;&lt;p&gt;上午面完试，下午的时间我们去周围景点逛了逛，去了沈阳中街，去了故宫，人还是蛮多的。&lt;/p&gt;&lt;figure&gt;&lt;img src=&quot;https://huowenlong.com/trilium-assets/4t7DdbbFdP02/086ab0471d062f6e.jpg&quot; width=&quot;1500&quot; height=&quot;2000&quot; /&gt;&lt;/figure&gt;&lt;figure&gt;&lt;img src=&quot;https://huowenlong.com/trilium-assets/4t7DdbbFdP02/3763950a785ce5c9.jpg&quot; width=&quot;2000&quot; height=&quot;1616&quot; /&gt;&lt;/figure&gt;&lt;figure&gt;&lt;img src=&quot;https://huowenlong.com/trilium-assets/4t7DdbbFdP02/52908e3808c48fd9.jpg&quot; width=&quot;2000&quot; height=&quot;1500&quot; /&gt;&lt;/figure&gt;&lt;p&gt;&lt;strong&gt;东北大学（完结）&lt;/strong&gt;&lt;/p&gt;&lt;h3&gt;西安电子科技大学（2025.09.16 午 - 2025.09.17 晚）&lt;/h3&gt;&lt;p&gt;这个是我最终确定去的学校。&lt;/p&gt;&lt;figure&gt;&lt;img src=&quot;https://huowenlong.com/trilium-assets/4t7DdbbFdP02/c086eae522a61b24.jpg&quot; width=&quot;2000&quot; height=&quot;1500&quot; /&gt;&lt;/figure&gt;&lt;p&gt;我和另一位同学一块去参加的面试。很不幸，当时去的时候，阴雨绵绵，我的心情如同这天气一样。我和他，一人一个行李箱，穿着西服，打着伞，走在这泥泞的路上。&lt;/p&gt;&lt;blockquote&gt;&lt;p&gt;当时的面试整体比较紧张，面试也是群面。由于太过紧张，我的英语口语说得结结巴巴，问的某些专业问题也没答上来，本来并不抱有希望，没想到最后我的成绩居然在录取名额内，算是铁 offer ，稍微有些意外。&lt;/p&gt;&lt;/blockquote&gt;&lt;p&gt;&lt;strong&gt;西安电子科技大学（完结）&lt;/strong&gt;&lt;/p&gt;&lt;h3&gt;上海大学（2025.09.17 晚 - 2025.09.18 晚）&lt;/h3&gt;&lt;p&gt;当时下午面试完西电我就赶着去西安机场，19:20 登机，由于整体时间比较赶，需要上交的材料还没有准备好，于是在飞机上我开始填材料。当时到宾馆已经半夜 11 点多了，明天一大早上的面试，那个时候打印店还没开，我抓紧叫了外卖，把打印的材料送过来，那个时候已经半夜 12 点多了，有点燃尽了💔。&lt;/p&gt;&lt;figure&gt;&lt;img src=&quot;https://huowenlong.com/trilium-assets/4t7DdbbFdP02/d75b139aa21e1ec6.png&quot; width=&quot;752&quot; height=&quot;1322&quot; /&gt;&lt;/figure&gt;&lt;p&gt;在路边等外卖的时候，遇到了一只小猫🐱，心中得到了一丝慰藉❤️‍🩹。&lt;/p&gt;&lt;figure&gt;&lt;img src=&quot;https://huowenlong.com/trilium-assets/4t7DdbbFdP02/de691babd4fe609c.jpg&quot; width=&quot;1219&quot; height=&quot;2000&quot; /&gt;&lt;/figure&gt;&lt;p&gt;早上 6 点多起来，洗了个澡，7 点左右出门了，还好宾馆旁边就是地铁，距离本来也不远，十几分钟就到了。&lt;/p&gt;&lt;figure&gt;&lt;img src=&quot;https://huowenlong.com/trilium-assets/4t7DdbbFdP02/12178c739afa4b8c.jpg&quot; width=&quot;1920&quot; height=&quot;1080&quot; /&gt;&lt;/figure&gt;&lt;blockquote&gt;&lt;p&gt;这次面试整体感觉自我良好，对英语的考察比较多。也许是我跨专业的原因，没有问我过多的专业问题。最后结果也是不出所料，直接被录取了，算是个铁 offer 。&lt;/p&gt;&lt;/blockquote&gt;&lt;figure&gt;&lt;img src=&quot;https://huowenlong.com/trilium-assets/4t7DdbbFdP02/c6abeb0818f9f94f.png&quot; width=&quot;1138&quot; height=&quot;736&quot; /&gt;&lt;/figure&gt;&lt;p&gt;上午面试完后，在学校骑车逛了一会，中午吃了个饭，下午就去机场了。&lt;/p&gt;&lt;figure&gt;&lt;img src=&quot;https://huowenlong.com/trilium-assets/4t7DdbbFdP02/a021ba3a271fc5a3.jpg&quot; width=&quot;2000&quot; height=&quot;1500&quot; /&gt;&lt;/figure&gt;&lt;p&gt;&lt;strong&gt;上海大学（完结）&lt;/strong&gt;&lt;/p&gt;&lt;h3&gt;吉林大学（2025.09.18 晚 - 2025.09.20）&lt;/h3&gt;&lt;p&gt;吉大滞留的时间最长，因为涉及到笔试、面试还有机试。当时到吉大的第二天中午收到西电导师的电话，问我去不去，我当时思考了很久，问了家里和同学的意见，最后承诺去西电了，所以在吉大的这两天已经基本上摆烂了。事实上，我报的这个专业由于学生已经放弃的七七八八再加上鸽子飞，竞争压力特别小，已经基本上属于正常笔试面试机试点击就送了。最后从录取结果来看，事实如此，录取率基本达到了97-98% ？当时进面的有谁，录取的基本上就是这些人。&lt;/p&gt;&lt;blockquote&gt;&lt;p&gt;我不知道我做的这个选择是否正确，但我知道，一旦选择了就没法后悔了。&lt;/p&gt;&lt;/blockquote&gt;&lt;figure&gt;&lt;img src=&quot;https://huowenlong.com/trilium-assets/4t7DdbbFdP02/027483946a1fe994.jpg&quot; width=&quot;1280&quot; height=&quot;1707&quot; /&gt;&lt;/figure&gt;&lt;h3&gt;其它&lt;/h3&gt;&lt;p&gt;中南大学、兰州大学、东北师范大学由于时间和精力的原因拒绝了。&lt;/p&gt;&lt;h3&gt;结果&lt;/h3&gt;&lt;figure&gt;&lt;img src=&quot;https://huowenlong.com/trilium-assets/4t7DdbbFdP02/b07a6dd81f6f5928.png&quot; width=&quot;894&quot; height=&quot;782&quot; /&gt;&lt;/figure&gt;&lt;p&gt; &lt;/p&gt;&lt;p&gt;&lt;strong&gt;至此，保研之旅画上句号。&lt;/strong&gt;&lt;/p&gt;&lt;h2&gt;我想说的&lt;/h2&gt;&lt;p&gt;确定录取之后，我有时会不自觉地去想，如果当初没有选择西电，而是填报了其它学校，我现在的人生会不会变得不一样。这个念头其实没有什么明确的起点，只是在某些很普通的时刻突然冒出来。可能是在刷到别人分享大学生活的时候，也可能是在某个安静的晚上，突然想到高考填志愿那几天反复权衡的自己。&lt;/p&gt;&lt;p&gt;我会忍不住去想象，如果当时去了上大、苏大这样的学校，生活或许会是另一种样子。也许会生活在更大的城市里，校园外是更热闹的街道和更多陌生的人；也许会遇到完全不同的一群同学，参加不同的社团，走进不同的教室。甚至连未来的方向，可能都会在某个不经意的瞬间慢慢改变。&lt;/p&gt;&lt;p&gt;有时候这些想象会被我描绘得很美好。好像在那条没有走过的路上，一切都显得更自由、更丰富，也更充满可能。但我也隐约知道，这种感觉其实是一种很常见的错觉。人似乎总是容易去美化那些没有选择的道路，因为那里没有真正经历过的挫折，也没有真实生活中的琐碎与疲惫。&lt;/p&gt;&lt;p&gt;站在已经选择的路上，我们看到的是每天真实的细节：课程、作业、考试，还有那些普通却重复的日子。而没有走过的那条路，却永远停留在想象里，所以看起来总是更闪亮一些。&lt;/p&gt;&lt;p&gt;或许每个人都会在某个阶段这样想过。如果当时做了另一种选择，人生会不会变得更好。但仔细想想，其实很多事情并不是由一所学校、一座城市就决定的。真正塑造我们的，可能还是那些在日复一日中做出的努力、选择和改变。&lt;/p&gt;&lt;p&gt;所以有时候我也会提醒自己，不必太沉迷于那些“如果当初”。那些没有走过的路确实存在，但此刻脚下的这条路，才是我真正能够继续走下去的方向。&lt;/p&gt;</content:encoded></item><item><title>远程访问NAS心得</title><link>https://huowenlong.com/blog/F2ea4malPi8x/</link><guid isPermaLink="true">https://huowenlong.com/blog/F2ea4malPi8x/</guid><description>从 fnconnect、公网 IPv6 配 DDNS，到雨云服务器跑 frp 内网穿透，再到飞牛 OS 路径穿越漏洞事件后的反思，讲述作者如何一步步收紧 NAS 的对外暴露面。</description><pubDate>Sun, 08 Mar 2026 10:14:50 GMT</pubDate><content:encoded>&lt;p&gt;在我看来，能够远程访问，才是一个 NAS 的核心。但也不可避免的会产生一定的安全隐患，我们该如何做，才能尽可能减小安全风险呢。这个帖子我将以我个人的心路历程来谈谈远程访问 NAS 。&lt;/p&gt;&lt;h2&gt;最开始&lt;/h2&gt;&lt;p&gt;1 月份购买的飞牛 EVO 送了我 5 年的 fnconnect 高级套餐，带宽 12mbps ，月均500G。我直接补了 99 元购买了 fnconnect 专业版的套餐，带宽提到了 40mbps，月均1000G，这个升级还是很划算的。通过 fnconnect ，成了我访问飞牛 nas 的一个手段。&lt;/p&gt;&lt;figure&gt;&lt;img src=&quot;https://huowenlong.com/trilium-assets/F2ea4malPi8x/c7bcef28898ab646.png&quot; width=&quot;2000&quot; height=&quot;1158&quot; /&gt;&lt;/figure&gt;&lt;p&gt;我的家庭宽带也提供了公网 ipv6 ，通过 DDNS 使用域名访问。&lt;/p&gt;&lt;figure&gt;&lt;img src=&quot;https://huowenlong.com/trilium-assets/F2ea4malPi8x/c725b0eddf480708.png&quot; width=&quot;2000&quot; height=&quot;1169&quot; /&gt;&lt;/figure&gt;&lt;p&gt;除此之外，我还购买了雨云服务器，搭建 frpc 和 frps 来进行内网穿透。下图是我当时用雨云服务器进行内网穿透的整体架构图。&lt;/p&gt;&lt;figure&gt;&lt;img src=&quot;https://huowenlong.com/trilium-assets/F2ea4malPi8x/462d5356f4fa9e69.png&quot; width=&quot;2000&quot; height=&quot;1346&quot; /&gt;&lt;/figure&gt;&lt;p&gt;但那时候在家，大部分时间还是通过内网访问，外网一般用于测试、折腾。&lt;/p&gt;&lt;h2&gt;转折点&lt;/h2&gt;&lt;p&gt;2 月份的时候，飞牛 OS 被曝光有严重的路径穿越漏洞。攻击者可以通过构造 URL ，在&lt;strong&gt;不登录的情况下读取 NAS 上的文件&lt;/strong&gt;。当时我立马查看我的 NAS 并用相关脚本进行检测，所幸没有中招。但是看到那些中招的 NAS 直接可以通过公网甚至是 fnconnect 访问里面的任意文件，所谓的隐私安全不复存在，自己的隐私被人窥视，哪怕没有什么重要资料，但也让人非常膈应。从那时起，我开始着重关注远程访问的安全性。&lt;/p&gt;&lt;h2&gt;我做的一些工作&lt;/h2&gt;&lt;h3&gt;拥抱了 VPN&lt;/h3&gt;&lt;p&gt;&lt;strong&gt;VPN&lt;/strong&gt;（Virtual Private Network，&lt;strong&gt;虚拟专用网络&lt;/strong&gt;）是一种通过互联网建立&lt;strong&gt;加密安全连接&lt;/strong&gt;的技术。它可以让你的设备在访问网络时，看起来像是从另一个网络或地区连接，同时保护数据安全。常见的加密协议有：&lt;/p&gt;&lt;ul&gt;&lt;li&gt;OpenVPN&lt;/li&gt;&lt;li&gt;WireGuard&lt;/li&gt;&lt;li&gt;IKEv2/IPSec&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;我选择的协议是 WireGuard ，速度延迟表现都不错，并且技术较新。市面上有许多基于 WireGuard 的软件比如 tailscale、easytier 等等。我使用的是原生的 WireGuard ，虽然方便性不如那些软件，但是灵活性较高，直连后速度理论上最快。因为我有ipv6，所以我通过 WireGuard 总是直连，下载速度基本上都能稳定在 15mb/s - 25mb/s。&lt;/p&gt;&lt;figure&gt;&lt;img src=&quot;https://huowenlong.com/trilium-assets/F2ea4malPi8x/e9be233a2afbc9ba.png&quot; width=&quot;1600&quot; height=&quot;1016&quot; /&gt;&lt;/figure&gt;&lt;h3&gt;更新了防火墙配置&lt;/h3&gt;&lt;p&gt;参照了&lt;a href=&quot;https://mp.weixin.qq.com/s/1wdThPGzT4TFpD6LPi8VFA&quot;&gt;这篇文章&lt;/a&gt;，我对防火墙进行了更新，只保留必要的端口。&lt;/p&gt;&lt;figure&gt;&lt;img src=&quot;https://huowenlong.com/trilium-assets/F2ea4malPi8x/1fb7dcecba97b06b.png&quot; width=&quot;2000&quot; height=&quot;1371&quot; /&gt;&lt;/figure&gt;&lt;h3&gt;其它&lt;/h3&gt;&lt;p&gt;还有一些小的措施，比如更新了强密码、修改了默认的 https 访问端口并强制使用 https 访问 、修改了 ssh 默认端口、启用了双层验证等等。&lt;/p&gt;&lt;h2&gt;现在&lt;/h2&gt;&lt;p&gt;目前，我使用的远程访问就只有 WireGuard 组网和 fnconnect 内网穿透。后者主要是保险，由于国内互联网环境的复杂性，使用 UDP 传输的 WireGuard 有可能导致丢包限速甚至是断连，还是加上 fnconnect 这一保险层（才不是因为我的套餐还没过期🤪）。我相信飞牛团队在经历这次重大事件后会着重 fnconnect 和 fnOS 安全性。毕竟，隐私安全不是小事。&lt;/p&gt;</content:encoded></item><item><title>分享一些docker项目</title><link>https://huowenlong.com/blog/wqP8s2pQuo0H/</link><guid isPermaLink="true">https://huowenlong.com/blog/wqP8s2pQuo0H/</guid><description>使用飞牛 EVO 两个多月里折腾过的 Docker 项目清单，包括 fn-message-bot、ani-rss、excalidraw、vaultwarden、clash、memos、trilium、matrix，逐个说明用途与实际使用感受。</description><pubDate>Sat, 07 Mar 2026 10:14:50 GMT</pubDate><content:encoded>&lt;p&gt;自从买了飞牛 EVO 也有两个多月了，期间也折腾不少，也发现了一些有意思的 docker 项目并进行了部署，大概有这些：&lt;/p&gt;&lt;ul&gt;&lt;li&gt;&lt;a href=&quot;https://hub.docker.com/r/sunanang/fn-message-bots&quot;&gt;fn-message-bot&lt;/a&gt;  一个用于消息推送和的机器人服务，用于接收飞牛nas日志通知。&lt;/li&gt;&lt;li&gt;&lt;a href=&quot;https://docs.wushuo.top/&quot;&gt;ani-rss&lt;/a&gt; 一个基于 RSS 的自动追番工具，可以订阅动漫更新并自动下载或推送新番资源。&lt;/li&gt;&lt;li&gt;&lt;a href=&quot;https://docs.excalidraw.com/&quot;&gt;excalidraw&lt;/a&gt; 一个开源的在线白板绘图工具，支持多人协作绘制手绘风格的流程图和草图。&lt;/li&gt;&lt;li&gt;&lt;a href=&quot;https://rs.ppgg.in/home&quot;&gt;vaultwarden&lt;/a&gt; Bitwarden 的轻量级兼容服务器实现，用于自建密码管理服务。&lt;/li&gt;&lt;li&gt;&lt;a href=&quot;https://hub.docker.com/r/metacubex/mihomo&quot;&gt;clash&lt;/a&gt; 一个规则驱动的网络代理工具，常用于科学上网、流量分流和代理管理。&lt;/li&gt;&lt;li&gt;&lt;a href=&quot;https://usememos.com/docs&quot;&gt;memos&lt;/a&gt; 一个开源的轻量级笔记与碎片化记录工具，类似私人版的微博或随手记。&lt;/li&gt;&lt;li&gt;&lt;a href=&quot;https://triliumnotes.org/zh-Hans/get-started/&quot;&gt;trilium&lt;/a&gt; 一个功能强大的层级式知识管理笔记系统，支持富文本、代码和复杂知识结构。&lt;/li&gt;&lt;li&gt;&lt;a href=&quot;https://matrix.org/&quot;&gt;matrix&lt;/a&gt; 一个开源的去中心化即时通信协议和服务器，用于构建类似 Discord/Slack 的聊天系统。&lt;/li&gt;&lt;/ul&gt;&lt;h2&gt;Fn-message-bot&lt;/h2&gt;&lt;p&gt;由于目前飞牛官方没有可以实时接收日志信息的监控通知器，这个项目可以进行平替。我目前将其接入 bark 服务中&lt;/p&gt;&lt;figure&gt;&lt;img src=&quot;https://huowenlong.com/trilium-assets/wqP8s2pQuo0H/36b9476677286882.png&quot; width=&quot;976&quot; height=&quot;2000&quot; /&gt;&lt;/figure&gt;&lt;figure&gt;&lt;img src=&quot;https://huowenlong.com/trilium-assets/wqP8s2pQuo0H/3540f241b9dd70f1.png&quot; width=&quot;1172&quot; height=&quot;2000&quot; /&gt;&lt;/figure&gt;&lt;h2&gt;Ani-rss&lt;/h2&gt;&lt;p&gt;平时我会追一些番剧，这个项目能大大节省我的时间。它接入了 Mikan 源，里面有很多 RSS 链接，选择想看的番剧，ani-rss 读取番剧对应的 RSS 源，调用 qbitorrent 下载相应的种子文件，并且定期更新动漫，下载完成后通过 bark 进行通知，实现一键追番。&lt;/p&gt;&lt;figure&gt;&lt;img src=&quot;https://huowenlong.com/trilium-assets/wqP8s2pQuo0H/2e5c4375c06ac658.png&quot; width=&quot;2000&quot; height=&quot;1297&quot; /&gt;&lt;/figure&gt;&lt;h2&gt;Excalidraw&lt;/h2&gt;&lt;p&gt;这是我偶然发现的项目，其实这个完全可以不用自建，官方的链接点进去就可以直接进行绘图，还能调用 AI 。&lt;/p&gt;&lt;figure&gt;&lt;img src=&quot;https://huowenlong.com/trilium-assets/wqP8s2pQuo0H/2db06aac24e24ed9.png&quot; width=&quot;2000&quot; height=&quot;1295&quot; /&gt;&lt;/figure&gt;&lt;h2&gt;Vaultwarden&lt;/h2&gt;&lt;p&gt;这个是我目前重度依赖的项目，一个自建的密码库，vaultwarden 使用 Rust 语言对 bitwarden 进行了重构，占用资源更小。最开始我使用的苹果自带的密码管理器，但是它的局限性太强，bitwarden 提供的浏览器插件太方便了，数据又存在我的本地 nas 中，非常不错。&lt;/p&gt;&lt;figure&gt;&lt;img src=&quot;https://huowenlong.com/trilium-assets/wqP8s2pQuo0H/97c396440d90837d.png&quot; width=&quot;2000&quot; height=&quot;1302&quot; /&gt;&lt;figcaption&gt; &lt;/figcaption&gt;&lt;/figure&gt;&lt;h2&gt;Clash&lt;/h2&gt;&lt;p&gt;没什么好说的，主要就用于科学上网。之前部署 openclaw 需要拉取国外服务器的资源，那个时候部署的。好在飞牛有 docker 的镜像仓库，这个用的比较少。&lt;/p&gt;&lt;h2&gt;Memos&lt;/h2&gt;&lt;p&gt;这个项目和 trillium 进行互补的。memos 主要用于碎片化的记忆，我把它当作一个备忘录，也用于日记。我部署它主要还是因为它有一个非官方的 iOS 应用（&lt;a href=&quot;https://memos.moe/&quot;&gt;Moe Memos&lt;/a&gt;），方便随时记录。&lt;/p&gt;&lt;figure&gt;&lt;img src=&quot;https://huowenlong.com/trilium-assets/wqP8s2pQuo0H/61973ab67042131f.png&quot; width=&quot;2000&quot; height=&quot;1493&quot; /&gt;&lt;/figure&gt;&lt;p&gt;整体设计感挺高，完美适配iOS26 Liquid Glass，值得推荐。&lt;/p&gt;&lt;h2&gt;Trilium&lt;/h2&gt;&lt;p&gt;这个是我很早的时候发现的项目，当时我想找一个可以记录笔记，并且可以一键进行发布到互联网的项目，trilium 可以说是很完美的项目，虽然有些软件比如 notion、obsidian 都可以做到，但是 trilium 胜在开源免费，可以自建服务，具有极强的 DIY 属性。&lt;/p&gt;&lt;figure&gt;&lt;img src=&quot;https://huowenlong.com/trilium-assets/wqP8s2pQuo0H/0bb52a4102fea0d9.png&quot; alt=&quot;Trilium Screenshot&quot; width=&quot;1000&quot; /&gt;&lt;/figure&gt;&lt;h2&gt;Matrix&lt;/h2&gt;&lt;p&gt;在我之前的博客中已经提到了，我当时部署它主要是将 openclaw 机器人接入到 matrix 。当时使用了很多接入方法都失败了，最后自建的 matrix 接入成功了。我把 openclaw 卸载后这个项目也不怎么用了。等到 openclaw 足够成熟时再下载部署，现阶段 bug 还是太多了，文档教程太少了。&lt;/p&gt;&lt;figure&gt;&lt;img src=&quot;https://huowenlong.com/trilium-assets/wqP8s2pQuo0H/560bb2e5f669c228.jpeg&quot; width=&quot;974&quot; height=&quot;2000&quot; /&gt;&lt;/figure&gt;</content:encoded></item><item><title>飞牛nas部署Matrix</title><link>https://huowenlong.com/blog/4o1kCT4CizAt/</link><guid isPermaLink="true">https://huowenlong.com/blog/4o1kCT4CizAt/</guid><description>在飞牛 NAS 上用 Docker 部署 Matrix 的完整流程：先生成配置文件避免容器反复重启，再通过 GUI 启动服务、在终端里创建管理员账号，最后用 Element 等客户端登录使用。</description><pubDate>Thu, 19 Feb 2026 10:14:50 GMT</pubDate><content:encoded>&lt;h2&gt;关于matrix&lt;/h2&gt;&lt;p&gt;&lt;strong&gt;Matrix&lt;/strong&gt; 是一个&lt;strong&gt;开源、去中心化、可联邦的实时通信协议&lt;/strong&gt;，目标是成为像 Email（SMTP）那样的通用通信网络标准。它可以用来做：&lt;/p&gt;&lt;ul&gt;&lt;li&gt;即时聊天（IM）&lt;/li&gt;&lt;li&gt;语音 / 视频通话（VoIP/WebRTC）&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;h2&gt;部署matrix&lt;/h2&gt;&lt;p&gt;以飞牛nas为例，使用docker，在镜像仓库搜素&lt;strong&gt;matrixdotorg/synapse&lt;/strong&gt;，选择第一个。&lt;/p&gt;&lt;figure&gt;&lt;img src=&quot;https://huowenlong.com/trilium-assets/4o1kCT4CizAt/ca027603a8e1f2eb.png&quot; width=&quot;2000&quot; height=&quot;1168&quot; /&gt;&lt;/figure&gt;&lt;p&gt; &lt;/p&gt;&lt;h3&gt;第一步：生成初始配置文件&lt;/h3&gt;&lt;p&gt;我们使用 Synapse 默认自带的 SQLite ，不使用 PostgreSQL 。在正式开启容器之前，需要先生成配置文件，否则会导致容器一直处于重启中。使用 ssh 工具连接 nas ，执行以下命令：&lt;/p&gt;&lt;pre&gt;&lt;code&gt;# 假设你的 matrix 文件夹路径是 /vol1/1000/docker/matrix
# 将 192.168.10.100 换成你的 NAS 局域网 IP 或你的域名
docker run -it --rm \
    -v /vol1/1000/docker/matrix:/data \
    -e SYNAPSE_SERVER_NAME=192.168.10.100 \
    -e SYNAPSE_REPORT_STATS=no \
    matrixdotorg/synapse:latest generate&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;执行完成后，matrix 文件夹下会多几个文件。&lt;/p&gt;&lt;h3&gt;第二步：使用飞牛dockerGUI部署&lt;/h3&gt;&lt;p&gt;端口设置：&lt;/p&gt;&lt;figure&gt;&lt;table&gt;&lt;colgroup&gt;&lt;col /&gt;&lt;col /&gt;&lt;col /&gt;&lt;/colgroup&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;&lt;span&gt;本地端口&lt;/span&gt;&lt;/td&gt;&lt;td&gt;&lt;span&gt;填写容器端口&lt;/span&gt;&lt;/td&gt;&lt;td&gt;&lt;span&gt;协议&lt;/span&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;19090&lt;/td&gt;&lt;td&gt;19090&lt;/td&gt;&lt;td&gt;TCP&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;8008&lt;/td&gt;&lt;td&gt;8008&lt;/td&gt;&lt;td&gt;TCP&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;8448&lt;/td&gt;&lt;td&gt;8448&lt;/td&gt;&lt;td&gt;TCP&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/figure&gt;&lt;p&gt;存储设置：&lt;/p&gt;&lt;figure&gt;&lt;table&gt;&lt;colgroup&gt;&lt;col /&gt;&lt;col /&gt;&lt;col /&gt;&lt;/colgroup&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;&lt;span&gt;本地路径&lt;/span&gt;&lt;/td&gt;&lt;td&gt;&lt;span&gt;装载路径&lt;/span&gt;&lt;/td&gt;&lt;td&gt;&lt;span&gt;权限&lt;/span&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;/vol1/1000/docker/matrix&lt;/td&gt;&lt;td&gt;/data&lt;/td&gt;&lt;td&gt;读写&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/figure&gt;&lt;p&gt;网络、环境变量、启动命令默认，功能选择最高权限，然后启动。&lt;/p&gt;&lt;h2&gt;第三步：创建个人账号&lt;/h2&gt;&lt;p&gt;容器运行后，我们需要一个管理员账号来开始聊天。&lt;/p&gt;&lt;ol&gt;&lt;li&gt;在飞牛 Docker 的容器列表，找到 matrix，点击右侧的 &lt;strong&gt;终端&lt;/strong&gt;（或“执行”）。&lt;/li&gt;&lt;li&gt;&lt;p&gt;在弹出的命令行窗口中，输入以下命令并回车：&lt;/p&gt;&lt;pre&gt;&lt;code&gt;register_new_matrix_user http://192.168.10.100:8008 -c /data/homeserver.yaml&lt;/code&gt;&lt;/pre&gt;&lt;/li&gt;&lt;li&gt;按照提示：&lt;ul&gt;&lt;li&gt;输入用户名（如 admin）。&lt;/li&gt;&lt;li&gt;输入并确认密码。&lt;/li&gt;&lt;li&gt;当询问 Make admin? [no]: 时，输入 yes 并回车。&lt;/li&gt;&lt;/ul&gt;&lt;/li&gt;&lt;/ol&gt;&lt;h2&gt;第四步：如何访问和使用？&lt;/h2&gt;&lt;ol&gt;&lt;li&gt;下载客户端：在手机或电脑上安装 &lt;strong&gt;Element&lt;/strong&gt; (Matrix 的官方推荐客户端)。&lt;/li&gt;&lt;li&gt;登录设置：&lt;ol&gt;&lt;li&gt;点击“登录”。&lt;/li&gt;&lt;li&gt;点击“编辑”按钮更改服务器地址（默认是 matrix.org）。&lt;/li&gt;&lt;li&gt;输入你的服务器地址：&lt;code&gt;http://192.168.10.100:8008&lt;/code&gt;（请换成你实际的 IP）。&lt;/li&gt;&lt;/ol&gt;&lt;/li&gt;&lt;li&gt;开始聊天：用刚才创建的账号登录即可。&lt;/li&gt;&lt;/ol&gt;</content:encoded></item><item><title>使用 Cryptomator：为云存储提供零知识加密</title><link>https://huowenlong.com/blog/2epdE8sgosX8/</link><guid isPermaLink="true">https://huowenlong.com/blog/2epdE8sgosX8/</guid><description>云服务商的服务器端加密并不等于零知识。作者介绍 Cryptomator 的原理与日常用法，在 iCloud、OneDrive 等云盘上实现上传前本地加密，让解密密钥只掌握在自己手里。</description><pubDate>Wed, 03 Dec 2025 11:33:17 GMT</pubDate><content:encoded>&lt;h2&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;/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;p&gt;&lt;strong&gt;服务器端加密（Server-side encryption）&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;/ul&gt;&lt;p&gt;如果你希望实现真正的：&lt;/p&gt;&lt;blockquote&gt;&lt;p&gt;&lt;strong&gt;零知识（Zero-Knowledge）存储&lt;/strong&gt;&lt;/p&gt;&lt;/blockquote&gt;&lt;p&gt;&lt;span&gt;就需要在&lt;/span&gt;&lt;strong&gt;上传前进行本地加密&lt;/strong&gt;&lt;span&gt;。&lt;/span&gt;这正是 Cryptomator 的用途。&lt;/p&gt;&lt;h2&gt;二、什么是 Cryptomator&lt;/h2&gt;&lt;p&gt;&lt;span&gt;Cryptomator&lt;/span&gt; 是一款开源的客户端加密软件，用于在上传到云存储之前对文件进行加密。&lt;/p&gt;&lt;p&gt;其核心特点包括：&lt;/p&gt;&lt;ul&gt;&lt;li&gt;客户端加密（Client-side encryption）&lt;/li&gt;&lt;li&gt;零知识架构（Zero-knowledge）&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;ul&gt;&lt;li&gt;Windows&lt;/li&gt;&lt;li&gt;macOS&lt;/li&gt;&lt;li&gt;Linux&lt;/li&gt;&lt;li&gt;iOS&lt;/li&gt;&lt;li&gt;Android&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;支持的云存储：&lt;/p&gt;&lt;ul&gt;&lt;li&gt;iCloud Drive&lt;/li&gt;&lt;li&gt;Google Drive&lt;/li&gt;&lt;li&gt;Dropbox&lt;/li&gt;&lt;li&gt;OneDrive&lt;/li&gt;&lt;li&gt;WebDAV&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;Cryptomator 本身并不提供云存储服务，而是作为：&lt;/p&gt;&lt;blockquote&gt;&lt;p&gt;&lt;strong&gt;云存储的安全层（Security Layer）&lt;/strong&gt;&lt;/p&gt;&lt;/blockquote&gt;&lt;figure&gt;&lt;img src=&quot;https://huowenlong.com/trilium-assets/2epdE8sgosX8/9437b496114da8f8.png&quot; width=&quot;2000&quot; height=&quot;1228&quot; /&gt;&lt;/figure&gt;&lt;h2&gt;三、Cryptomator 的工作原理&lt;/h2&gt;&lt;p&gt;Cryptomator 的核心思路非常简单：&lt;/p&gt;&lt;p&gt;&lt;strong&gt;在本地创建一个“保险库（Vault）”。&lt;/strong&gt;&lt;/p&gt;&lt;p&gt;所有文件在进入保险库时都会被自动加密。&lt;/p&gt;&lt;p&gt;基本流程如下：&lt;/p&gt;&lt;pre&gt;&lt;code&gt;明文文件
     ↓
本地加密（AES）
     ↓
密文文件
     ↓
上传到云存储&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;当用户访问文件时：&lt;/p&gt;&lt;pre&gt;&lt;code&gt;云端密文
     ↓
下载到本地
     ↓
即时解密
     ↓
用户读取明文&lt;/code&gt;&lt;/pre&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;h2&gt;四、文件加密方式&lt;/h2&gt;&lt;p&gt;Cryptomator 使用现代加密算法保护数据：&lt;/p&gt;&lt;p&gt;主要包括：&lt;/p&gt;&lt;ul&gt;&lt;li&gt;&lt;span&gt;&lt;strong&gt;AES-256&lt;/strong&gt;&lt;/span&gt;：文件内容加密&lt;/li&gt;&lt;li&gt;&lt;strong&gt;Scrypt&lt;/strong&gt;&lt;span&gt;：密钥派生&lt;/span&gt;&lt;/li&gt;&lt;li&gt;&lt;strong&gt;GCM 模式&lt;/strong&gt;&lt;span&gt;：认证加密&lt;/span&gt;&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;此外，Cryptomator 还做了一件非常重要的事情：&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;p&gt;Cryptomator 会把：&lt;/p&gt;&lt;pre&gt;&lt;code&gt;photo.jpg&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;转换为类似：&lt;/p&gt;&lt;pre&gt;&lt;code&gt;AB/CD/EFG234ADF...&lt;/code&gt;&lt;/pre&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;h2&gt;五、目录结构设计&lt;/h2&gt;&lt;p&gt;Cryptomator 并不是将所有数据打包为一个大型容器。相反，它采用：&lt;strong&gt;逐文件加密（per-file encryption）&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;典型的 vault 结构如下：&lt;/p&gt;&lt;pre&gt;&lt;code&gt;vault/
 ├─ d/
 │   ├─ AB/
 │   │   └─ encrypted-file
 ├─ m/
 └─ masterkey.cryptomator&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;其中：&lt;/p&gt;&lt;ul&gt;&lt;li&gt;masterkey.cryptomator&lt;span&gt; 保存加密配置&lt;/span&gt;&lt;/li&gt;&lt;li&gt;&lt;span&gt;d/&lt;/span&gt; 存储加密后的数据块&lt;/li&gt;&lt;/ul&gt;&lt;h2&gt;六、使用流程&lt;/h2&gt;&lt;p&gt;使用 Cryptomator 的基本步骤如下：&lt;/p&gt;&lt;h3&gt;1 创建 Vault&lt;/h3&gt;&lt;p&gt;选择云存储目录，例如：&lt;/p&gt;&lt;pre&gt;&lt;code&gt;iCloud Drive/CryptomatorVault&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;创建新的 Vault。&lt;/p&gt;&lt;h3&gt;2 设置密码&lt;/h3&gt;&lt;p&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;Cryptomator 不保存任何恢复密钥。&lt;/p&gt;&lt;h3&gt;3 解锁 Vault&lt;/h3&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;h3&gt;4 自动同步&lt;/h3&gt;&lt;p&gt;文件会自动：&lt;/p&gt;&lt;pre&gt;&lt;code&gt;本地加密 → 云端同步&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;整个过程用户几乎感知不到。&lt;/p&gt;&lt;h2&gt;七、我的使用场景&lt;/h2&gt;&lt;p&gt;我将加密的文件存在我的 nas 上，fnOS 支持使用 webDAV 访问文件。通过 WireGuard 访问解密后的文件，整体体验很不错。&lt;/p&gt;</content:encoded></item><item><title>关于对教程主义的思考</title><link>https://huowenlong.com/blog/VOsmZSt8PWpI/</link><guid isPermaLink="true">https://huowenlong.com/blog/VOsmZSt8PWpI/</guid><description>由一支视频引发的自我反思：长期依赖教程、靠拍照存资料，容易陷入被动思考。作者剖析教程的重复性与拍照损伤效应，并尝试用主动复盘、独立解题来摆脱这种状态。</description><pubDate>Wed, 05 Mar 2025 10:14:50 GMT</pubDate><content:encoded>&lt;h2&gt;&lt;strong&gt;始于&lt;/strong&gt;&lt;/h2&gt;&lt;p&gt;前段时间在 b 站上看到了一个视频，转自油管 &lt;a href=&quot;https://youtu.be/piBzLIdg3Ks?si=O8kiw9LrMBQ9NVh8&quot;&gt;If You’re Not Motivated to Code, Do This&lt;/a&gt; 。使我感悟颇深，我开始进行反思。&lt;/p&gt;&lt;h2&gt;&lt;strong&gt;现在面临的窘境&lt;/strong&gt;&lt;/h2&gt;&lt;p&gt;”这是一个最好的时代，也是一个最坏的时代。“如今，我们处在信息爆炸式增长的时代，网上充斥着参差不齐的信息资源，各式各样的学习教程资源也已然占据了互联网的半壁江山。当遇到不会的问题，我习惯于从互联网寻找教程，从国内的搜索引擎、b站、知乎、CSDN到国外的搜索引擎、youtube、GitHub，往往都能找到有过相同问题的人及其对应的解决方案，除此之外，AI 的加持也极大提升了我们的工作效率和学习效率。但是，这些知识真正能被我们所吸收吗？或者下次再遇到类似的问题能够独立解决吗？只能机械着跟着教程一步一步的走吗？这些正是我现在面临的窘境。&lt;/p&gt;&lt;h3&gt;&lt;strong&gt;1、教程的重复性！&lt;/strong&gt;&lt;/h3&gt;&lt;p&gt;经常性的，比如我想为 pc 配置了一个环境，我会去搜教程，找到了一个合适的教程，跟着教程一步一步配置，生怕哪个环节出现问题，但不怎么过大脑，导致下次再遇到环境配置问题再去搜索教程，感觉陷入了被动思考的泥潭，心想着，反正有教程，跟着来准会没错。就像，在学校上课，老师在黑板上板书，手机相机一咔嚓，照片存起来，想着，反正已经拍下来了，之后再看呗，实际上过段时间就忘了曾经拍过照片。这种现象也被称为&lt;a href=&quot;https://www.xinli001.com/info/100487906&quot;&gt;拍照损伤效应（photo-taking-impairment-effect）&lt;/a&gt;。&lt;/p&gt;&lt;h3&gt;&lt;strong&gt;2、教程的权威性？&lt;/strong&gt;&lt;/h3&gt;&lt;p&gt;在有些时候，可能由于第一次接触陌生的事物（比如计算机相关知识），对于教程中的每一步只能完全照搬，生怕因为某些细节导致功亏一篑。事实上，这些东西的容错率很高，大不了再来一次。1+1+1=&amp;gt;3，1+0.5+1.5=&amp;gt;3，0.5+0.5+2=&amp;gt;3，”条条大路通罗马“，好的教程可能只是选取了对作者来说最优的一种方案，但不一定是最适合自己的。并且最重要的是在吸收理解该方案后能否举一反三。&lt;/p&gt;&lt;h2&gt;&lt;strong&gt;如何破局？&lt;/strong&gt;&lt;/h2&gt;&lt;p&gt;针对第一种情况。俗话说，好记性不如烂笔头。我可以在广泛吸收他们经验教程后，取其精华，去其糟粕，整理成符合自己预期的，完整的解决方案。这个博客在此背景下应运而生。&lt;/p&gt;我为什么选择博客而不是纸质记录？&lt;div&gt;&lt;p&gt;首先对于知识的整理吸收不限媒介，其次对于博客我可以随时随地记录自己的想法，只需一个手机即可，而不用随时携带纸和笔。最后，在保存方面，个人认为数据保存远比实体保存来的容易与持久。&lt;/p&gt;&lt;/div&gt;&lt;p&gt;针对第二种情况。重要的是将知识联系起来，在此基础上举一反三。就像做数学题目一样，这道题目的核心思想什么，有什么对应的解决方案，是否能与之建立关联，或者说两个的组合能够和它建立关联。实现此目标前提是有足够的知识储备，其次再去提创新。&lt;/p&gt;&lt;h2&gt;&lt;strong&gt;接下来的想法&lt;/strong&gt;&lt;/h2&gt;&lt;p&gt;写这篇博客已经离我创建这篇文章有两个月了，期间忙着各种各样的事。好在五一假期基本都忙完了，能够让我在假期这几天专心写博客。之后的博客我会将学习思考等一一整理好并记录在此博客。以此记录我的成长。&lt;/p&gt;</content:encoded></item></channel></rss>