<?xml version="1.0" encoding="UTF-8"?><rss version="2.0" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>雨后初晴社</title><description>宇文Teacher的个人博客</description><link>https://www.rainafter.cn/</link><language>zh_CN</language><item><title>在生活中寻找勇气与清醒</title><link>https://www.rainafter.cn/posts/%E5%9C%A8%E7%94%9F%E6%B4%BB%E4%B8%AD%E5%AF%BB%E6%89%BE%E5%8B%87%E6%B0%94%E4%B8%8E%E6%B8%85%E9%86%92/</link><guid isPermaLink="true">https://www.rainafter.cn/posts/%E5%9C%A8%E7%94%9F%E6%B4%BB%E4%B8%AD%E5%AF%BB%E6%89%BE%E5%8B%87%E6%B0%94%E4%B8%8E%E6%B8%85%E9%86%92/</guid><description>又下了一场雨，窗外的树叶被雨水洗得发亮。我在桌前翻看雷军和张一鸣的经历，起初只是想了解两位创业者的故事，可读得久了，心中竟慢慢生出一种安静而复杂的感受。</description><pubDate>Mon, 18 May 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;又下了一场雨，窗外的树叶被雨水洗得发亮。我在桌前翻看雷军和张一鸣的经历，起初只是想了解两位创业者的故事，可读得久了，心中竟慢慢生出一种安静而复杂的感受。&lt;/p&gt;
&lt;p&gt;他们离普通人的生活似乎很远。一个创办小米，把手机、智能家居、汽车这些庞大的产业一步步连成“人车家全生态”；一个创办字节跳动，让信息推荐和内容平台改变了许多人获取世界的方式。&lt;/p&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;p&gt;我常常想，若把这些经历摊开来看，里面当然有宏大的商业判断，但更深处其实是一种“不肯停在原地”的心气。一个人已经有了足够光鲜的履历，仍然愿意进入一个陌生战场，在新的领域内重新学习、仍然愿意把自己放到聚光灯下承受质疑，这本身就需要极大的勇气。后来小米 Su7 大获成功，这并不令人感到意外，正是这种长期投入和坚持以及愿意对新领域的探索，才让小米能够在竞争激烈的市场中脱颖而出。&lt;/p&gt;
&lt;p&gt;雷军带给我的启发是不要轻易用年龄、阶段、过往成绩为自己划定边界。&lt;/p&gt;
&lt;p&gt;当然，这并不意味着每个人都要去做惊天动地的大事。对我而言，它更像是一种提醒：不要因为暂时的舒适而放弃新的可能，不要因为害怕失败而拒绝学习陌生的东西，也不要因为眼前看不到回报，就轻易否定一件真正值得投入的事情。&lt;/p&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;p&gt;这是一种很冷静的判断。它不只关乎某一个产品能否流行，而是关乎一个时代的信息结构正在怎样改变。率先使用了特殊推荐算法的今日头条、后来又将个性化推荐与短视频结合的抖音，以及在全球范围内推广的 TikTok，这些极其成功的产品，无一例外地把“信息的准确流动”放在了一个非常核心的位置。&lt;/p&gt;
&lt;p&gt;我在张一鸣身上看到的，并不只是技术判断，还有一种持续自我更新的能力。&lt;/p&gt;
&lt;p&gt;人很容易被自己的经验困住。过去做成过什么，便以为未来也应当如此；可技术和社会从不因为人的习惯而停止变化。移动互联网、算法推荐、短视频、AI，每一轮变化都像一次新的潮汐，既带来机会，也带来问题。若没有持续学习的能力，再辉煌的经验也可能变成沉重的旧衣。&lt;/p&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;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;img src=&quot;https://resource.rainafter.cn/img/20260518173950086.webp&quot; alt=&quot;雨后&quot; /&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;
</content:encoded></item><item><title>为你的 Twikoo 评论系统更换一个现代化的邮件通知模板</title><link>https://www.rainafter.cn/posts/%E4%B8%BA%E4%BD%A0%E7%9A%84-twikoo-%E8%AF%84%E8%AE%BA%E7%B3%BB%E7%BB%9F%E6%9B%B4%E6%8D%A2%E4%B8%80%E4%B8%AA%E7%8E%B0%E4%BB%A3%E5%8C%96%E7%9A%84%E9%82%AE%E4%BB%B6%E9%80%9A%E7%9F%A5%E6%A8%A1%E6%9D%BF/</link><guid isPermaLink="true">https://www.rainafter.cn/posts/%E4%B8%BA%E4%BD%A0%E7%9A%84-twikoo-%E8%AF%84%E8%AE%BA%E7%B3%BB%E7%BB%9F%E6%9B%B4%E6%8D%A2%E4%B8%80%E4%B8%AA%E7%8E%B0%E4%BB%A3%E5%8C%96%E7%9A%84%E9%82%AE%E4%BB%B6%E9%80%9A%E7%9F%A5%E6%A8%A1%E6%9D%BF/</guid><description>基于 React Email 为 Twikoo 评论系统生成现代化邮件通知模板，支持本地预览、主题配置、HTML 导出和真实邮箱测试。</description><pubDate>Mon, 11 May 2026 00:00:00 GMT</pubDate><content:encoded>&lt;h1&gt;为你的 Twikoo 评论系统更换一个现代化的邮件通知模板&lt;/h1&gt;
&lt;h2&gt;一、为什么要做这个项目&lt;/h2&gt;
&lt;p&gt;Twikoo 自带的邮件通知功能已经足够好用，但如果想把邮件样式改得更符合自己博客的风格，就会遇到一个比较现实的问题：邮件模板本质上是一大段极难维护的 HTML，并且是不支持 HTML5 的 HTML。&lt;/p&gt;
&lt;p&gt;直接编写 HTML 邮件模板并不舒服，大多数邮件 HTML 都需要使用相对于现代前端来说比较古老的 table 表格布局和内嵌 style 样式。结构稍微复杂一点，布局和样式就会变得很难维护；想看一眼效果，还需要保存配置、触发评论、等待邮件发送；而且不同邮箱客户端对 HTML 和 CSS 的兼容性也不完全一致、甚至相同邮箱的网页端与移动端APP的兼容性也不同，最终效果往往要发到真实邮箱里才能确认。&lt;/p&gt;
&lt;p&gt;所以我做了一个小项目：&lt;a href=&quot;https://github.com/IceTeacher/rainafter-twikoo-email&quot;&gt;Rainafter-Twikoo-Email&lt;/a&gt;。&lt;/p&gt;
&lt;p&gt;::github{repo=&quot;IceTeacher/rainafter-twikoo-email&quot;}&lt;/p&gt;
&lt;p&gt;它的目标很简单：用更现代化的前端开发方式来写 Twikoo 邮件模板。模板开发阶段可以本地预览，修改主题色和 Banner 图片也有统一配置，最后再导出为 Twikoo 可以直接使用的 HTML。&lt;/p&gt;
&lt;h2&gt;二、这个项目能做什么&lt;/h2&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/IceTeacher/rainafter-twikoo-email&quot;&gt;rainafter-twikoo-email&lt;/a&gt; 是一个基于 React Email 的 Twikoo 邮件通知模板项目，目前内置了两套主题：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;Rainafter&lt;/code&gt;：清新、简约，配色比较柔和。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Fuwari&lt;/code&gt;：参考 Astro 博客主题 &lt;a href=&quot;https://github.com/saicaca/fuwari&quot;&gt;Fuwari&lt;/a&gt; 的风格和配色。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;每套主题都包含两类模板：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;notification.tsx&lt;/code&gt;：访客收到评论回复通知时使用。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;notification-admin.tsx&lt;/code&gt;：站点管理员收到新评论通知时使用。&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;2.1 Rainafter 主题&lt;/h3&gt;
&lt;p&gt;Rainafter 主题以清新简约的设计风格为主，配色柔和。&lt;/p&gt;
&lt;p&gt;&amp;lt;div style=&quot;display: grid; grid-template-columns: repeat(auto-fit, minmax(300px, 1fr)); gap: 16px; align-items: start;&quot;&amp;gt;
&amp;lt;div align=&quot;center&quot;&amp;gt;
&amp;lt;img src=&quot;https://resource.rainafter.cn/img/20260512011828052.webp&quot; alt=&quot;fuwari 普通评论回复通知&quot; style=&quot;height: 430px; aspect-ratio: 734/1037; object-fit: cover; margin-bottom: 4px;&quot; /&amp;gt;
&amp;lt;div style=&quot;font-size: 0.9em; color: gray;&quot;&amp;gt;普通评论回复通知&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div align=&quot;center&quot;&amp;gt;
&amp;lt;img src=&quot;https://resource.rainafter.cn/img/20260512011829502.webp&quot; alt=&quot;fuwari 管理员新评论通知&quot; style=&quot;height: 430px; aspect-ratio: 723/789; object-fit: cover; margin-bottom: 4px;&quot; /&amp;gt;
&amp;lt;div style=&quot;font-size: 0.9em; color: gray;&quot;&amp;gt;管理员新评论通知&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;&lt;/p&gt;
&lt;h3&gt;2.2 Fuwari 主题&lt;/h3&gt;
&lt;p&gt;Fuwari 主题参考了知名 Astro 博客主题 &lt;a href=&quot;https://github.com/saicaca/fuwari&quot;&gt;Fuwari&lt;/a&gt; 的设计风格和配色方案。&lt;/p&gt;
&lt;p&gt;&amp;lt;div style=&quot;display: grid; grid-template-columns: repeat(auto-fit, minmax(300px, 1fr)); gap: 16px; align-items: start;&quot;&amp;gt;
&amp;lt;div align=&quot;center&quot;&amp;gt;&lt;br /&gt;
&amp;lt;img src=&quot;https://resource.rainafter.cn/img/20260512011822983.webp&quot; alt=&quot;rainafter 普通评论回复通知&quot; style=&quot;height: 720px; aspect-ratio: 771/1517; object-fit: cover; margin-bottom: 8px;&quot; /&amp;gt;
&amp;lt;div style=&quot;font-size: 0.9em; color: gray;&quot;&amp;gt;普通评论回复通知&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div align=&quot;center&quot;&amp;gt;
&amp;lt;img src=&quot;https://resource.rainafter.cn/img/20260512011825663.webp&quot; alt=&quot;rainafter 管理员新评论通知&quot; style=&quot;height: 720px; aspect-ratio: 753/1391; object-fit: cover; margin-bottom: 8px;&quot; /&amp;gt;
&amp;lt;div style=&quot;font-size: 0.9em; color: gray;&quot;&amp;gt;管理员新评论通知&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;&lt;/p&gt;
&lt;p&gt;项目主要解决这几件事：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;可以像写 React 组件一样维护邮件模板。&lt;/li&gt;
&lt;li&gt;可以在本地启动预览服务，实时查看模板效果。&lt;/li&gt;
&lt;li&gt;可以通过配置文件修改主题色、Banner 图片和预览数据。&lt;/li&gt;
&lt;li&gt;可以导出最终 HTML，再复制到 Twikoo 的邮件通知配置中。&lt;/li&gt;
&lt;li&gt;完整的测试用例，包含中文长文本、中英文混排、长链接、代码块、行内代码、远程图片、父评论和回复内容等场景，可以通过 SMTP 真实发送测试邮件，检查 QQ 邮箱、163 邮箱、Outlook、Gmail 等客户端中的实际显示效果。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;如果你只是想快速换一套更现代的 Twikoo 邮件通知样式，可以直接使用本项目内置的默认主题；如果你想继续做自己的风格，也可以把它当作邮件模板开发的起点，参考下方的步骤自定义邮件模板。&lt;/p&gt;
&lt;h2&gt;三、开始使用&lt;/h2&gt;
&lt;h3&gt;3.1 直接使用内置主题&lt;/h3&gt;
&lt;p&gt;若果你比较认可模板默认的效果、不想修改模板细节、配色方案或者 Banner 图片，可以直接使用项目打包后的模板。&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;打开本项目的 &lt;a href=&quot;https://github.com/IceTeacher/rainafter-twikoo-email/releases/latest&quot;&gt;Releases&lt;/a&gt; 页面&lt;/li&gt;
&lt;li&gt;下载最新版本的 &lt;code&gt;out.zip&lt;/code&gt;，解压后得到默认的 HTML 模板文件&lt;/li&gt;
&lt;li&gt;接下来直接参考 &lt;a href=&quot;#%E4%B8%83%E5%B0%86%E9%82%AE%E4%BB%B6%E6%A8%A1%E6%9D%BF%E6%B7%BB%E5%8A%A0%E5%88%B0-twikoo-%E4%B8%AD&quot;&gt;七、将邮件模板添加到 Twikoo 中&lt;/a&gt; 的步骤，把 HTML 模板右键使用记事本打开后，将内容复制到 Twikoo 后台的邮件通知配置里即可。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&amp;lt;div align=&quot;center&quot;&amp;gt;&lt;br /&gt;
&amp;lt;img src=&quot;https://resource.rainafter.cn/img/20260512084643378.webp&quot; alt=&quot;打开本项目的 Releases 页面&quot; style=&quot;margin-bottom: 8px;&quot; /&amp;gt;
&amp;lt;div style=&quot;font-size: 0.9em; color: gray;&quot;&amp;gt;打开本项目的 Releases 页面&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;&lt;/p&gt;
&lt;p&gt;&amp;lt;div align=&quot;center&quot;&amp;gt;
&amp;lt;img src=&quot;https://resource.rainafter.cn/img/20260512084646168.webp&quot; alt=&quot;下载out.zip并解压&quot; style=&quot;margin-bottom: 8px;&quot; /&amp;gt;
&amp;lt;div style=&quot;font-size: 0.9em; color: gray;&quot;&amp;gt;下载out.zip并解压&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;&lt;/p&gt;
&lt;h3&gt;3.2 计划修改邮件模板&lt;/h3&gt;
&lt;p&gt;如果你想修改模板细节，或者基于现有主题开发自己的主题，建议先在本地预览确认修改效果，再导出 HTML 模板，最后再替换到 Twikoo 中。&lt;/p&gt;
&lt;p&gt;首先把项目克隆到本地，然后安装依赖：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;git clone https://github.com/IceTeacher/rainafter-twikoo-email
cd rainafter-twikoo-email
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;pnpm install
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;启动本地预览服务：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;pnpm dev
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;启动后，在浏览器中打开：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;http://localhost:3000
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这时就可以直接看到项目中的邮件模板预览。借助 React Email 库的本地预览功能，在修改主题模板或新建新模板时相比在 Twikoo 后台里反复保存、评论、收邮件，本地预览的效率会高很多。可以先确认整体布局、颜色、字体层级和内容长度是否合适，再考虑导出正式模板，最后再通过邮件测试用例，观察各邮件服务中的实际显示效果。&lt;/p&gt;
&lt;h2&gt;四、修改主题配置&lt;/h2&gt;
&lt;h3&gt;4.1 通过配置文件修改主题配色和Banner图片&lt;/h3&gt;
&lt;p&gt;如果只是想换成自己的博客风格，建议优先从主题配置文件开始改，而不是直接改模板组件。&lt;/p&gt;
&lt;p&gt;当前主要配置入口如下：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;emails/
├── rainafter/
│   ├── config.ts
│   ├── notification.tsx
│   └── notification-admin.tsx
└── fuwari/
    ├── config.ts
    ├── notification.tsx
    └── notification-admin.tsx
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;其中：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;emails/rainafter/config.ts&lt;/code&gt;：Rainafter 主题配置。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;emails/fuwari/config.ts&lt;/code&gt;：Fuwari 主题配置。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;notification.tsx&lt;/code&gt;：访客回复通知模板。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;notification-admin.tsx&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;Banner 图片地址。&lt;/li&gt;
&lt;li&gt;本地预览时使用的示例评论数据。&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;// 站点默认 Banner 图片 URL
export const defaultBannerImage = &apos;https://www.rainafter.cn/images/banner.jpg&apos;;

// 邮件主题色配置
export const colors = {
  pageBackground: &apos;#EAEEF5&apos;, // 页面背景色
  emailSurface: &apos;#FFFFFF&apos;, // 邮件卡片主体
  sectionSubtleBackground: &apos;#F5F8FE&apos;, // 用于区分层次的次级背景色
  textPrimary: &apos;#1F2937&apos;, // 主要文本色
  textSecondary: &apos;#475467&apos;, // 次要文本色
  textMuted: &apos;#667085&apos;, // 辅助文字色
  actionPrimary: &apos;#4272B8&apos;, // 主要操作色
  actionPrimarySubtle: &apos;#E4EFFF&apos;, // 主要操作色（柔和）
  borderSubtle: &apos;#D7E1F0&apos;, // 次要边框色
  commentCardOriginalBackground: &apos;#FFFFFF&apos;, // 原始评论卡片背景色
  commentCardReplyBackground: &apos;#E4EFFF&apos;, // 回复评论卡片背景色
  commentCardNewCommentBackground: &apos;#E4EFFF&apos;, // 新评论卡片背景色
  richContentCodeBackground: &apos;#F4F4F4&apos;, // 富文本中代码块背景色
  richContentCodeText: &apos;#333333&apos;, // 富文本中代码块文字色
} as const;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;比如你想让邮件通知和自己的博客主题保持一致，就可以先替换 Banner 图片，再调整主色、背景色、链接颜色等配置。确认配置层面无法满足需求后，再去修改对应主题下的模板文件。&lt;/p&gt;
&lt;h3&gt;4.2 修改模板组件&lt;/h3&gt;
&lt;p&gt;如果你需要修改模板的结构或者样式细节，可以直接修改对应主题下的 &lt;code&gt;notification.tsx&lt;/code&gt; 和 &lt;code&gt;notification-admin.tsx&lt;/code&gt; 文件。组件里已经有完整注释。修改时建议先在本地预览确认效果，再导出 HTML 模板，最后再通过邮件测试用例检查在真实邮箱中的显示效果。&lt;/p&gt;
&lt;p&gt;本项目使用 React Email 来开发邮件模板，组件里可以直接使用 React Email 提供的组件库和 Tailwind CSS 来构建邮件结构和样式，具体的使用方法和限制可参考 &lt;a href=&quot;https://react.email/docs/introduction&quot;&gt;React Email 官方文档&lt;/a&gt;。&lt;/p&gt;
&lt;p&gt;:::note
建议在修改模板组件后参考 &lt;a href=&quot;#%E5%85%AD%E6%B5%8B%E8%AF%95%E9%82%AE%E4%BB%B6%E5%85%BC%E5%AE%B9%E6%80%A7&quot;&gt;六、测试邮件兼容性&lt;/a&gt; 的步骤，通过真实 SMTP 发信的方式把修改后的模板发送到测试邮箱里，检查在不同邮箱客户端中的实际显示效果。
:::&lt;/p&gt;
&lt;h2&gt;五、导出 Twikoo 可用的 HTML 模板&lt;/h2&gt;
&lt;p&gt;本地预览确认没有问题后，就可以导出 HTML 模板：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;pnpm build
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;也可以使用：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;pnpm export
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;导出完成后，项目会在 &lt;code&gt;out/&lt;/code&gt; 目录中生成对应的 HTML 产物。你可以根据主题和模板类型选择需要的文件：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;普通评论回复通知：对应 &lt;code&gt;notification&lt;/code&gt; 模板。&lt;/li&gt;
&lt;li&gt;管理员新评论通知：对应 &lt;code&gt;notification-admin&lt;/code&gt; 模板。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;实际使用时，直接打开 &lt;code&gt;out/&lt;/code&gt; 中对应的 HTML 文件，右键使用记事本打开后，将内容复制到 Twikoo 后台的邮件通知配置里即可。&lt;/p&gt;
&lt;h2&gt;六、测试邮件兼容性&lt;/h2&gt;
&lt;p&gt;邮件模板和普通网页不太一样。邮件的 HTML 不支持 HTML5 且大多只能使用 table 布局，且很多邮箱客户端会限制 CSS 能力，有些样式在浏览器里正常，在邮箱里可能就会出现间距、图片、圆角或链接显示不一致的问题。&lt;/p&gt;
&lt;p&gt;项目提供了基于真实 SMTP 发信的测试命令，可以把导出的模板发送到你的测试邮箱中，测试数据会在邮件内容中覆盖中文长文本、中英文混排、长链接、代码块、行内代码、远程图片、父评论和回复内容等场景，检查实际显示效果。&lt;/p&gt;
&lt;p&gt;本项目自带的 &lt;code&gt;Rainafter&lt;/code&gt; 和 &lt;code&gt;Fuwari&lt;/code&gt; 主题已经过测试，兼容主流邮箱的网页端和客户端，如果你基于现有主题修改或者开发了自己的主题，建议也通过测试邮件来检查最终效果。&lt;/p&gt;
&lt;p&gt;先复制环境变量示例文件：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;cp .env.example .env
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;然后在 &lt;code&gt;.env&lt;/code&gt; 中填写测试用的 SMTP 信息。需要配置的核心内容包括：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;EMAIL_TEST_RECIPIENTS=test@example.com
EMAIL_TEST_FROM=Rainafter Mail Test &amp;lt;sender@example.com&amp;gt;
SMTP_HOST=smtp.example.com
SMTP_PORT=587
SMTP_USER=sender@example.com
SMTP_PASS=your-smtp-password
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;如果你的 SMTP 服务商需要 SSL/TLS 或 STARTTLS，也可以按实际情况配置：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;SMTP_SECURE=false
SMTP_REQUIRE_TLS=false
EMAIL_TEST_SUBJECT_PREFIX=[email-compat]
EMAIL_TEST_SEND_DELAY_MS=1000
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;配置完成后运行：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;pnpm test:email
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这个命令会先执行构建，检查 &lt;code&gt;out/&lt;/code&gt; 目录下的模板产物是否存在，然后按照“模板 × 收件人”的组合发送测试邮件。&lt;/p&gt;
&lt;p&gt;需要注意的是，&lt;code&gt;pnpm test:email&lt;/code&gt; 会真实发送邮件，所以建议使用专门的测试邮箱和 SMTP 授权码，不要把 &lt;code&gt;.env&lt;/code&gt; 提交到仓库。如果配置了多个收件人，每个收件人都会收到全部模板的测试邮件。&lt;/p&gt;
&lt;p&gt;:::note
建议在修改主题配色或模板后，配置 Outlook邮箱、Gmail 邮箱、QQ邮箱、163邮箱等常见邮箱，在对应邮箱服务商的网页端和APP端观察排版、样式等来进行测试。
:::&lt;/p&gt;
&lt;h2&gt;七、将邮件模板添加到 Twikoo 中&lt;/h2&gt;
&lt;p&gt;模板导出并测试完成后，就可以把它替换到 Twikoo 中了，导出后的文件在 &lt;code&gt;out/&lt;/code&gt; 目录中。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;out/
├── rainafter/
│   ├── notification.html
│   └── notification-admin.html
└── fuwari/
    ├── notification.html
    └── notification-admin.html
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;其中：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;emails/rainafter/&lt;/code&gt;：Rainafter 主题。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;emails/fuwari/&lt;/code&gt;：Fuwari 主题。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;notification.html&lt;/code&gt;：访客回复通知模板。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;notification-admin.html&lt;/code&gt;：管理员新评论通知模板。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;进入 Twikoo 管理面板，找到 &lt;code&gt;配置管理&lt;/code&gt; -&amp;gt; &lt;code&gt;邮件通知&lt;/code&gt; ，然后按模板类型填写：&lt;/p&gt;
&lt;p&gt;找到对应主题的 &lt;code&gt;访客回复通知模板&lt;/code&gt; 和 &lt;code&gt;管理员新评论通知模板&lt;/code&gt; html 文件，右键使用记事本打开后复制完整内容，粘贴到 Twikoo 后台的对应输入框里：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;将 &lt;code&gt;访客回复通知模板&lt;/code&gt; 填入 &lt;code&gt;MAIL_TEMPLATE&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;将 &lt;code&gt;管理员新评论通知模板&lt;/code&gt; 填入 &lt;code&gt;MAIL_TEMPLATE_ADMIN&lt;/code&gt;。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src=&quot;https://resource.rainafter.cn/img/20260512090504939.webp&quot; alt=&quot;&quot; /&gt;
&lt;img src=&quot;https://resource.rainafter.cn/img/20260512090504940.webp&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;如果你还想同时调整邮件标题，也可以配置：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;MAIL_SUBJECT&lt;/code&gt;：访客回复通知邮件标题。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;MAIL_SUBJECT_ADMIN&lt;/code&gt;：管理员新评论通知邮件标题。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;保存配置后，因为并未覆盖 Twikoo 的测试邮件模板且博主自己的评论无法触发邮件通知，可自行使用除博主之外的用户名和邮箱发一条评论，触发邮件通知，检查最终效果。&lt;/p&gt;
&lt;h2&gt;八、最后&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;Rainafter-Twikoo-Email&lt;/code&gt; 本质上只是把 Twikoo 邮件模板这件事从“在后台里维护一大段 HTML”，变成了“用 React Email 开发、预览、导出和测试模板”。&lt;/p&gt;
&lt;p&gt;如果你正在使用 Twikoo，并且想让评论通知邮件更符合自己博客的视觉风格，可以直接使用项目内置的 &lt;code&gt;Rainafter&lt;/code&gt; 或 &lt;code&gt;Fuwari&lt;/code&gt; 主题；如果你想继续深度定制，也可以基于现有结构扩展自己的主题。&lt;/p&gt;
&lt;p&gt;后续如果有新的主题样式或更好的邮箱兼容性处理，我也会继续放到这个项目里。&lt;/p&gt;
</content:encoded></item><item><title>在Arch Linux上使用GVT-g直通Intel核显至Windows虚拟机</title><link>https://www.rainafter.cn/posts/%E5%9C%A8arch-linux%E4%B8%8A%E4%BD%BF%E7%94%A8gvt-g%E7%9B%B4%E9%80%9Aintel%E6%A0%B8%E6%98%BE%E8%87%B3windows%E8%99%9A%E6%8B%9F%E6%9C%BA/</link><guid isPermaLink="true">https://www.rainafter.cn/posts/%E5%9C%A8arch-linux%E4%B8%8A%E4%BD%BF%E7%94%A8gvt-g%E7%9B%B4%E9%80%9Aintel%E6%A0%B8%E6%98%BE%E8%87%B3windows%E8%99%9A%E6%8B%9F%E6%9C%BA/</guid><description>在 Arch Linux 主机上用 QEMU/KVM将 Intel 核显以 GVT-g vGPU 形式分配给虚拟机的完整过程。</description><pubDate>Tue, 05 May 2026 00:00:00 GMT</pubDate><content:encoded>&lt;h1&gt;在Arch Linux上使用GVT-g直通Intel核显至Windows虚拟机&lt;/h1&gt;
&lt;p&gt;:::note
本文记录在 Arch Linux 主机上用 QEMU/KVM 将 Intel 核显以 GVT-g vGPU 形式分配给虚拟机的完整过程。写作时间为 2026-05，GVT-g 已经是维护状态很差的旧技术，但胜在操作简单、适合老平台折腾，作为在Arch Linux上Wine运行异常、或者必须使用Windows时的一种高性能且体验优秀的解决方案（GVT-g vGPU性能远超过其他虚拟机显卡），并不适合把它当作新硬件的长期方案。
:::&lt;/p&gt;
&lt;h2&gt;先说结论和限制&lt;/h2&gt;
&lt;p&gt;Intel GVT-g 是 Intel i915 驱动里的 vGPU/mdev 方案。它不是把整块核显独占直通给虚拟机，而是在宿主机继续使用核显的同时，把核显资源切成一个或多个 mediated device，再通过 VFIO 交给虚拟机。虚拟机里看到的是一块 Intel 显卡，可以安装 Intel 官方驱动，性能通常比 QXL、virtio-gpu 或纯软件渲染好得多。&lt;/p&gt;
&lt;p&gt;但是它有几个必须先接受的限制：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;GVT-g 主要支持 Broadwell 到 Comet Lake 这一代区间的 Intel iGPU。Ice Lake 移动平台、Gen11 以及更新的 Xe/Gen12 架构通常不支持 GVT-g。新平台应优先看
SR-IOV 或完整设备直通。&lt;/li&gt;
&lt;li&gt;Intel 的 &lt;code&gt;intel/gvt-linux&lt;/code&gt; 仓库已经在 2024-10-03 归档，并明确写明项目不再维护，且存在已知安全逃逸问题。因此不要把不可信虚拟机接到这个方案上。&lt;/li&gt;
&lt;li&gt;GVT-g 依赖 &lt;code&gt;Intel VT-d/IOMMU&lt;/code&gt;、&lt;code&gt;i915.enable_gvt=1&lt;/code&gt;、&lt;code&gt;kvmgt&lt;/code&gt;、&lt;code&gt;mdev&lt;/code&gt;、&lt;code&gt;VFIO&lt;/code&gt; 以及 libvirt/QEMU 对 mdev 的支持。少一个环节都会导致 &lt;code&gt;mdev_supported_types&lt;/code&gt; 不出现。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;本文的宿主机为 Intel CPU + Intel 核显，核显 PCI 地址为 &lt;code&gt;0000:00:02.0&lt;/code&gt;。这是多数笔记本和台式机的默认地址，但不要盲信，实际应以 &lt;code&gt;lspci -D -nn&lt;/code&gt; 为准。&lt;/p&gt;
&lt;h2&gt;一、BIOS/UEFI 固件设置&lt;/h2&gt;
&lt;p&gt;重启进入主板或笔记本固件设置，确认至少开启：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;Intel Virtualization Technology&lt;/code&gt; 或 &lt;code&gt;VT-x&lt;/code&gt;：KVM 虚拟化 CPU 需要。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Intel VT-d&lt;/code&gt;：IOMMU/VFIO 需要，GVT-g 必须启用。&lt;/li&gt;
&lt;li&gt;核显保持启用：如果机器有独显，不要把 iGPU 完全禁用。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;进入系统后可以先做硬件检查：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;lscpu | grep -E &apos;Virtualization|Model name&apos;
lspci -D -nn | grep -Ei &apos;vga|3d|display&apos;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;输出结果应类似，保证核显型号在上述允许范围内：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;0000:00:02.0 VGA compatible controller [0300]: Intel Corporation UHD Graphics 620 [8086:5917]
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;二、安装 QEMU/KVM、libvirt 和 virt-manager&lt;/h2&gt;
&lt;p&gt;先更新系统：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;sudo pacman -Syyu
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;安装 QEMU/KVM、libvirt 和 virt-manager 以及必要依赖：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;sudo pacman -S qemu-full virt-manager libvirt ovmf dnsmasq vde2 iproute2
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这里需要注意一个 Arch 包名变化：当前 Arch 官方仓库中的 OVMF 包名是
&lt;code&gt;edk2-ovmf&lt;/code&gt;，不是 &lt;code&gt;ovmf&lt;/code&gt; (本文写作时间为2026年5月)。如果 &lt;code&gt;pacman&lt;/code&gt; 报 &lt;code&gt;target not found: ovmf&lt;/code&gt;，请使用：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;sudo pacman -S qemu-full virt-manager libvirt edk2-ovmf dnsmasq vde2 iproute2
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;各包的作用如下：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;qemu-full&lt;/code&gt;：完整 QEMU 安装集合。提供 &lt;code&gt;qemu-system-x86_64&lt;/code&gt;、&lt;code&gt;qemu-img&lt;/code&gt;、各类 block/audio/display 后端和桌面虚拟化常用模块。GVT-g 最终由 QEMU 的 &lt;code&gt;vfio-pci&lt;/code&gt; 设备把 mdev 交给虚拟机。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;virt-manager&lt;/code&gt;：图形化虚拟机管理器。它调用 libvirt 创建、编辑、启动虚拟机，适合先完成 Windows/Linux 虚拟机基础配置，再手动补充 GVT-g XML。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;libvirt&lt;/code&gt;：虚拟机管理守护进程和 API。负责保存 domain XML、管理存储池、网络、权限、mdev/hostdev 设备，以及调用 QEMU。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;edk2-ovmf&lt;/code&gt; / 老文档里的 &lt;code&gt;ovmf&lt;/code&gt;：虚拟机 UEFI 固件。Windows 11 基本应使用 OVMF；Secure Boot 相关固件文件通常位于 &lt;code&gt;/usr/share/edk2/x64/&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;dnsmasq&lt;/code&gt;：libvirt 默认 NAT 网络的 DHCP/DNS 后端。一般不要手动启动系统级 &lt;code&gt;dnsmasq.service&lt;/code&gt;，libvirt 会为每个虚拟网络启动自己的 dnsmasq 实例。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;vde2&lt;/code&gt;：Virtual Distributed Ethernet，QEMU 可用的虚拟以太网工具。普通 virt-manager/libvirt NAT 网络不一定直接用到它，但安装完整 QEMU 桌面环境时常见。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;iproute2&lt;/code&gt;：提供 &lt;code&gt;ip&lt;/code&gt;、&lt;code&gt;ss&lt;/code&gt;、&lt;code&gt;tc&lt;/code&gt; 等网络工具。排查 &lt;code&gt;virbr0&lt;/code&gt;、tap、路由和地址时必备。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;如果 libvirt 默认 NAT 网络报错，还可能需要额外安装：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;sudo pacman -S iptables-nft
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;libvirt&lt;/code&gt; 把 &lt;code&gt;dnsmasq&lt;/code&gt; 和 &lt;code&gt;iptables-nft&lt;/code&gt; 都列为默认 NAT/DHCP 网络相关的可选依赖。&lt;/p&gt;
&lt;p&gt;启用 libvirt：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;sudo systemctl enable --now libvirtd
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;把当前用户加入常用虚拟化组：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;sudo usermod -aG libvirt,kvm &quot;$USER&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;注销并重新登录，或者临时执行：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;newgrp libvirt
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;启动并设置默认 NAT 网络自启：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;sudo virsh net-autostart default
sudo virsh net-start default
sudo virsh net-list --all
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;如果 &lt;code&gt;default&lt;/code&gt; 已经是 active，&lt;code&gt;net-start&lt;/code&gt; 报错“already active”是正常现象。&lt;/p&gt;
&lt;h2&gt;三、设置 GRUB 内核参数&lt;/h2&gt;
&lt;p&gt;GVT-g 需要 IOMMU 和 i915 GVT 开关。编辑：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;vim /etc/default/grub
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;找到 &lt;code&gt;GRUB_CMDLINE_LINUX_DEFAULT&lt;/code&gt;，加入下面这些参数：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;intel_iommu=on iommu=pt i915.enable_gvt=1 i915.enable_guc=0 i915.enable_fbc=0
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;一个完整例子：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;GRUB_CMDLINE_LINUX_DEFAULT=&quot;loglevel=3 quiet&quot;

GRUB_CMDLINE_LINUX_DEFAULT=&quot;loglevel=3 quiet intel_iommu=on iommu=pt i915.enable_gvt=1 i915.enable_guc=0 i915.enable_fbc=0&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;参数解释：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;intel_iommu=on&lt;/code&gt;：启用 Intel VT-d/IOMMU。没有它，VFIO/mdev 设备无法安全分配给虚拟机。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;i915.enable_gvt=1&lt;/code&gt;：启用 i915 的 GVT-g 支持。没有它，通常不会出现
&lt;code&gt;/sys/bus/pci/devices/0000:00:02.0/mdev_supported_types/&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;i915.enable_guc=0&lt;/code&gt;：关闭 GuC/HuC 固件加载。&lt;a href=&quot;https://wiki.archlinux.org/title/Intel_GVT-g&quot;&gt;ArchWiki&lt;/a&gt; 中对 GVT-g 与 GuC/HuC 的组合有警告，为了稳定性这里按 GVT-g 文档关闭。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;i915.enable_fbc=0&lt;/code&gt;：可选，关闭 framebuffer compression。在使用 DMA-BUF 显示的场景下更稳定。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;iommu=pt&lt;/code&gt;：可选，把未直通设备放在 passthrough IOMMU 模式，可能降低宿主机 IOMMU 开销。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;生成 GRUB 配置：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;sudo grub-mkconfig -o /boot/grub/grub.cfg
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;然后重启：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;sudo reboot
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;重启后确认参数生效：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;cat /proc/cmdline
dmesg | grep -Ei &apos;DMAR|IOMMU|GVT|i915&apos;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;能看到类似 &lt;code&gt;Intel-IOMMU: enabled&lt;/code&gt;、&lt;code&gt;DMAR&lt;/code&gt;、&lt;code&gt;GVT&lt;/code&gt;、&lt;code&gt;i915&lt;/code&gt; 的相关输出则证明配置成功。如果完全没有 IOMMU 信息，先参考&lt;a href=&quot;#%E4%B8%80biosuefi-%E5%9B%BA%E4%BB%B6%E8%AE%BE%E7%BD%AE&quot;&gt;第一步&lt;/a&gt;在 BIOS 检查 VT-d，再检查 GRUB 参数是否真的生效。&lt;/p&gt;
&lt;h2&gt;四. 启动 KVM、VFIO 和 mdev 模块&lt;/h2&gt;
&lt;p&gt;创建 &lt;code&gt;/etc/modules-load.d/kvm.conf&lt;/code&gt;：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;sudoedit /etc/modules-load.d/kvm.conf
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;写入：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;kvm
kvm_intel
kvmgt
vfio
vfio_iommu_type1
mdev
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;说明：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;kvm&lt;/code&gt;：KVM 核心模块。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;kvm_intel&lt;/code&gt;：Intel CPU 的 KVM 加速模块。AMD 平台才是 &lt;code&gt;kvm_amd&lt;/code&gt;，但本文是 Intel GVT-g。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;kvmgt&lt;/code&gt;：Intel GVT-g 的 KVM 后端。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;vfio&lt;/code&gt;：VFIO 核心，用于直通设备到虚拟机。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;vfio_iommu_type1&lt;/code&gt;：VFIO IOMMU 后端，GVT-g mdev 需要它来分配给虚拟机。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;mdev&lt;/code&gt;：Linux mediated device 框架。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;有些内核会把某些模块编译进内核，&lt;code&gt;lsmod&lt;/code&gt; 看不到不一定是坏事；但如果&lt;code&gt;systemd-modules-load&lt;/code&gt; 明确报模块不存在，要确认当前正在运行的内核和&lt;code&gt;/usr/lib/modules/$(uname -r)&lt;/code&gt; 是否匹配。Arch 更新内核后未重启时，很容易出现“新模块目录已安装，当前运行旧内核加载不到模块”的现象。&lt;/p&gt;
&lt;p&gt;立即加载一次，避免等待重启：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;sudo modprobe kvm
sudo modprobe kvm_intel
sudo modprobe vfio
sudo modprobe vfio_iommu_type1
sudo modprobe mdev
sudo modprobe kvmgt
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;检查：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;lsmod | grep -E &apos;kvm|kvm_intel|kvmgt|vfio|mdev&apos;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;再检查核显是否暴露 mdev 类型：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;ls /sys/bus/pci/devices/0000:00:02.0/mdev_supported_types/
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;如果目录不存在，优先排查：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;CPU/iGPU 是否在 GVT-g 支持范围内。&lt;/li&gt;
&lt;li&gt;BIOS 是否开启 VT-d。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;/proc/cmdline&lt;/code&gt; 是否真的有 &lt;code&gt;intel_iommu=on i915.enable_gvt=1 i915.enable_guc=0&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;kvmgt&lt;/code&gt; 模块是否成功加载。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;dmesg | grep -i gvt&lt;/code&gt; 是否出现 &lt;code&gt;Unsupported device. GVT-g is disabled&lt;/code&gt;。&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;五、安装并使用 mdevctl 创建 GVT-g vGPU&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;mdevctl&lt;/code&gt; 是 mediated device 的管理和持久化工具。它会把定义保存到 &lt;code&gt;/etc/mdevctl.d/&lt;/code&gt;，并通过 udev 在父设备可用时自动创建设置为 &lt;code&gt;auto&lt;/code&gt; 的 mdev。&lt;/p&gt;
&lt;p&gt;安装 &lt;code&gt;mdevctl&lt;/code&gt; 的 AUR 包：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;yay -S mdevctl
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;安装后列出可创建的 mdev 类型：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;mdevctl types
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;具体名字由平台决定，一般来说，同一平台内编号更低的类型分配更多显存、更高分辨率或更多 GPU 时间片，但可同时创建的实例更少。创建前可以看 mdevctl types 命令的输出：&lt;/p&gt;
&lt;p&gt;例如：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;0000:00:02.0
  i915-GVTg_V5_4
    Available instances: 0
    Device API: vfio-pci
    Name: GVTg_V5_4
    Description: low_gm_size: 128MB, high_gm_size: 512MB, fence: 4, resolution: 1920x1080, weight: 4
  i915-GVTg_V5_8
    Available instances: 0
    Device API: vfio-pci
    Name: GVTg_V5_8
    Description: low_gm_size: 64MB, high_gm_size: 384MB, fence: 4, resolution: 1024x768, weight: 2
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;定义一个自动启动的 GVT-g vGPU：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;sudo mdevctl define --auto --uuid $(uuidgen) --parent 0000:00:02.0 --type i915-GVTg_V5_4
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;注意：&lt;code&gt;define --auto&lt;/code&gt; 的含义是“创建持久化定义，并设置为父设备出现时自动启动”。它不会总是等价于“当前立刻创建一个已经运行的 mdev”。为了现在就用，继续执行：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;sudo mdevctl start --uuid &quot;$GVT_UUID&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;检查当前运行的 mdev：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;mdevctl list
mdevctl list -d
ls -l &quot;/sys/bus/mdev/devices/$GVT_UUID&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;可以看到 GVT-g vGPU 设备成功创建，如果 virt-manager 或 libvirt 看不到新设备，重启 libvirt：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;sudo systemctl restart libvirtd
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;如果要删除设备：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;sudo mdevctl stop --uuid &quot;$GVT_UUID&quot;
sudo mdevctl undefine --uuid &quot;$GVT_UUID&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;六、用 virt-manager 创建基础虚拟机&lt;/h2&gt;
&lt;p&gt;建议先创建一台普通虚拟机，安装好系统和驱动，再添加 GVT-g。这样排错简单，毕竟如果加 GVT-g vGPU 后黑屏，还能回退到普通显示设备。&lt;/p&gt;
&lt;h3&gt;6.1 新建虚拟机&lt;/h3&gt;
&lt;p&gt;打开 virt-manager （安装后在DE桌面环境中名字往往是 &quot;虚拟系统管理器&quot;） ，在界面中：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;连接到 &lt;code&gt;QEMU/KVM&lt;/code&gt; 系统会话，不是用户会话。系统会话通常显示为
&lt;code&gt;qemu:///system&lt;/code&gt;，权限和 mdev/VFIO 配合更省事。&lt;/li&gt;
&lt;li&gt;点击“新建虚拟机”。&lt;/li&gt;
&lt;li&gt;选择本地 ISO，例如 Windows 11 的镜像文件。&lt;/li&gt;
&lt;li&gt;选择系统类型。Windows 11 请选择 Windows 11，或至少 Windows 10/11 相近模板。&lt;/li&gt;
&lt;li&gt;分配 CPU 和内存。示例：4 vCPU、8 GiB RAM。&lt;/li&gt;
&lt;li&gt;创建 qcow2 磁盘。示例：60 GiB 或更大。&lt;/li&gt;
&lt;li&gt;勾选“安装前自定义配置”。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&amp;lt;div style=&quot;
display: grid;
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
gap: 15px;
justify-content: center;
&quot;&amp;gt;
&amp;lt;img src=&quot;https://resource.rainafter.cn/img/20260504190853073.webp&quot; style=&quot;width: 100%; height: auto; display: block;&quot; /&amp;gt;
&amp;lt;img src=&quot;https://resource.rainafter.cn/img/20260504190853074.webp&quot; style=&quot;width: 100%; height: auto; display: block;&quot; /&amp;gt;
&amp;lt;img src=&quot;https://resource.rainafter.cn/img/20260504190853075.webp&quot; style=&quot;width: 100%; height: auto; display: block;&quot; /&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div style=&quot;text-align: center; font-size: 0.9em; color: gray;&quot;&amp;gt;新建虚拟机&amp;lt;/div&amp;gt;&lt;/p&gt;
&lt;h3&gt;6.2 配置基础硬件&lt;/h3&gt;
&lt;p&gt;在“概况”里调整：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;芯片组：&lt;code&gt;Q35&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;固件：&lt;code&gt;UEFI x86_64&lt;/code&gt; / OVMF。Windows 11 推荐 UEFI + TPM 2.0。&lt;/li&gt;
&lt;li&gt;CPU：&lt;code&gt;host-passthrough&lt;/code&gt;。注意在拓扑中勾选手动设置CPU拓扑，例如设置 sockets=1（插槽数）、cores=2（核心数）、threads=2（线程数）即表示共分配 1x2x2=4 个CPU核心。&lt;/li&gt;
&lt;li&gt;磁盘总线：新装系统推荐 &lt;code&gt;VirtIO&lt;/code&gt;，但 Windows 安装时要加载 VirtIO 驱动 ISO；为了省事也可以先用 SATA，安装完再迁移。&lt;/li&gt;
&lt;li&gt;网卡：&lt;code&gt;virtio&lt;/code&gt; 性能最好；若安装阶段缺驱动，可临时用 &lt;code&gt;e1000e&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;显示：初始安装阶段先保留 &lt;code&gt;SPICE&lt;/code&gt; + &lt;code&gt;Video QXL&lt;/code&gt; 或 &lt;code&gt;Virtio&lt;/code&gt;，安装完系统和远程访问后再移除。&lt;/li&gt;
&lt;li&gt;Windows 11：添加 &lt;code&gt;TPM&lt;/code&gt;，型号选 &lt;code&gt;CRB&lt;/code&gt;，版本 &lt;code&gt;2.0&lt;/code&gt;。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&amp;lt;div style=&quot;
display: grid;
grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
gap: 15px;
justify-content: center;
&quot;&amp;gt;
&amp;lt;img src=&quot;https://resource.rainafter.cn/img/20260504191142926.webp&quot; style=&quot;width: 100%; height: auto; display: block;&quot; /&amp;gt;
&amp;lt;img src=&quot;https://resource.rainafter.cn/img/20260504191142927.webp&quot; style=&quot;width: 100%; height: auto; display: block;&quot; /&amp;gt;
&amp;lt;img src=&quot;https://resource.rainafter.cn/img/20260504191142928.webp&quot; style=&quot;width: 100%; height: auto; display: block;&quot; /&amp;gt;
&amp;lt;img src=&quot;https://resource.rainafter.cn/img/20260504191142929.webp&quot; style=&quot;width: 100%; height: auto; display: block;&quot; /&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div style=&quot;text-align: center; font-size: 0.9em; color: gray;&quot;&amp;gt;配置基础硬件&amp;lt;/div&amp;gt;&lt;/p&gt;
&lt;p&gt;启动虚拟机，完成系统安装。Windows 客户机里建议安装：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;VirtIO 驱动：磁盘、网卡、balloon、qemu guest agendt 等客户机驱动，可以点击&lt;a href=&quot;https://fedorapeople.org/groups/virt/virtio-win/direct-downloads/stable-virtio/virtio-win.iso&quot;&gt;下载链接&lt;/a&gt;跳转至Fedora People的virtio-win.iso最新版下载页面。&lt;/li&gt;
&lt;li&gt;Intel 核显驱动：添加 GVT-g 后需要它接管 vGPU，可以先在 Intel 官网下载驱动程序&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;在虚拟机下载 virtio-win.iso 后可以直接在资源管理器中双击打开，之后分别安装 &lt;code&gt;virtio-win-gt-x64.msi&lt;/code&gt;（图形相关驱动）和 &lt;code&gt;virtio-win-guest-tools-x64.msi&lt;/code&gt;（增强功能工具）。安装完成后重启虚拟机。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://resource.rainafter.cn/img/20260504203348236.webp&quot; alt=&quot;安装 virtio-win 驱动&quot; /&gt;
&amp;lt;div style=&quot;text-align: center; font-size: 0.9em; color: gray;&quot;&amp;gt;安装 virtio-win 驱动&amp;lt;/div&amp;gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://resource.rainafter.cn/img/20260505114828478.webp&quot; alt=&quot;安装 Intel 核显驱动&quot; /&gt;
&amp;lt;div style=&quot;text-align: center; font-size: 0.9em; color: gray;&quot;&amp;gt;安装 Intel 核显驱动&amp;lt;/div&amp;gt;&lt;/p&gt;
&lt;p&gt;安装 virtio-win 驱动后主机便可以与虚拟机共享剪贴板、传输文件、调整分辨率（但其驱动的 QXL 虚拟显卡性能远远不如接下来将要添加的 GVT-g vGPU）。&lt;/p&gt;
&lt;h2&gt;七、在 virt-manager 中添加 GVT-g vGPU&lt;/h2&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;code&gt;MDEV主机设备&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;找到刚创建的 GVT-g vGPU mdev 设备，名称通常包含 UUID，或显示为 &lt;code&gt;mdev_...&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;添加到虚拟机。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;img src=&quot;https://resource.rainafter.cn/img/20260504193405584.webp&quot; alt=&quot;添加 GVT-g mdev 设备&quot; /&gt;
&amp;lt;div style=&quot;text-align: center; font-size: 0.9em; color: gray;&quot;&amp;gt;添加 GVT-g mdev 设备&amp;lt;/div&amp;gt;&lt;/p&gt;
&lt;p&gt;添加后启动虚拟机，如果一切顺利，客户机里应该能看到新的 Intel 显卡设备（或除了已经添加的QXL显卡外，额外显示一个 Microsoft 标准显示适配器），安装官方的 Intel 显卡驱动后就可以使用了。&lt;/p&gt;
&lt;h2&gt;八、显示输出：使用 Looking Glass 标准共享内存&lt;/h2&gt;
&lt;p&gt;安装好 Intel 显卡驱动后，虚拟机里通常会同时看到 QXL/virtio 虚拟显卡和 Intel GVT-g vGPU。为了让 Windows 桌面真正跑在 Intel vGPU 上，同时获得比 SPICE/QXL 更流畅的低延迟画面，本文使用 &lt;a href=&quot;https://looking-glass.io/&quot;&gt;Looking Glass&lt;/a&gt; 作为显示输出方案。&lt;/p&gt;
&lt;p&gt;:::note
需要特别说明的是：Looking Glass 官方更推荐 &lt;code&gt;KVMFR&lt;/code&gt; 模块，因为 &lt;code&gt;KVMFR&lt;/code&gt; 可以让 GPU 通过 DMA 访问共享内存，性能更好。但经过实测 &lt;code&gt;KVMFR&lt;/code&gt; 方式会与 &lt;code&gt;virtiofs&lt;/code&gt; 冲突，无法将Arch Linux主机的文件夹共享至虚拟机，原因是 &lt;code&gt;virtiofs&lt;/code&gt; 依赖虚拟机内存共享来完成零拷贝的数据读写，但是 &lt;code&gt;virtiofs&lt;/code&gt; 无法将 &lt;code&gt;KVMFR&lt;/code&gt; 模块创建的设备的内存共享，造成冲突。因此本文按照 Looking Glass B7 文档中的 &lt;a href=&quot;https://looking-glass.io/docs/B7/ivshmem_shm/&quot;&gt;IVSHMEM 共享内存方式&lt;/a&gt; 方式配置，也就是使用 &lt;code&gt;/dev/shm/looking-glass&lt;/code&gt; 作为标准共享内存后端。
:::&lt;/p&gt;
&lt;h3&gt;8.1 在 Windows 虚拟机中安装 Looking Glass Host&lt;/h3&gt;
&lt;p&gt;安装步骤：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;在 Windows 虚拟机里打开 Looking Glass 下载页：&lt;a href=&quot;https://looking-glass.io/downloads&quot;&gt;https://looking-glass.io/downloads&lt;/a&gt;。&lt;/li&gt;
&lt;li&gt;下载与宿主机客户端版本一致的 &lt;code&gt;looking-glass-host-setup.exe&lt;/code&gt;，例如 B7 对 B7。&lt;/li&gt;
&lt;li&gt;右键安装器，选择“以管理员身份运行”。&lt;/li&gt;
&lt;li&gt;按安装向导一路继续。默认选项适合大多数场景，安装器会安装 IVSHMEM 驱动和 Looking Glass Host 服务。&lt;/li&gt;
&lt;li&gt;安装完成后，Looking Glass Host 会作为 Windows 服务运行。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;安装完成后关闭虚拟机，继续下面的显示设备调整和共享内存配置。&lt;/p&gt;
&lt;h3&gt;8.2 调整虚拟机显示设备&lt;/h3&gt;
&lt;p&gt;先关闭虚拟机，不要挂起。&lt;/p&gt;
&lt;p&gt;打开 virt-manager（虚拟系统管理器），选择已经创建的 Windows 虚拟机，进入“虚拟机详情”：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;左侧找到“显卡”设备，通常是“显卡 QXL”，点击进入。&lt;/li&gt;
&lt;li&gt;将视频型号改为 &lt;code&gt;None&lt;/code&gt;。这样 Windows 启动后主要显示设备就是 Intel GVT-g vGPU。&lt;/li&gt;
&lt;li&gt;如想要保留Windows启动前的BIOS画面，把上述的视频型号改为 &lt;code&gt;Ramfb&lt;/code&gt; 即可。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;img src=&quot;https://resource.rainafter.cn/img/20260504221702647.webp&quot; alt=&quot;调整显示设备&quot; /&gt;
&amp;lt;div style=&quot;text-align: center; font-size: 0.9em; color: gray;&quot;&amp;gt;调整显示设备&amp;lt;/div&amp;gt;&lt;/p&gt;
&lt;h3&gt;8.3 添加 Looking Glass 标准共享内存设备&lt;/h3&gt;
&lt;p&gt;在 virt-manager 的虚拟机详情界面，点击第一个 &lt;code&gt;概况&lt;/code&gt; ，再点击 详情 旁的 XML，滚动到最后，在 &lt;code&gt;&amp;lt;devices&amp;gt;&lt;/code&gt; 节点内加入一个新的共享内存设备：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;shmem name=&quot;looking-glass&quot;&amp;gt;
  &amp;lt;model type=&quot;ivshmem-plain&quot;/&amp;gt;
  &amp;lt;size unit=&quot;M&quot;&amp;gt;32&amp;lt;/size&amp;gt;
&amp;lt;/shmem&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&quot;https://resource.rainafter.cn/img/20260504222625023.webp&quot; alt=&quot;添加共享内存设备&quot; /&gt;
&amp;lt;div style=&quot;text-align: center; font-size: 0.9em; color: gray;&quot;&amp;gt;添加共享内存设备&amp;lt;/div&amp;gt;&lt;/p&gt;
&lt;p&gt;其中 &lt;code&gt;32M&lt;/code&gt; 适合 1920x1080 SDR。Looking Glass 官方给出的计算方式是：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;宽度 x 高度 x BPP x 2 = frame size in bytes
frame size / 1024 / 1024 = frame size in MiB
frame size in MiB + 10 = required size in MiB
向上取整到 2 的幂 = 最终大小
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;其中 SDR 32-bit RGB 的 &lt;code&gt;BPP&lt;/code&gt; 为 &lt;code&gt;4&lt;/code&gt;，HDR 64-bit 为 &lt;code&gt;8&lt;/code&gt;。常见取值可以直接按下面选择：&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;分辨率&lt;/th&gt;
&lt;th&gt;SDR&lt;/th&gt;
&lt;th&gt;HDR&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;1920x1080&lt;/td&gt;
&lt;td&gt;32M&lt;/td&gt;
&lt;td&gt;64M&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;1920x1200&lt;/td&gt;
&lt;td&gt;32M&lt;/td&gt;
&lt;td&gt;64M&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2560x1440&lt;/td&gt;
&lt;td&gt;64M&lt;/td&gt;
&lt;td&gt;128M&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3840x2160&lt;/td&gt;
&lt;td&gt;128M&lt;/td&gt;
&lt;td&gt;256M&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;这个值不是越大越好，设得过大只会固定占用宿主机内存。若设置太小，Looking Glass 会截断画面底部并提示需要增大共享内存。&lt;/p&gt;
&lt;h3&gt;8.4 设置 &lt;code&gt;/dev/shm/looking-glass&lt;/code&gt; 权限&lt;/h3&gt;
&lt;p&gt;标准共享内存方式会使用 &lt;code&gt;/dev/shm/looking-glass&lt;/code&gt;。默认情况下，这个文件可能由 QEMU 创建并归 QEMU 用户所有，普通用户运行 &lt;code&gt;looking-glass-client&lt;/code&gt; 时无法读写。接下来我们使用 &lt;code&gt;systemd-tmpfiles&lt;/code&gt; 预先创建并设置权限。&lt;/p&gt;
&lt;p&gt;新建 &lt;code&gt;/etc/tmpfiles.d/10-looking-glass.conf&lt;/code&gt;：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;vim /etc/tmpfiles.d/10-looking-glass.conf
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;写入：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# Type Path                    Mode UID         GID Age Argument
f /dev/shm/looking-glass       0660 User        kvm -
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;把其中的 &lt;code&gt;User&lt;/code&gt; 改成实际的 Arch Linux 用户名。然后执行：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;sudo systemd-tmpfiles --create /etc/tmpfiles.d/10-looking-glass.conf
ls -l /dev/shm/looking-glass
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;输出中应能看到文件归 Arch Linux 所使用的用户和 &lt;code&gt;kvm&lt;/code&gt; 组所有，权限为 &lt;code&gt;rw-rw----&lt;/code&gt;。&lt;/p&gt;
&lt;p&gt;如果后续修改了 &lt;code&gt;&amp;lt;size unit=&apos;M&apos;&amp;gt;...&amp;lt;/size&amp;gt;&lt;/code&gt; 的大小，需要先关闭虚拟机，再删除旧共享内存文件再重新创建：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;sudo rm -f /dev/shm/looking-glass
sudo systemd-tmpfiles --create /etc/tmpfiles.d/10-looking-glass.conf
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;8.5 在 Arch Linux 宿主机安装 Looking Glass Client&lt;/h3&gt;
&lt;p&gt;Arch 上可从 AUR 安装 Looking Glass 客户端。AUR 包名是 &lt;code&gt;looking-glass&lt;/code&gt;，安装后提供的命令是 &lt;code&gt;looking-glass-client&lt;/code&gt;：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;yay -S looking-glass
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;安装完成后在 virt-manager 启动 Windows 虚拟机，再打开桌面环境中的 Looking Glass Client 即可看到虚拟机画面，并且在安装 Intel 驱动后，直通的显卡也正常使用，其性能远超过普通的虚拟机自带显卡，完全可以作为在Arch Linux上必须使用Windows时的解决方案。
&lt;img src=&quot;https://resource.rainafter.cn/img/20260505114828476.webp&quot; alt=&quot;Looking Glass Client 显示虚拟机画面&quot; /&gt;
&amp;lt;div style=&quot;text-align: center; font-size: 0.9em; color: gray;&quot;&amp;gt;Looking Glass Client 显示虚拟机画面&amp;lt;/div&amp;gt;&lt;/p&gt;
&lt;h2&gt;九、验证虚拟机的 GVT-g vGPU 是否正常工作&lt;/h2&gt;
&lt;p&gt;虚拟机启动前确认 mdev 存在：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;mdevctl list
ls /sys/bus/mdev/devices/
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;启动虚拟机后查看 QEMU 日志：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;sudo journalctl -u libvirtd -b
sudo ls /var/log/libvirt/qemu/
sudo tail -n 200 /var/log/libvirt/qemu/&amp;lt;虚拟机名称&amp;gt;.log
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;虚拟机内验证：设备管理器中应出现 Intel 显卡；安装 Intel 驱动后不应是 Microsoft基本显示适配器。&lt;/p&gt;
&lt;p&gt;宿主机验证 GPU 活动可用：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;sudo pacman -S intel-gpu-tools
sudo intel_gpu_top
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;如果虚拟机观看视频时能看到 render/blitter/video 活动变化，说明 vGPU 在工作。&lt;/p&gt;
&lt;h2&gt;十、常见故障&lt;/h2&gt;
&lt;h3&gt;10.1 &lt;code&gt;/mdev_supported_types&lt;/code&gt; 不存在&lt;/h3&gt;
&lt;p&gt;检查：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;cat /proc/cmdline
lsmod | grep -E &apos;kvmgt|mdev|vfio&apos;
dmesg | grep -Ei &apos;gvt|i915|dmar|iommu&apos;
lspci -D -nn | grep -Ei &apos;vga|display&apos;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;常见原因：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;硬件不是 Broadwell 到 Comet Lake 这类 GVT-g 支持平台。&lt;/li&gt;
&lt;li&gt;BIOS 没开 VT-d。&lt;/li&gt;
&lt;li&gt;GRUB 参数没有生效。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;i915.enable_guc=0&lt;/code&gt; 没设置，或 GuC/HuC 配置与 GVT-g 冲突。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;kvmgt&lt;/code&gt; 没加载。&lt;/li&gt;
&lt;li&gt;内核升级后未重启，导致当前内核找不到匹配模块。&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;10.2 &lt;code&gt;mdevctl define/start&lt;/code&gt; 报没有空间或没有实例&lt;/h3&gt;
&lt;p&gt;检查可用实例数：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;cat /sys/bus/pci/devices/0000:00:02.0/mdev_supported_types/i915-GVTg_V4_1/available_instances
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;如果是 &lt;code&gt;0&lt;/code&gt;，说明该类型已经被占用，或显存资源不够。可以换更小类型，例如从 &lt;code&gt;V4_1&lt;/code&gt;
换成 &lt;code&gt;V4_2&lt;/code&gt;、&lt;code&gt;V4_4&lt;/code&gt;，具体以 &lt;code&gt;mdevctl types&lt;/code&gt; 输出为准。&lt;/p&gt;
&lt;h3&gt;10.3 virt-manager 看不到 mdev&lt;/h3&gt;
&lt;p&gt;先确认 mdev 已启动：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;mdevctl list
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;再重启 libvirt：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;sudo systemctl restart libvirtd
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;还可以用 libvirt 节点设备命令看：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;virsh nodedev-list --cap mdev
virsh nodedev-list --cap mdev_types
&lt;/code&gt;&lt;/pre&gt;
</content:encoded></item><item><title>樱花正好，春意渐长</title><link>https://www.rainafter.cn/posts/%E6%A8%B1%E8%8A%B1%E6%AD%A3%E5%A5%BD%E6%98%A5%E6%84%8F%E6%B8%90%E9%95%BF/</link><guid isPermaLink="true">https://www.rainafter.cn/posts/%E6%A8%B1%E8%8A%B1%E6%AD%A3%E5%A5%BD%E6%98%A5%E6%84%8F%E6%B8%90%E9%95%BF/</guid><description>待到树下，才看清是一树一树的樱花，花瓣薄得近乎透明，在阳光下泛着柔和的光。细细的脉络隐在花瓣之间，像是春天悄悄写下的纹路。偶尔有风从枝头掠过，花枝轻轻一颤，便有几片花瓣飘落下来，落在肩头。</description><pubDate>Sat, 28 Mar 2026 00:00:00 GMT</pubDate><content:encoded>&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;&amp;lt;div style=&quot;display: flex; gap:15px;&quot;&amp;gt;
&amp;lt;img src=&quot;https://resource.rainafter.cn/img/20260504010443868.webp&quot; width=&quot;50%&quot;/&amp;gt;
&amp;lt;img src=&quot;https://resource.rainafter.cn/img/20260504011920505.webp&quot; width=&quot;50%&quot;/&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div style=&quot;text-align: center; font-size: 0.9em; color: gray;&quot;&amp;gt;早樱&amp;lt;/div&amp;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;img src=&quot;https://resource.rainafter.cn/img/20260504012245217.webp&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
</content:encoded></item><item><title>生活如雨，心境如新</title><link>https://www.rainafter.cn/posts/%E7%94%9F%E6%B4%BB%E5%A6%82%E9%9B%A8%E5%BF%83%E5%A2%83%E5%A6%82%E6%96%B0/</link><guid isPermaLink="true">https://www.rainafter.cn/posts/%E7%94%9F%E6%B4%BB%E5%A6%82%E9%9B%A8%E5%BF%83%E5%A2%83%E5%A6%82%E6%96%B0/</guid><description>​最近是多雨的日子。对于远离家乡的我而言，这里的雨似乎比记忆中的更长，也更缠绵。尤其是在初秋这个时节，连续几日的秋雨仿佛要将整个城市洗刷得一尘不染。我常常站在窗前，看着雨丝织成的幕布。</description><pubDate>Wed, 24 Sep 2025 00:00:00 GMT</pubDate><content:encoded>&lt;h1&gt;一、引子&lt;/h1&gt;
&lt;p&gt;不久前有位朋友向我发送了一封邮件，说是在无意中寻到我的博客，很是喜欢其中几篇文章，同时也向我发出了一个疑问，表示对博客的介绍部分不理解。&lt;/p&gt;
&lt;p&gt;:::note
我想，生活如雨。雨中，听雨声轻灵。雨后，赏水木清华。聆听这广阔天地，感受那动人瞬间。
:::&lt;/p&gt;
&lt;p&gt;就是上述的这段话。这位朋友表示不明白为什么我会用“生活如雨”来形容我的生活状态。其实，这句话的灵感来源于我对雨天的一种微妙的感觉。雨天总是给人一种宁静、清新的感觉，仿佛一切都被洗涤过一般。而我希望我的生活也能像雨一样，充满变化与生机，同时又能保持内心的平静与纯净。&lt;/p&gt;
&lt;p&gt;这一首最近作的小诗，也算是对这个“生活如雨”的一个补充说明吧。&lt;/p&gt;
&lt;p&gt;:::poem{title=&quot;《秋》&quot; author=&quot;宇文Teacher&quot;}
秋雨初歇秋風涼，疏竹搖窗夜未央。
幾回夢醒聞雁落，半枕清愁憶舊鄉。
風拂殘燭驚孤影，雨打芭蕉起清商。
但得雲開千峯霽，終有心舒見朝陽。
:::
 
 
 &lt;/p&gt;
&lt;h1&gt;二、《生活如雨，心境如新》&lt;/h1&gt;
&lt;p&gt;​最近是多雨的日子。对于远离家乡的我而言，这里的雨似乎比记忆中的更长，也更缠绵。尤其是在初秋这个时节，连续几日的秋雨仿佛要将整个城市洗刷的一尘不染。我常常站在窗前，看着雨丝织成的幕布，看着院落里那株柿子树在雨中微微颤抖，那些尚且青涩的果实，在雨水的冲刷下显得愈发晶莹，我的心底却涌上一层淡淡的寂寥。
&amp;lt;div&amp;gt;
&amp;lt;div style=&quot;display: flex; gap:15px;&quot;&amp;gt;
&amp;lt;img src=&quot;https://resource.rainafter.cn/img/20250924002857784.jpg&quot; style=&quot;max-width: 50%; object-fit: cover;&quot;/&amp;gt;
&amp;lt;img src=&quot;https://resource.rainafter.cn/img/20250924002844138.jpg&quot; style=&quot;max-width: 50%; object-fit: cover;&quot;/&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div style=&quot;text-align: center; font-size: 0.9em; color: gray;&quot;&amp;gt;庭院里的柿子树&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;&lt;/p&gt;
&lt;p&gt;然而，正是在一场急雨停歇之后，我的一次不意间的外出，让心境忽然开阔。那场雨来得急，去得也快，仿佛是天空积蓄已久的情绪，在瞬间爆发，又在瞬间平复。当它停歇时，空气里弥漫着泥土与草木的清香，带着一种久违的清新；我走出门，踩在湿漉漉的路上，脚下传来细微的声响。转角处的芭蕉叶被雨水冲刷得透亮碧绿，叶尖垂着几颗水珠，随着微风轻轻颤动，忽而坠落，溅起清脆的声响；我抬头望向远方，在薄雾间，远处传来几声雁鸣，在薄雾间回荡，带着穿透人心的力量。那一刻，我仿佛被击中，心思随着雁声飞远，穿越了重重雨幕，又回到眼前这被雨水冲洗过的世界：青苔正从石缝里探出头来，脆弱却坚定，在雨后显得格外有生机。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://resource.rainafter.cn/img/20250924002830368.jpg&quot; alt=&quot;雨打芭蕉&quot; /&gt;
&amp;lt;div style=&quot;text-align: center; font-size: 0.9em; color: gray;&quot;&amp;gt;雨打芭蕉&amp;lt;/div&amp;gt;&lt;/p&gt;
&lt;h2&gt;我曾以为自己是爱雨的。&lt;/h2&gt;
&lt;p&gt;孩提时，雨天是最自由的冒险。奔跑在积水中，溅起一朵又一朵小小的浪花，好像连天空都在与我一同嬉笑。那时的雨，单纯而无忧。而只有在雨天家人才会在送我去上学的路上打开那把宽大的花伞，撑起一片属于我的小天地，雨滴打在伞面上，发出“噼啪”的声音，像是为我奏响的专属乐章。那时的我，总觉得雨天是最特别的日子，因为它让我真切地感受到一种被保护的稳稳地幸福。&lt;/p&gt;
&lt;h2&gt;​可我真的是喜欢雨吗？&lt;/h2&gt;
&lt;p&gt;或许并非如此。我逐渐的开始意识到，我喜欢的并不是雨本身，而是雨过后的宁静与新生。我喜欢那混合着泥土气息的清香，像是大地在舒展的呼吸，带着一种原始而又充满力量的气息；我喜欢雨水洗涤过的万物，颜色更浓，姿态更鲜活。那株柿子树，叶子更绿，果实更青；那片芭蕉叶，碧绿透亮，水珠晶莹。我喜欢细雨停歇后，那一抹新绿在风中轻轻摇曳，仿佛是在提醒我，生命仍在悄然滋长，即使经历了风雨的洗礼，依然能够迸发出新的生机。&lt;/p&gt;
&lt;h2&gt;​我只是不讨厌雨罢了。&lt;/h2&gt;
&lt;p&gt;我想这是一种新的心境。它不同于童年时对雨的欢喜，也不同于青春期对雨的厌倦，而是一种更为成熟、更加复杂的感受。里面有对故乡的思念，有对生活的感悟，也有对未来的期待。或许，我们不必非要等到云开雾散，才去寻找清朗的时刻；即使在绵绵秋雨之中，我们依旧能够安静地看见美好、找到属于自己的那份平静。此刻的我，不喜不悲，只是静静望着雨，感受它带来的变化。这大概就是我与雨之间，最真实、也最舒适的关系。&lt;/p&gt;
</content:encoded></item><item><title>使用Prisma ORM连接多个数据库的最佳实践</title><link>https://www.rainafter.cn/posts/%E4%BD%BF%E7%94%A8prisma-orm%E8%BF%9E%E6%8E%A5%E5%A4%9A%E4%B8%AA%E6%95%B0%E6%8D%AE%E5%BA%93/</link><guid isPermaLink="true">https://www.rainafter.cn/posts/%E4%BD%BF%E7%94%A8prisma-orm%E8%BF%9E%E6%8E%A5%E5%A4%9A%E4%B8%AA%E6%95%B0%E6%8D%AE%E5%BA%93/</guid><description>最近遇到了需要使用Prisma ORM同时连接两个不同MySQL数据库进行数据读写的需求。然而，Prisma ORM默认只支持单数据库连接。经过深入研究和实践，我总结出了一套适用于此类需求的最佳实践方案。</description><pubDate>Mon, 25 Aug 2025 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;一、起因&lt;/h2&gt;
&lt;p&gt;最近在一个Next.js + tRPC + Prisma ORM + MySQL的技术栈项目中，我遇到了需要同时连接两个不同MySQL数据库进行数据读写的需求。然而，Prisma ORM默认只支持单数据库连接，官方文档对多数据库场景的解决方案并不详细。经过深入研究和实践，我总结出了一套适用于此类需求的最佳实践方案。&lt;/p&gt;
&lt;h2&gt;二、我实现的Prisma多数据库连接的原理&lt;/h2&gt;
&lt;h3&gt;2.1 单数据库的局限性&lt;/h3&gt;
&lt;p&gt;Prisma ORM的标准配置是面向单数据库设计的：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;datasource db {
  provider = &quot;mysql&quot;
  url      = env(&quot;DATABASE_URL&quot;)
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这种配置无法直接扩展到多数据库场景。&lt;/p&gt;
&lt;h3&gt;2.2 多数据库的解决思路&lt;/h3&gt;
&lt;p&gt;Prisma虽然不支持在单个&lt;code&gt;schema.prisma&lt;/code&gt;文件中定义多个&lt;code&gt;datasource&lt;/code&gt;块来连接多个数据库，但我们可以通过以下方式实现：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;创建多个独立的Schema文件：&lt;/strong&gt; 每个数据库对应一个schema文件&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;生成独立的客户端：&lt;/strong&gt; 为每个数据库生成专用的Prisma Client&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;统一管理连接实例：&lt;/strong&gt; 在应用层统一管理多个数据库连接&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;三、完整实现方案&lt;/h2&gt;
&lt;h3&gt;3.1 项目结构：&lt;/h3&gt;
&lt;p&gt;首先，我们需要合理规划项目结构，将多个配置文件有序组织：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;project-root
 ├── next.config.js
 ├── package.json
 ├── /prisma                   // Prisma ORM配置目录
 │   ├── /prisma-client        // 自定义的Prisma客户端代码目录
 │   │   ├── /db1Client        // 数据库1的客户端
 │   │   └── /db2Client        // 数据库2的客户端
 │   ├── schema1.prisma        // 第一个数据库的Schema文件
 │   └── schema2.prisma        // 第二个数据库的Schema文件
 ├── /public
 │   ├── assets
 │   └── favicon.ico
 ├── /src
 │   ├── /app
 │   ├── /server               // 服务端代码目录
 │   │   ├── /api
 │   │   └── db.ts             // Prisma ORM数据库连接配置文件
 │   ├── /trpc
 │   └── env.js
 └── tsconfig.json
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;3.2 配置多个Schema文件和生成客户端&lt;/h3&gt;
&lt;p&gt;我们在&lt;code&gt;/prisma&lt;/code&gt;目录下创建两个&lt;code&gt;schema.prisma&lt;/code&gt;文件，分别命名为&lt;code&gt;schema.prisma&lt;/code&gt;和&lt;code&gt;schema2.prisma&lt;/code&gt;，分别配置不同的数据库连接。同时在&lt;code&gt;/prisma/prisma-client&lt;/code&gt;目录下创建了两个子目录&lt;code&gt;db1Client&lt;/code&gt;和&lt;code&gt;db2Client&lt;/code&gt;，用于存放生成的Prisma客户端代码。&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;配置不同的数据库连接&lt;/strong&gt;：
&lt;strong&gt;在&lt;code&gt;schema1.prisma&lt;/code&gt;中配置第一个数据库连接：&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code&gt;datasource db1 {
  provider = &quot;mysql&quot;
  url      = env(&quot;DATABASE_URL_1&quot;)
}

generator client {
  provider = &quot;prisma-client-js&quot;
  // 自定义客户端代码输出目录
  output   = &quot;./prisma-client/db1Client&quot;
}

// 在此处定义第一个数据库的模型...
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;在&lt;code&gt;schema2.prisma&lt;/code&gt;中配置第二个数据库连接：&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;datasource db2 {
  provider = &quot;mysql&quot;
  url      = env(&quot;DATABASE_URL_2&quot;)
}

generator client {
  provider = &quot;prisma-client-js&quot;
  // 自定义客户端代码输出目录
  output   = &quot;./prisma-client/db2Client&quot;
}

// 在此处定义第二个数据库的模型...
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;注意：我们在每个&lt;code&gt;schema.prisma&lt;/code&gt;文件中都自定义了一个Prisma客户端代码输出目录为 &lt;strong&gt;/prisma/prisma-client/&lt;/strong&gt;，以便我们再后续修改Prisma数据库连接配置文件时使用。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;关键配置说明：&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;每个schema使用不同的数据源名称（&lt;code&gt;db1&lt;/code&gt;、&lt;code&gt;db2&lt;/code&gt;）&lt;/li&gt;
&lt;li&gt;通过&lt;code&gt;output&lt;/code&gt;字段将客户端代码生成到不同目录（默认情况下Prisma会将操作数据库的客户端代码生成在&lt;code&gt;/node_modules&lt;/code&gt;文件夹下的模块自有目录内并在后续的配置文件中将客户端代码隐式引用，因此我们需要自定义&lt;code&gt;output&lt;/code&gt;字段）&lt;/li&gt;
&lt;li&gt;使用不同的环境变量来配置数据库连接字符串&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;3.3 创建统一的数据库连接管理器&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;修改Prisma ORM的数据库连接配置文件：&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;在&lt;code&gt;/src/server/db.ts&lt;/code&gt;中创建和管理多个数据库连接：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;// 导入上一步中自定义目录中生成的Prisma客户端
import { PrismaClient as DB1Client } from &quot;../../prisma/prisma-client/db1Client&quot;; 
import { PrismaClient as DB2Client } from &quot;../../prisma/prisma-client/db2Client&quot;;

import { env } from &quot;~/env&quot;;

// 第一个数据库的客户端工厂函数
const createPrismaClient = () =&amp;gt; {
  return new DB1Client({
    log: env.NODE_ENV === &quot;development&quot; ? [&quot;query&quot;, &quot;error&quot;, &quot;warn&quot;] : [&quot;error&quot;],
  });
};

// 第二个数据库的客户端工厂函数
const createPrismaClient2 = () =&amp;gt; {
  return new DB2Client({
    log: env.NODE_ENV === &quot;development&quot; ? [&quot;query&quot;, &quot;error&quot;, &quot;warn&quot;] : [&quot;error&quot;],
  });
};

// 开发环境下的全局实例管理，避免热重载时重复创建连接
const globalForPrisma = globalThis as unknown as {
  prisma: ReturnType&amp;lt;typeof createPrismaClient&amp;gt; | undefined;
};

const globalForPrisma2 = globalThis as unknown as {
  prisma2: ReturnType&amp;lt;typeof createPrismaClient2&amp;gt; | undefined;
};

// 导出数据库连接实例
export const db = globalForPrisma.prisma ?? createPrismaClient();
export const db2 = globalForPrisma2.prisma2 ?? createPrismaClient2();

// 在非生产环境下缓存实例
if (env.NODE_ENV !== &quot;production&quot;) {
  globalForPrisma.prisma = db;
  globalForPrisma2.prisma2 = db2;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;3.4 更新NPM Scripts&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;修改&lt;code&gt;package.json&lt;/code&gt;中的Prisma相关命令，添加对多数据库的支持：&lt;/strong&gt;：&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;我们需要修改原来对prisma迁移的命令，在原有执行 &lt;code&gt;prisma migrate dev&lt;/code&gt; 命令之后加入参数&lt;code&gt;--schema=./prisma/schema2.prisma&lt;/code&gt;来显式声明这是对db2数据库的操作&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;{
  &quot;scripts&quot;: {
    // 第一个数据库的操作命令
    &quot;db:migrate-dev&quot;: &quot;dotenv -e .env.development.local -- prisma migrate dev&quot;,
    &quot;db:migrate&quot;: &quot;dotenv -e .env.production.local -- prisma migrate deploy&quot;,
    &quot;db:push&quot;: &quot;prisma db push&quot;,
    &quot;db:studio&quot;: &quot;prisma studio&quot;,
    
    // 第二个数据库的操作命令
    &quot;db2:migrate-dev&quot;: &quot;dotenv -e .env.development.local -- prisma migrate dev --schema=./prisma/schema2.prisma&quot;,
    &quot;db2:migrate&quot;: &quot;dotenv -e .env.production.local -- prisma migrate deploy --schema=./prisma/schema2.prisma&quot;,
    &quot;db2:push&quot;: &quot;prisma db push --schema=./prisma/schema2.prisma&quot;,
    &quot;db2:studio&quot;: &quot;prisma studio --schema=./prisma/schema2.prisma&quot;
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;命令说明：&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;通过&lt;code&gt;--schema&lt;/code&gt;参数指定特定的schema文件&lt;/li&gt;
&lt;li&gt;为不同数据库的命令添加前缀区分（如&lt;code&gt;db2:&lt;/code&gt;）&lt;/li&gt;
&lt;li&gt;保持命令的一致性和可预测性&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;在执行这些命令时，确保相应的环境变量（&lt;code&gt;DATABASE_URL_1&lt;/code&gt;和&lt;code&gt;DATABASE_URL_2&lt;/code&gt;）已正确配置。&lt;/p&gt;
&lt;p&gt;在分别执行完两个数据库的迁移命令后，我们就可以在&lt;code&gt;/prisma/prisma-client/db1Client&lt;/code&gt;和&lt;code&gt;/prisma/prisma-client/db2Client&lt;/code&gt;目录下看到生成的Prisma客户端代码。此时，我们就完成了多数据库连接的配置。&lt;/p&gt;
&lt;h2&gt;四、使用示例&lt;/h2&gt;
&lt;p&gt;在应用代码中，我们可以通过导入&lt;code&gt;db&lt;/code&gt;和&lt;code&gt;db2&lt;/code&gt;来分别操作两个数据库：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import { db, db2 } from &quot;~/server/db&quot;;

// 使用第一个数据库
const user = await db.user.findMany();

// 使用第二个数据库  
const orders = await db2.order.findMany();
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;当然，在实际应用中，还是建议根据业务需求合理划分数据库职责，避免跨库的复杂事务操作，以便充分发挥这种架构的优势。&lt;/p&gt;
</content:encoded></item><item><title>在Arch Linux上使用Sunshine和Moonlight实现使用平板电脑作为副屏</title><link>https://www.rainafter.cn/posts/%E5%9C%A8arch-linux%E4%B8%8A%E4%BD%BF%E7%94%A8sunshine%E5%92%8Cmoonlight%E5%AE%9E%E7%8E%B0%E4%BD%BF%E7%94%A8%E5%B9%B3%E6%9D%BF%E7%94%B5%E8%84%91%E4%BD%9C%E4%B8%BA%E5%89%AF%E5%B1%8F/</link><guid isPermaLink="true">https://www.rainafter.cn/posts/%E5%9C%A8arch-linux%E4%B8%8A%E4%BD%BF%E7%94%A8sunshine%E5%92%8Cmoonlight%E5%AE%9E%E7%8E%B0%E4%BD%BF%E7%94%A8%E5%B9%B3%E6%9D%BF%E7%94%B5%E8%84%91%E4%BD%9C%E4%B8%BA%E5%89%AF%E5%B1%8F/</guid><description>在Arch Linux的Wayland环境下创建虚拟显示器并自建EDID文件实现与平板屏幕分辨率对应，并配合Sunshine+Moonlight的方式实现使用平板作为副屏。</description><pubDate>Mon, 09 Jun 2025 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;现在的Arch默认使用Wayland，这使得之前不少资料中的通过创建虚拟显示器的方法失效（因为大多使用X11），在经过一段时间查资料后得到了完整的在Arch Linux的Wayland环境下创建虚拟显示器并自建EDID文件实现与平板屏幕分辨率对应、并配合Sunshine+Moonlight的方式实现使用平板作为虚拟显示器的方法&lt;/p&gt;
&lt;h2&gt;一、安装Sunshine&lt;/h2&gt;
&lt;p&gt;在Arch中可以使用Extra软件源中的正式版的&lt;code&gt;sunshine&lt;/code&gt;，也可以使用AUR软件源中的预发布版&lt;code&gt;sunshine-beta-bin&lt;/code&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;sudo pacman -S sunshine
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;yay -S sunshine-beta-bin 
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;两者二选一即可，但是在安装后的实际体验来看，本人使用sunshine时出现了在Wayland下不能正常硬件编码的问题，经测试后sunshine-beta-bin正常。故最后选用了AUR软件源中的sunshine-beta-bin&lt;/p&gt;
&lt;p&gt;:::note
就本文写作时间来看，sunshine的版本号是（2025.122.141614-7），sunshine-beta-bin的版本号是（2025.608.232654-1）
:::&lt;/p&gt;
&lt;h2&gt;二、在Wayland下配置虚拟显示器&lt;/h2&gt;
&lt;p&gt;配置虚拟显示器的方式分为两类：一种是已有显卡欺骗器硬件的方式，另一种是通过创建虚拟显示器的方式。&lt;/p&gt;
&lt;h3&gt;2.1. 已有显卡欺骗器硬件的方式&lt;/h3&gt;
&lt;p&gt;这种方式需要有显卡欺骗器硬件，可以直接插入电脑的HDMI接口，系统会自动识别为一个虚拟显示器。如果正好有显卡欺骗器的话，直接在电脑上插入显卡欺骗器即可解决问题，如果没有显卡欺骗器，请参考2.2部分来创建虚拟显示器。
:::tip
即使已有显卡欺骗器硬件，也依然建议参考2.3部分通过修改EDID文件将显示器分辨率修改为平板电脑的原生分辨率，可以极大的提升使用体验。
:::&lt;/p&gt;
&lt;h3&gt;2.2. 创建虚拟显示器的方式&lt;/h3&gt;
&lt;p&gt;在Wayland环境下，类似于使用&lt;code&gt;XrandR&lt;/code&gt;工具等大多数创建虚拟显示器的方法均已经失效，经过反复尝试，最终找到了一种通过创建虚拟显示器的方式来实现的办法。
这种方式使用了&lt;a href=&quot;https://wiki.archlinuxcn.org/wiki/%E5%86%85%E6%A0%B8%E7%BA%A7%E6%98%BE%E7%A4%BA%E6%A8%A1%E5%BC%8F%E8%AE%BE%E7%BD%AE#&quot;&gt;&lt;code&gt;内核级显示模式（KMS）&lt;/code&gt;&lt;/a&gt;来创建虚拟显示器，并通过&lt;a href=&quot;https://wiki.archlinuxcn.org/wiki/%E5%86%85%E6%A0%B8%E7%BA%A7%E6%98%BE%E7%A4%BA%E6%A8%A1%E5%BC%8F%E8%AE%BE%E7%BD%AE#%E5%BC%BA%E5%88%B6%E8%AE%BE%E7%BD%AE%E6%98%BE%E7%A4%BA%E6%A8%A1%E5%BC%8F%E4%B8%8E_EDID&quot;&gt;&lt;code&gt;强制设置显示模式&lt;/code&gt;&lt;/a&gt;与&lt;code&gt;自定义EDID&lt;/code&gt;的方式创建与平板电脑分辨率相同的虚拟显示器，其中的具体步骤如下：&lt;/p&gt;
&lt;h4&gt;2.2.1. 创建虚拟显示器&lt;/h4&gt;
&lt;p&gt;使用&lt;a href=&quot;https://wiki.archlinuxcn.org/wiki/%E5%86%85%E6%A0%B8%E7%BA%A7%E6%98%BE%E7%A4%BA%E6%A8%A1%E5%BC%8F%E8%AE%BE%E7%BD%AE#%E5%BC%BA%E5%88%B6%E8%AE%BE%E7%BD%AE%E6%98%BE%E7%A4%BA%E6%A8%A1%E5%BC%8F%E4%B8%8E_EDID&quot;&gt;&lt;code&gt;强制设置显示模式&lt;/code&gt;&lt;/a&gt;来创建虚拟显示器时需要修改Grub的内核参数，具体步骤如下：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;打开GRUB配置文件&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code&gt;sudo vim /etc/default/grub
&lt;/code&gt;&lt;/pre&gt;
&lt;ol&gt;
&lt;li&gt;找到并在&lt;code&gt;GRUB_CMDLINE_LINUX_DEFAULT&lt;/code&gt;一行中(一般在文件的靠前部分)添加参数&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code&gt;# GRUB boot loader configuration

GRUB_DEFAULT=0
GRUB_TIMEOUT=5
GRUB_DISTRIBUTOR=&quot;Arch&quot;
GRUB_CMDLINE_LINUX_DEFAULT=&quot;loglevel=5 nowatchdog nvidia_drm.modeset=1&quot;

GRUB_CMDLINE_LINUX_DEFAULT=&quot;loglevel=5 nowatchdog nvidia_drm.modeset=1 video=HDMI-A-3:e&quot;
# xxx为你的未使用的HDMI接口号，由于是强制创建虚拟显示器，只要不占用已用接口，此处填任何数字均可以，示例填写为video=HDMI-A-3:e
GRUB_CMDLINE_LINUX=&quot;&quot;
GRUB_DISABLE_OS_PROBER=false

# Preload both GPT and MBR modules so that they are not missed
GRUB_PRELOAD_MODULES=&quot;part_gpt part_msdos&quot;

# Uncomment to enable booting from LUKS encrypted devices
#GRUB_ENABLE_CRYPTODISK=y

# Set to &apos;countdown&apos; or &apos;hidden&apos; to change timeout behavior,
# press ESC key to display menu.
GRUB_TIMEOUT_STYLE=menu

# Uncomment to use basic console
GRUB_TERMINAL_INPUT=console
&lt;/code&gt;&lt;/pre&gt;
&lt;ol&gt;
&lt;li&gt;保存并退出编辑器。&lt;/li&gt;
&lt;li&gt;更新Grub配置。&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code&gt;sudo grub-mkconfig -o /boot/grub/grub.cfg
&lt;/code&gt;&lt;/pre&gt;
&lt;ol&gt;
&lt;li&gt;重启系统后即可看到成功创建了一个虚拟显示器，此时我们已经可以通过iPad或其他Android Pad等平板电脑使用MoonLight串流到Arch Linux来将平板电脑作为一个副屏。
&lt;img src=&quot;https://resource.rainafter.cn/img/20250708232555345.jpg&quot; alt=&quot;通过KMS的强制设置显示模式创建的虚拟显示器&quot; /&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;blockquote&gt;
&lt;p&gt;通过KMS的强制设置显示模式创建的虚拟显示器&lt;/p&gt;
&lt;/blockquote&gt;
&lt;ol&gt;
&lt;li&gt;虚拟显示器成功创建后，默认的分辨率为电脑内置屏幕的标准分辨率，为了便于之后通过iPad或其他Android Pad等平板电脑使用MoonLight串流时能够将铺满整个平板电脑的屏幕，强烈建议通过接下来的创建并修改EDID文件的方式，将创建的虚拟显示器分辨率调整为平板电脑的原生分辨率来获得更优秀的使用体验。&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;2.3. 创建EDID文件&lt;/h3&gt;
&lt;h4&gt;2.3.1 根据已有显示器导出EDID文件&lt;/h4&gt;
&lt;p&gt;在创建虚拟显示器之前，需要先创建一个EDID文件，其中EDID（Extended Display Identification Data）是显示器的识别数据，可以通过以下命令来获取：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;首先找到系统连接到的虚拟显示器&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code&gt;# 找出您的虚拟显示器路径
find /sys/devices/ -name edid -type f
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;此处会列举出很多显示器，在这里可以根据显示器所连接到的显卡来判断哪一个是虚拟显示器。
:::tip
如果在这一步不确定哪一个是新添加的虚拟显示器的EDID文件，也可以导出任意一个显示器的EDID文件（例如笔记本电脑的内置屏幕），然后在后续的步骤中自定义EDID文件修改显示器的分辨率即可。（经测试基本上没有任何影响）
:::&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;再将虚拟显示器的EDID数据复制到一个想要保存的目录中（该目录需要记住，接下来的步骤中会使用到该目录）&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code&gt;# 复制 EDID 数据 (替换路径为上面找到的实际路径)
sudo cp /sys/devices/pci0000:00/0000:00:02.0/drm/card0/card1-HDMI-A-3/edid /xxxx想要保存到的路径xxxx/edid.bin
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;2.3.2 自定义EDID文件&lt;/h4&gt;
&lt;ol&gt;
&lt;li&gt;自定义EDID文件，将虚拟显示器分辨率修改为平板电脑的原生分辨率
在这里推荐一个好用的EDID在线编辑网站&lt;a href=&quot;https://edid.wherelse.cc/&quot;&gt;EDID解析与编辑工具(点击可查看)&lt;/a&gt;，我们在网站左侧上传刚才保存的EDID文件，点击解析后，在网站下面的水平和垂直分辨率部分修改为平板电脑的原生分辨率
&lt;img src=&quot;https://resource.rainafter.cn/img/20250617140623522.png&quot; alt=&quot;自定义EDID文件&quot; /&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;blockquote&gt;
&lt;p&gt;自定义EDID文件
编辑完成后，点击底部的下载EDID文件，并保存到一个目录中&lt;/p&gt;
&lt;/blockquote&gt;
&lt;ol&gt;
&lt;li&gt;将刚才下载的自定义EDID文件复制到系统中&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code&gt;sudo cp /xxxx下载的自定义EDID文件的路径xxxx/edid.bin /usr/lib/firmware/edid/hdmi.bin
&lt;/code&gt;&lt;/pre&gt;
&lt;ol&gt;
&lt;li&gt;修改GRUB配置文件&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code&gt;# GRUB boot loader configuration

GRUB_DEFAULT=0
GRUB_TIMEOUT=5
GRUB_DISTRIBUTOR=&quot;Arch&quot;
GRUB_CMDLINE_LINUX_DEFAULT=&quot;loglevel=5 nowatchdog nvidia_drm.modeset=1 video=HDMI-A-3:e&quot;

GRUB_CMDLINE_LINUX_DEFAULT=&quot;loglevel=5 nowatchdog nvidia_drm.modeset=1 video=HDMI-A-3:e drm.edid_firmware=HDMI-A-3:edid/hdmi.bin&quot;
GRUB_CMDLINE_LINUX=&quot;&quot;
GRUB_DISABLE_OS_PROBER=false

# Preload both GPT and MBR modules so that they are not missed
GRUB_PRELOAD_MODULES=&quot;part_gpt part_msdos&quot;

# Uncomment to enable booting from LUKS encrypted devices
#GRUB_ENABLE_CRYPTODISK=y

# Set to &apos;countdown&apos; or &apos;hidden&apos; to change timeout behavior,
# press ESC key to display menu.
GRUB_TIMEOUT_STYLE=menu

# Uncomment to use basic console
GRUB_TERMINAL_INPUT=console
&lt;/code&gt;&lt;/pre&gt;
&lt;ol&gt;
&lt;li&gt;更新Grub配置&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code&gt;sudo grub-mkconfig -o /boot/grub/grub.cfg
&lt;/code&gt;&lt;/pre&gt;
&lt;ol&gt;
&lt;li&gt;重启后，即可看到虚拟显示器的分辨率已经变为我们指定的平板的分辨率。
&lt;img src=&quot;https://resource.rainafter.cn/img/20250617135314179.png&quot; alt=&quot;成功修改分辨率后的虚拟显示器&quot; /&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;blockquote&gt;
&lt;p&gt;成功修改分辨率后的虚拟显示器&lt;/p&gt;
&lt;/blockquote&gt;
&lt;ol&gt;
&lt;li&gt;将新添加的虚拟显示器作为Sunshine的默认捕捉显示器即可。&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;三、使用MoonLight作为客户端连接到电脑&lt;/h2&gt;
&lt;h3&gt;3.1 安装Moonlight&lt;/h3&gt;
&lt;p&gt;其中iOS和iPad OS设备建议使用MoonLight砖家版（APP Store美区有上架；国区ID也可以通过TestFlight使用测试版，且测试版的版本更新、功能更多）来满足竖屏使用副屏的操作。&lt;/p&gt;
&lt;p&gt;Android设备建议使用Moonlight阿西西修改版。&lt;/p&gt;
&lt;h3&gt;3.2 保证优秀的局域网环境&lt;/h3&gt;
&lt;p&gt;为了保证Moonlight连接，建议使用5Ghz的Wifi来进行无线连接&lt;/p&gt;
</content:encoded></item><item><title>海棠花开，春风徐来</title><link>https://www.rainafter.cn/posts/%E6%B5%B7%E6%A3%A0%E8%8A%B1%E5%BC%80%E6%98%A5%E9%A3%8E%E5%BE%90%E6%9D%A5/</link><guid isPermaLink="true">https://www.rainafter.cn/posts/%E6%B5%B7%E6%A3%A0%E8%8A%B1%E5%BC%80%E6%98%A5%E9%A3%8E%E5%BE%90%E6%9D%A5/</guid><description>前段时间，有近半个月的日子，每一天真可以说的上是微风不噪、阳光甚好。常去散步的小路两旁栽满了我很是喜欢海棠花。校园里这条栽满海棠的小径，我已走过无数次，却依然不觉厌倦。</description><pubDate>Fri, 11 Apr 2025 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;前段时间，有近半个月的日子，每一天真可以说的上是微风不噪、阳光甚好。常去散步的小路两旁栽满了我很是喜欢海棠花。校园里这条栽满海棠的小径，在我往返教学楼时已走过无数次，却依然不觉厌倦。或许是因为天气格外宜人的缘故，这些日子每次漫步于熟悉的路段，总能欣赏到海棠花愈发舒展，或是在不同的阳光下愈加绚丽。我想，是和煦的春天慢慢的到来了。&lt;/p&gt;
&lt;p&gt;海棠的品类有不少，有西府海棠，亭亭玉立，花朵红粉相间，既有着明艳之姿，又不失端庄典雅；垂丝海棠则花梗细长下垂，花朵如娇羞的少女，低眉颔首；还有绚丽海棠，花梗极短，花朵紧贴枝干，花色深红，热烈而奔放 ，每一种海棠花都有着独特的韵味，为这春日增添了无尽的色彩。&lt;/p&gt;
&lt;h2&gt;一、怡红快绿与绿肥红瘦交织的西府海棠&lt;/h2&gt;
&lt;p&gt;海棠花开娇艳动人，但一般的海棠花无香味，只有西府海棠既香且艳，是海棠中的上品。其花未开时，花蕾红艳，似胭脂点点，开后则渐变粉红，有如“雪绽霞铺锦水头”。西府海棠花形较大，四至七朵成簇朵朵，树型也能轻松长得很大，多数情况下，西府海棠长至三到五米不成问题。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://resource.rainafter.cn/img/20250412165559610.jpg&quot; alt=&quot;西府海棠&quot; /&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;如“雪绽霞铺锦水头”的西府海棠&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;img src=&quot;https://resource.rainafter.cn/img/20250413123850495.jpg&quot; alt=&quot;8&quot; /&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;长势极好，加上树冠足有三层教学楼高的西府海棠&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3&gt;1.1 红楼大观园中的怡红快绿&lt;/h3&gt;
&lt;p&gt;&lt;img src=&quot;https://resource.rainafter.cn/img/20250413115532494.jpg&quot; alt=&quot;红香绿玉&quot; /&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://resource.rainafter.cn/img/20250413115559823.jpg&quot; alt=&quot;怡红快绿&quot; /&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;后来随贾家日渐衰败，象征荣国府的怡红院中的红海棠也渐渐枯萎。不过在高鹗续书的后40回中，贾政被皇帝优先了外任，属于是升迁，贾府摆宴为其庆贺，而怡红院中枯死已久的海棠树也在十一月的冬天中枯木逢春似的海棠花开。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://resource.rainafter.cn/img/20250413124110950.jpg&quot; alt=&quot;怡红快绿&quot; /&gt;&lt;/p&gt;
&lt;p&gt;:::note
整体来看的话，“怡红快绿”形容海棠和芭蕉确实很是贴切
:::&lt;/p&gt;
&lt;h3&gt;1.2 李清照笔下的绿肥红瘦&lt;/h3&gt;
&lt;p&gt;:::poem{title=&quot;《如梦令·昨夜雨疏风骤》&quot; author=&quot;李清照&quot;}
昨夜雨疏風驟，濃睡不消殘酒。試問卷簾人，卻道海棠依舊。知否，知否？應是綠肥紅瘦。
:::&lt;/p&gt;
&lt;p&gt;:::note
“绿”代替叶，“红”代替花，是两种颜色的对比；“肥”形容雨后的叶子因水份充足而茂盛肥大，“瘦”形容雨后的花朵因不堪雨打而凋谢稀少，是两种状态的对比。由这四个字生发联想，那“红瘦”正是表明春天的渐渐消逝，而“绿肥”正是象征着绿叶成荫的盛夏的即将来临。
:::&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://resource.rainafter.cn/img/20250413123725754.jpg&quot; alt=&quot;绿肥红瘦&quot; /&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;绿肥红瘦的西府海棠&lt;/p&gt;
&lt;/blockquote&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;img src=&quot;https://resource.rainafter.cn/img/20250413125139534.jpg&quot; alt=&quot;垂丝海棠&quot; /&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;低眉颔首的垂丝海棠&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;在海棠诗社社员的多首咏作之中，首首点明所咏的海棠花开是在秋季。如贾探春有&quot;斜阳寒草&quot;、&quot;苔翠盈铺&quot;的字样；薛宝钗有&quot;胭脂洗出秋阶影&quot;的句子；贾宝玉则开口就是&quot;秋容浅淡映重门&quot;；林黛玉写有&quot;秋闺怨女&quot;、&quot;倦倚西风&quot;；这还不算，就是在后来史湘云的和韵两首中，也都明确写出&quot;霜娥&quot;、&quot;秋阴&quot;、&quot;悲秋&quot;等字样，无一例外。&lt;/p&gt;
&lt;p&gt;:::poem{title=&quot;《咏白海棠》&quot; author=&quot;潇湘妃子&quot;}
半卷湘簾半掩門，碾冰爲土玉爲盆。
偷來梨蕊三分白，借得梅花一縷魂。
月窟仙人縫縞袂，秋閨怨女拭啼痕。
嬌羞默默同誰訴，倦倚西風夜已昏。
:::&lt;/p&gt;
&lt;p&gt;不妨来分析一下上方卡片中，潇湘妃子林黛玉所做的《咏白海棠》，其中的“偷来梨蕊三分白，借得梅花一缕魂。”一句对这种白海棠的花形和颜色刻画的十分贴切且到位。事实上垂丝海棠花有粉、红、白等不同颜色，大部分的海棠花，从花开到花落，颜色上会有一个由深到浅的变化。有的所谓的白海棠，也并非纯白色，而是由粉红色逐渐变为白色的。贾芸所购的白海棠当属此类。因此，结合黛玉所描写的白海棠花形与颜色，我猜测这两盆白海棠应是垂丝海棠。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://resource.rainafter.cn/img/20250412165559506.jpg&quot; alt=&quot;垂丝海棠&quot; /&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;由粉红色逐渐变为白色的的垂丝海棠&lt;/p&gt;
&lt;/blockquote&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;img src=&quot;https://resource.rainafter.cn/img/20250412165559361.jpg&quot; alt=&quot;绚丽海棠&quot; /&gt;&lt;/p&gt;
&lt;p&gt;最神奇的当属它的叶片了，仿佛被赋予了灵动的情感，会随着时间与心情的变化而改变颜色。起初，它与花朵一同崭露头角，呈现出与花朵相呼应的紫红色，深沉而神秘，与满树繁花融为一体，共同演绎着春日的浪漫。随着时光的流转，它又像是被大自然这位神奇的画师轻轻点染，渐渐地从紫红向绿色转变。那渐变的过程细腻而微妙，从叶尖开始，一抹清新的嫩绿悄然出现，而后如同涟漪般慢慢扩散，直至整个叶片都被绿色占据 ，鲜嫩欲滴，在阳光的照耀下闪烁着生命的光彩，宣告着夏日的蓬勃生机。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://resource.rainafter.cn/img/20250412165559409.jpg&quot; alt=&quot;绚丽海棠&quot; /&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://resource.rainafter.cn/img/20250412165559558.jpg&quot; alt=&quot;绚丽海棠&quot; /&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;img src=&quot;https://resource.rainafter.cn/img/20250413125430343.jpg&quot; alt=&quot;海棠花开&quot; /&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;&amp;lt;div style=&quot;display: flex; gap:15px;&quot;&amp;gt;
&amp;lt;img src=&quot;https://resource.rainafter.cn/img/20250412165546367.jpg&quot; width=&quot;50%&quot;/&amp;gt;
&amp;lt;img src=&quot;https://resource.rainafter.cn/img/20250413125430412.jpg&quot; width=&quot;50%&quot;/&amp;gt;
&amp;lt;/div&amp;gt;&lt;/p&gt;
</content:encoded></item><item><title>为你的 Fuwari 博客添加自定义渲染节点 —— 以自定义诗词卡片为例</title><link>https://www.rainafter.cn/posts/%E4%B8%BA%E4%BD%A0%E7%9A%84fuwari%E5%8D%9A%E5%AE%A2%E6%B7%BB%E5%8A%A0%E8%87%AA%E5%AE%9A%E4%B9%89%E6%B8%B2%E6%9F%93%E8%8A%82%E7%82%B9/</link><guid isPermaLink="true">https://www.rainafter.cn/posts/%E4%B8%BA%E4%BD%A0%E7%9A%84fuwari%E5%8D%9A%E5%AE%A2%E6%B7%BB%E5%8A%A0%E8%87%AA%E5%AE%9A%E4%B9%89%E6%B8%B2%E6%9F%93%E8%8A%82%E7%82%B9/</guid><description>带你一起活用Markdwon AST，为你的Fuwari博客添加一个渲染诗词卡片的自定义渲染节点。</description><pubDate>Sun, 16 Mar 2025 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;一、起因&lt;/h2&gt;
&lt;h3&gt;1.1 背景&lt;/h3&gt;
&lt;p&gt;不久前，记得那天是周五，下午休息的时候习惯性的打开&lt;a href=&quot;https://www.ruanyifeng.com/blog/2025/03/weekly-issue-341.html&quot;&gt;阮一峰老师的周刊&lt;/a&gt;，发现周刊的优秀项目中推荐了一款十分清秀的手写字体，并且还进行了开源，不由得引起我的注意。&lt;/p&gt;
&lt;p&gt;::github{repo=&quot;Chenyu-otf/chenyuluoyan_thin&quot;}&lt;/p&gt;
&lt;p&gt;因本人的习惯和爱好，不时会饶有兴趣的写一些偏向散文的随笔或者诗词，这次看到如此清秀的手写字体，不由得产生了使用这种字体为我的诗词进行展示的想法。&lt;/p&gt;
&lt;p&gt;当然，由于 Fuwari 博客使用 Markdown 渲染博客页面的原因，使用特殊字体渲染诗词的最佳实践应是自定义渲染节点&lt;/p&gt;
&lt;p&gt;:::note
事实上，上方Fuwari博客自带的 Github 仓库展示语法就是一种自定义渲染节点，本文的这词要实现的东西便是定义一种类似于这种效果的节点对诗词进行渲染
:::&lt;/p&gt;
&lt;p&gt;说干就干，经过一通查找 mdast 语法分析树的资料和编写代码，成功实现了自定义 Markdown 渲染节点的效果，首先展示一下成品&lt;/p&gt;
&lt;h3&gt;1.2 现代诗渲染效果&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;输入&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code&gt;:::poem{title=&quot;《爲什麼》&quot; author=&quot;席慕蓉&quot;}
我可以鎖住筆慮什麼，
卻鎖不住愛和憂傷。
在長長的一生裏願什麼，
款樂總是乍現就雕落，
走得最急的都是最美的時光。
:::
&lt;/code&gt;&lt;/pre&gt;
&lt;ol&gt;
&lt;li&gt;输出
:::poem{title=&quot;《爲什麼》&quot; author=&quot;席慕蓉&quot;}
我可以鎖住筆慮什麼，
卻鎖不住愛和憂傷。
在長長的一生裏願什麼，
款樂總是乍現就雕落，
走得最急的都是最美的時光。
:::&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;1.3 诗词渲染效果&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;输入&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code&gt;:::poem{title=&quot;《苏幕遮 • 咏梅》&quot; author=&quot;宇文Teacher&quot;}
梅凝枝，香氛沁。飛雪玉花，獨綻寒中韻。額點壽陽妝未褪，歲晚燈籠，焰影映春信。

百劫身，千古印。逋老孤山，鶴子梅妻問。素心不共羣芳燼，待折梅枝，寄取春風訊。
:::
&lt;/code&gt;&lt;/pre&gt;
&lt;ol&gt;
&lt;li&gt;输出
:::poem{title=&quot;《苏幕遮 • 咏梅》&quot; author=&quot;宇文Teacher&quot;}
梅凝枝，香氛沁。飛雪玉花，獨綻寒中韻。額點壽陽妝未褪，歲晚燈籠，焰影映春信。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;百劫身，千古印。逋老孤山，鶴子梅妻問。素心不共羣芳燼，待折梅枝，寄取春風訊。
:::&lt;/p&gt;
&lt;h2&gt;二、自定义渲染节点&lt;/h2&gt;
&lt;h3&gt;2.1 新建诗词卡片节点&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;首先我们在 &lt;strong&gt;/src/plugins&lt;/strong&gt; 目录下新建文件 &lt;strong&gt;rehype-component-poem-card.mjs&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;在 &lt;strong&gt;rehype-component-poem-card.mjs&lt;/strong&gt; 文件内新增&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code&gt;/// &amp;lt;reference types=&quot;mdast&quot; /&amp;gt;
import { h } from &quot;hastscript&quot;;

/**
 * 处理诗词内容节点
 * @param {import(&apos;mdast&apos;).Content} child 要处理的节点
 * @returns {import(&apos;mdast&apos;).Element[]} 处理后的元素数组
 */
function processContentNode(child) {s
  if (child.type === &quot;paragraph&quot; || (child.type === &quot;element&quot; &amp;amp;&amp;amp; child.tagName === &quot;p&quot;)) {
    const text = extractTextContent(child).trim();
    return [createPoemParagraph(splitIntoLines(text))];
  }

  if (child.type === &quot;element&quot; &amp;amp;&amp;amp; child.children?.length &amp;gt; 0) {
    // 如果是p元素，需要特殊处理
    if (child.tagName === &quot;p&quot;) {
      const text = extractTextContent(child).trim();
      return [createPoemParagraph(splitIntoLines(text))];
    }

    const processedChildren = child.children.flatMap((grandchild) =&amp;gt; {
      if (grandchild.type === &quot;text&quot;) {
        return [createPoemParagraph(splitIntoLines(grandchild.value))];
      }
      return processContentNode(grandchild);
    });
    return [h(&quot;div&quot;, { class: &quot;poem-paragraph&quot; }, processedChildren)];
  }

  if (child.type === &quot;text&quot; &amp;amp;&amp;amp; child.value) {
    return [createPoemParagraph(splitIntoLines(child.value))];
  }

  return [child];
}

/**
 * 创建一个古风诗词卡片组件
 * @param {Object} properties 组件属性
 * @param {string} [properties.title] 诗词标题
 * @param {string} [properties.author] 作者
 * @param {import(&apos;mdast&apos;).Content[]} children 诗词内容
 * @returns {import(&apos;mdast&apos;).Element} 创建的诗词卡片组件
 */
export function PoemCardComponent(properties, children) {
  if (!Array.isArray(children) || children.length === 0) {
    return h(&quot;div&quot;, { class: &quot;hidden&quot; }, &quot;无效的诗词指令。(需要包含诗词内容)&quot;);
  }

  // 创建标题和作者
  const header = h(
    &quot;div&quot;,
    { class: &quot;poem-header&quot; },
    [
      properties.title &amp;amp;&amp;amp; h(&quot;span&quot;, { class: &quot;poem-title&quot; }, properties.title),
      properties.author &amp;amp;&amp;amp; h(&quot;span&quot;, { class: &quot;poem-author&quot; }, `/ ${properties.author}`),
    ].filter(Boolean)
  );

  // 处理诗词内容
  const processedChildren = children.flatMap(processContentNode);

  // 创建诗词内容容器
  const content = h(&quot;div&quot;, { class: &quot;poem-content&quot; }, processedChildren);

  // 返回整个卡片容器
  return h(&quot;div&quot;, { class: &quot;poem-card&quot; }, [header, content]);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;ol&gt;
&lt;li&gt;在 &lt;strong&gt;/src/styles&lt;/strong&gt; 目录下新增诗词卡片的样式文件 &lt;strong&gt;poem-card.css&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;:::note
注意：在 &lt;strong&gt;/src/styles&lt;/strong&gt; 目录下的样式文件会自动引入Astro全局
:::&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;@font-face {
  /* 上文提到的开源字体： 辰宇落雁體，可自行前往Github仓库下载，并重命名放在项目根目录下的 /public/fonts 目录内*/
  font-family: &quot;ChenYuluoyan&quot;;
  src: url(&quot;/fonts/ChenYuluoyan.ttf&quot;) format(&quot;ttf&quot;);
  font-display: swap;
}

.poem-card {
  @apply relative my-6 rounded-xl p-10;
  /* 请在项目根目录下的 /public/poem-card 目录下新增两个图片，用作日间模式和黑暗模式状态下诗词卡片的背景 */
  @apply bg-[url(&apos;/poem-card/light.jpg&apos;)] bg-cover bg-top bg-no-repeat;
  @apply dark:bg-[url(&apos;/poem-card/dark.jpg&apos;)] dark:bg-center dark:before:bg-black/30;
  @apply before:absolute before:inset-0 before:rounded-xl;
  @apply before:backdrop-blur-[12px];
  @apply isolate;
  /* 添加背景缩放和过渡效果 */
  @apply bg-[length:105%_auto] hover:bg-[length:115%_auto];
  @apply transition-[background-size] duration-500;
  /* 添加模糊效果的过渡 */
  @apply before:transition-[backdrop-filter] before:duration-500;
  @apply hover:before:backdrop-blur-[16px];
  /* 卡片阴影 */
  box-shadow: 1.1px 1.1px 1.4px rgba(0, 0, 0, 0.008), 2.7px 2.7px 3.3px rgba(0, 0, 0, 0.012),
    5px 5px 6.3px rgba(0, 0, 0, 0.015), 8.9px 8.9px 11.2px rgba(0, 0, 0, 0.018),
    16.7px 16.7px 20.9px rgba(0, 0, 0, 0.022), 40px 40px 50px rgba(0, 0, 0, 0.03);
}

.poem-header,
.poem-content {
  @apply relative z-10;
}

.poem-header {
  @apply font-serif;
  font-family: serif;
}

.poem-title {
  @apply mr-3 text-2xl font-bold;
  @apply text-gray-800 dark:text-gray-100;
}

.poem-author {
  @apply text-lg;
  @apply text-gray-600 dark:text-gray-300;
}

/* 诗词内容基础样式 */
.poem-content {
  @apply flex w-full flex-col;
  font-family: &quot;ChenYuluoyan&quot;, serif;
  @apply text-4xl leading-relaxed;
  @apply text-gray-800 dark:text-gray-100;
  white-space: pre-wrap;
}

.poem-paragraph {
  @apply mb-2 last:mb-0; /* 段落间距 */
  white-space: pre-wrap;
}

.poem-line {
  @apply my-2 block w-fit max-w-full tracking-wider; /* 行间距保持不变 */
  white-space: pre-wrap;
}

/* 移动端适配 */
@media (max-width: 768px) {
  .poem-card {
    @apply p-6;
  }

  .poem-content {
    @apply text-3xl;
  }

  .poem-title {
    @apply text-xl;
  }

  .poem-author {
    @apply text-base;
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;ol&gt;
&lt;li&gt;上文提到的开源字体： 辰宇落雁體，可自行前往Github仓库下载，并重命名放在项目根目录下的 /fonts 目录内&lt;/li&gt;
&lt;li&gt;请在项目根目录下的 /public/poem-card 目录下新增两个图片，用作日间模式和黑暗模式状态下诗词卡片的背景图片&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;此时，我们已经完成诗词卡片的创建&lt;/p&gt;
&lt;h3&gt;2.2 在Astro中导入本诗词卡片，用于自定义节点的渲染&lt;/h3&gt;
&lt;p&gt;打开 &lt;strong&gt;/astro.config.mjs&lt;/strong&gt; 文件：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;新增诗词卡片的导入，在 &lt;strong&gt;/astro.config.mjs&lt;/strong&gt; 文件的上方区域（含有大量import语句的区域），新增如下代码：&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code&gt;import { poemChars } from &apos;./src/plugins/rehype-component-poem-card.mjs&apos;
&lt;/code&gt;&lt;/pre&gt;
&lt;ol&gt;
&lt;li&gt;在 &lt;strong&gt;/astro.config.mjs&lt;/strong&gt; 文件中查找 rehypePlugins，在其中的 rehypeComponents 后的对象中插入如下代码：&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code&gt;poem: PoemCardComponent,
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;示例&lt;/h4&gt;
&lt;ol&gt;
&lt;li&gt;原始：&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code&gt;[
    rehypeComponents,
    {
        components: {
        github: GithubCardComponent,
        note: (x, y) =&amp;gt; AdmonitionComponent(x, y, &quot;note&quot;),
        tip: (x, y) =&amp;gt; AdmonitionComponent(x, y, &quot;tip&quot;),
        important: (x, y) =&amp;gt; AdmonitionComponent(x, y, &quot;important&quot;),
        caution: (x, y) =&amp;gt; AdmonitionComponent(x, y, &quot;caution&quot;),
        warning: (x, y) =&amp;gt; AdmonitionComponent(x, y, &quot;warning&quot;),
        },
    },
],
&lt;/code&gt;&lt;/pre&gt;
&lt;ol&gt;
&lt;li&gt;修改后：&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code&gt;[
    rehypeComponents,
    {
        components: {
        github: GithubCardComponent,
            
        poem: PoemCardComponent,
        note: (x, y) =&amp;gt; AdmonitionComponent(x, y, &quot;note&quot;),
        tip: (x, y) =&amp;gt; AdmonitionComponent(x, y, &quot;tip&quot;),
        important: (x, y) =&amp;gt; AdmonitionComponent(x, y, &quot;important&quot;),
        caution: (x, y) =&amp;gt; AdmonitionComponent(x, y, &quot;caution&quot;),
        warning: (x, y) =&amp;gt; AdmonitionComponent(x, y, &quot;warning&quot;),
        },
    },
],
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;2.3 大功告成（暂时的）&lt;/h3&gt;
&lt;p&gt;此时，我们已经初步完成了自定义渲染节点——诗词卡片的创建以及导入，使用下方示例的特定语法即可成功渲染出诗词卡片。&lt;/p&gt;
&lt;h4&gt;示例&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;:::poem{title=&quot;标题&quot; author=&quot;作者&quot;}
这里填写正文
注意：
1. 正常输入文本敲击回车后即可正常渲染出单行
2. 若需要进行分段，可以在段与段中间保留空行，会自动渲染出段落
:::
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;此时自定义渲染节点——诗词卡片已经完全可用，若非对引入字体过大，减慢了页面加载速度而介意，则不必要继续进行下方的优化操作。&lt;/p&gt;
&lt;h2&gt;三、优化操作（非必要）&lt;/h2&gt;
&lt;p&gt;这里我在博客站点引入了&lt;a href=&quot;https://github.com/Chenyu-otf/chenyuluoyan_thin&quot;&gt;辰宇落雁體&lt;/a&gt;，大小有9M，本站点使用的是一台带宽仅有8M的服务器，若不对字体进行处理，则在该字体经网络传输完毕前不会正常显示，而是会使用系统默认字体替代。&lt;/p&gt;
&lt;p&gt;作为一名前端工程师，自然对这种因为网络加载而使得页面元素发生跳动的情况不能容忍。&lt;/p&gt;
&lt;h3&gt;3.1 尝试使用woff2压缩ttf字体&lt;/h3&gt;
&lt;p&gt;常见的思路是将ttf字体转换为woff2字体，但是该手写字体在经过压缩后仍然有4M的大小，依然不能满足页面点击后即可快速加载完成的需求。
&lt;img src=&quot;https://image.rainafter.cn/i/2025/03/16/67d6f0582dee4.png&quot; alt=&quot;将ttf字体转换为woff2字体&quot; /&gt;&lt;/p&gt;
&lt;h3&gt;3.2 使用工具将字体进行子集化处理&lt;/h3&gt;
&lt;p&gt;这里我们使用百度出品的 &lt;a href=&quot;https://www.npmjs.com/package/fontmin&quot;&gt;&lt;strong&gt;fontmin&lt;/strong&gt;&lt;/a&gt; 包，在项目构建时（开发环境构建 和 生产环境构建时）对诗词卡片渲染节点中的所使用到的文字进行子集化处理，这样可以使得引入的字体中仅包含所有的 &lt;strong&gt;.md文件&lt;/strong&gt; 中诗词卡片所用到的字，可以极大的压缩字体的体积。&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;首先在项目根目录打开终端，安装npm包&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code&gt;pnpm i fontmin // 使用 npm、yarn、pnpm均可
&lt;/code&gt;&lt;/pre&gt;
&lt;ol&gt;
&lt;li&gt;在 &lt;strong&gt;/src/plugins&lt;/strong&gt; 目录下新建文件 &lt;strong&gt;vite-plugin-font-subset.mjs&lt;/strong&gt; 作为Astro的Vite收集诗词所用字符的插件&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code&gt;import path from &apos;path&apos;
import { fileURLToPath } from &apos;url&apos;
import Fontmin from &apos;fontmin&apos;
import fs from &apos;fs/promises&apos;

/**
 * 创建字体子集化插件
 * @param {Set&amp;lt;string&amp;gt;} charsSet - 需要保留的字符集
 * @param {Object} options - 插件配置选项
 * @param {string} options.srcFont - 源字体文件路径
 * @param {string} options.destDir - 输出目录路径
 * @returns {import(&apos;vite&apos;).Plugin}
 */
export function createFontSubsetPlugin(charsSet, options = {}) {
  return {
    name: &apos;vite-plugin-font-subset&apos;,
    async buildStart() {
      // 添加一些基本标点符号和常用字符
      const basicChars = &apos;，。！？；：&quot;&quot;\&apos;\&apos;（）【】《》、…—&apos;
      basicChars.split(&apos;&apos;).forEach(char =&amp;gt; charsSet.add(char))

      const __dirname = path.dirname(fileURLToPath(import.meta.url))
      const srcFontPath =
        options.srcFont ||
        path.join(__dirname, &apos;../../public/fonts/ChenYuluoyan.ttf&apos;)
      const destFontPath =
        options.destDir || path.join(__dirname, &apos;../../dist/fonts&apos;)

      // 确保输出目录存在
      await fs.mkdir(destFontPath, { recursive: true })

      // 获取所有收集到的字符
      const chars = Array.from(charsSet).join(&apos;&apos;)
      console.log(&apos;收集到的字符数量:&apos;, chars.length)
      console.log(&apos;字符列表:&apos;, chars)

      // 创建 Fontmin 实例
      const fontmin = new Fontmin()
        .src(srcFontPath)
        .dest(destFontPath)
        .use(
          Fontmin.glyph({
            text: chars,
            hinting: false,
          }),
        )
        // 添加 ttf2woff2 转换
        .use(Fontmin.ttf2woff2())

      // 执行字体子集化
      await new Promise((resolve, reject) =&amp;gt; {
        fontmin.run((err, files) =&amp;gt; {
          if (err) {
            console.error(&apos;字体子集化失败:&apos;, err)
            reject(err)
            return
          }
          console.log(&apos;字体子集化完成&apos;)
          resolve(files)
        })
      })

      // 删除生成的 ttf 文件，只保留 woff2
      const ttfFile = path.join(destFontPath, &apos;ChenYuluoyan.ttf&apos;)
      try {
        await fs.unlink(ttfFile)
      } catch (err) {
        // 忽略文件不存在的错误
        if (err.code !== &apos;ENOENT&apos;) {
          console.error(&apos;删除 TTF 文件失败:&apos;, err)
        }
      }
    },
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;ol&gt;
&lt;li&gt;在 &lt;strong&gt;/src/utils&lt;/strong&gt; 目录下新增文件 &lt;strong&gt;char-collector.mjs&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code&gt;/**
 * 字符收集器类
 */
export class CharCollector {
  constructor() {
    this.chars = new Set()
  }

  /**
   * 收集文本中的所有字符
   * @param {string} text - 要收集字符的文本
   */
  collectFromText(text) {
    Array.from(text).forEach(char =&amp;gt; this.chars.add(char))
  }

  /**
   * 收集节点中的所有字符
   * @param {any} node - 要处理的节点
   */
  collectFromNode(node) {
    if (typeof node === &apos;string&apos;) {
      this.collectFromText(node)
      return
    }

    // 处理文本节点
    if (node.type === &apos;text&apos; &amp;amp;&amp;amp; node.value) {
      this.collectFromText(node.value)
      return
    }

    // 处理子节点
    if (node.children) {
      node.children.forEach(child =&amp;gt; this.collectFromNode(child))
    }
  }

  /**
   * 获取收集到的字符集合
   * @returns {Set&amp;lt;string&amp;gt;}
   */
  getChars() {
    return this.chars
  }
}

// 导出一个全局实例用于收集诗词字符
export const poemCharCollector = new CharCollector()
&lt;/code&gt;&lt;/pre&gt;
&lt;ol&gt;
&lt;li&gt;修改 &lt;strong&gt;/src/plugins&lt;/strong&gt; 目录下的文件 &lt;strong&gt;rehype-component-poem-card.mjs&lt;/strong&gt;
新增收集诗词所用字符的代码&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code&gt;/// &amp;lt;reference types=&quot;mdast&quot; /&amp;gt;
import { h } from &apos;hastscript&apos;
import { poemCharCollector } from &apos;../utils/char-collector.mjs&apos;

// 导出字符集合以保持兼容性
export const poemChars = poemCharCollector.getChars()

/**
 * 将文本内容按行分割并过滤空行
 * @param {string} text 要处理的文本
 * @returns {string[]} 处理后的行数组
 */
function splitIntoLines(text) {
  return text.split(&apos;\n&apos;)
    .map(line =&amp;gt; line.trim())
    .filter(Boolean)
}

/**
 * 递归提取节点中的所有文本内容
 * @param {import(&apos;mdast&apos;).Content} node 要处理的节点
 * @returns {string} 提取的文本内容
 */
function extractTextContent(node) {
  if (!node) return &apos;&apos;
  if (node.type === &apos;text&apos; || node.value) return node.value || &apos;&apos;
  if (node.children?.length &amp;gt; 0) {
    return node.children.map(extractTextContent).join(&apos;&apos;)
  }
  return &apos;&apos;
}

/**
 * 创建诗行容器
 * @param {string[]} lines 诗句数组
 * @returns {import(&apos;mdast&apos;).Element} 诗行容器元素
 */
function createPoemParagraph(lines) {
  return h(
    &apos;div&apos;,
    { class: &apos;poem-paragraph&apos; },
    lines.map(line =&amp;gt; h(&apos;div&apos;, { class: &apos;poem-line&apos; }, line))
  )
}

/**
 * 处理诗词内容节点
 * @param {import(&apos;mdast&apos;).Content} child 要处理的节点
 * @returns {import(&apos;mdast&apos;).Element[]} 处理后的元素数组
 */
function processContentNode(child) {
  // 收集字符
  poemCharCollector.collectFromNode(child)

  if (child.type === &apos;paragraph&apos; || (child.type === &apos;element&apos; &amp;amp;&amp;amp; child.tagName === &apos;p&apos;)) {
    const text = extractTextContent(child).trim()
    return [createPoemParagraph(splitIntoLines(text))]
  }

  if (child.type === &apos;element&apos; &amp;amp;&amp;amp; child.children?.length &amp;gt; 0) {
    // 如果是p元素，需要特殊处理
    if (child.tagName === &apos;p&apos;) {
      const text = extractTextContent(child).trim()
      return [createPoemParagraph(splitIntoLines(text))]
    }
    
    const processedChildren = child.children.flatMap(grandchild =&amp;gt; {
      if (grandchild.type === &apos;text&apos;) {
        return [createPoemParagraph(splitIntoLines(grandchild.value))]
      }
      return processContentNode(grandchild)
    })
    return [h(&apos;div&apos;, { class: &apos;poem-paragraph&apos; }, processedChildren)]
  }

  if (child.type === &apos;text&apos; &amp;amp;&amp;amp; child.value) {
    return [createPoemParagraph(splitIntoLines(child.value))]
  }

  return [child]
}

/**
 * 创建一个古风诗词卡片组件
 * @param {Object} properties 组件属性
 * @param {string} [properties.title] 诗词标题
 * @param {string} [properties.author] 作者
 * @param {import(&apos;mdast&apos;).Content[]} children 诗词内容
 * @returns {import(&apos;mdast&apos;).Element} 创建的诗词卡片组件
 */
export function PoemCardComponent(properties, children) {
  if (!Array.isArray(children) || children.length === 0) {
    return h(&apos;div&apos;, { class: &apos;hidden&apos; }, &apos;无效的诗词指令。(需要包含诗词内容)&apos;)
  }

  // 收集标题和作者的字符
  if (properties.title) poemCharCollector.collectFromText(properties.title)
  if (properties.author) poemCharCollector.collectFromText(properties.author)

  // 处理诗词内容
  const processedChildren = children.flatMap(processContentNode)

  // 创建标题和作者
  const header = h(&apos;div&apos;, { class: &apos;poem-header&apos; }, [
    properties.title &amp;amp;&amp;amp; h(&apos;span&apos;, { class: &apos;poem-title&apos; }, properties.title),
    properties.author &amp;amp;&amp;amp; h(&apos;span&apos;, { class: &apos;poem-author&apos; }, `/ ${properties.author}`),
  ].filter(Boolean))

  // 创建诗词内容容器
  const content = h(&apos;div&apos;, { class: &apos;poem-content&apos; }, processedChildren)

  // 返回整个卡片容器
  return h(&apos;div&apos;, { class: &apos;poem-card&apos; }, [header, content])
}
&lt;/code&gt;&lt;/pre&gt;
&lt;ol&gt;
&lt;li&gt;修改 &lt;strong&gt;/astro.config.mjs&lt;/strong&gt; Astro文件
新增诗词字体子集化插件的导入，在 &lt;strong&gt;/astro.config.mjs&lt;/strong&gt; 文件的上方区域（含有大量import语句的区域），新增如下代码：&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code&gt;import { createFontSubsetPlugin } from &apos;./src/plugins/vite-plugin-font-subset.mjs&apos;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;并修改 &lt;strong&gt;vite&lt;/strong&gt; 配置，新增如下代码：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;vite: {
    plugins: [
      createFontSubsetPlugin(poemChars, {
        srcFont: &apos;public/fonts/ChenYuluoyan.ttf&apos;,
        destDir: &apos;dist/fonts&apos;
      })
    ],
    build: {
      rollupOptions: {
        onwarn(warning, warn) {
          // temporarily suppress this warning
          if (
            warning.message.includes(&quot;is dynamically imported by&quot;) &amp;amp;&amp;amp;
            warning.message.includes(&quot;but also statically imported by&quot;)
          ) {
            return;
          }
          warn(warning);
        },
      },
    },
  },
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;最后，重新构建项目，即可发现 &lt;strong&gt;dist/fonts&lt;/strong&gt; 目录下新增了 &lt;strong&gt;ChenYuluoyan.woff2&lt;/strong&gt; 文件，且页面中诗词卡片渲染时，字体已经变为 &lt;strong&gt;ChenYuluoyan.woff2&lt;/strong&gt; 字体，此时由于字体仅包含了诗词卡片所用到的字符，因此字体体积非常小。
&lt;img src=&quot;https://image.rainafter.cn/i/2025/03/17/67d837e9cf619.png&quot; alt=&quot;网络加载&quot; /&gt;&lt;/p&gt;
&lt;h2&gt;四、总结&lt;/h2&gt;
&lt;p&gt;通过上述操作，我们已经成功将诗词卡片渲染节点中的字体进行了子集化处理，这样在页面加载时，诗词卡片渲染节点中的字体将会优先使用 &lt;strong&gt;ChenYuluoyan.woff2&lt;/strong&gt; 字体，从而使得页面加载速度更快。&lt;/p&gt;
&lt;p&gt;事实上，在完成了第二步后，就已经可以正常使用诗词卡片组件了，第三步的优化操作只是为了在页面加载时，诗词卡片渲染节点中的字体能够更快地加载完成，从而使得页面加载速度更快。&lt;/p&gt;
&lt;h2&gt;五、未解决的问题&lt;/h2&gt;
&lt;p&gt;在进行字体子集化处理时，我最初使用的是 &lt;a href=&quot;https://github.com/aui/font-spider&quot;&gt;&lt;strong&gt;font-spider（字蛛）&lt;/strong&gt;&lt;/a&gt; 包，我遇到了一个非常棘手的问题，那就是由于font-spider需要将收集到的字体放在一个临时的 &lt;strong&gt;.html&lt;/strong&gt; 文件中，再进行子集化处理。但是Astro在构建时会自动将 &lt;strong&gt;public&lt;/strong&gt; 目录下的文件复制到 &lt;strong&gt;dist&lt;/strong&gt; 目录下，导致找不到创建的临时 &lt;strong&gt;.html&lt;/strong&gt; 文件，从而导致字体子集化处理失败。&lt;/p&gt;
&lt;p&gt;因此，我放弃了使用font-spider，转而使用 &lt;a href=&quot;https://www.npmjs.com/package/fontmin&quot;&gt;&lt;strong&gt;fontmin&lt;/strong&gt;&lt;/a&gt; 包，这样我就可以将收集到的字体放在 &lt;strong&gt;public&lt;/strong&gt; 目录下，从而避免了上述问题。&lt;/p&gt;
&lt;p&gt;当然，此处使用font-spider时也可能是我使用不当，如果大家有更好的方法，欢迎在评论区留言。&lt;/p&gt;
</content:encoded></item><item><title>早春红梅，点燃梦想的火焰</title><link>https://www.rainafter.cn/posts/%E6%97%A9%E6%98%A5%E7%BA%A2%E6%A2%85%E7%82%B9%E7%87%83%E6%A2%A6%E6%83%B3%E7%9A%84%E7%81%AB%E7%84%B0/</link><guid isPermaLink="true">https://www.rainafter.cn/posts/%E6%97%A9%E6%98%A5%E7%BA%A2%E6%A2%85%E7%82%B9%E7%87%83%E6%A2%A6%E6%83%B3%E7%9A%84%E7%81%AB%E7%84%B0/</guid><description>从未如此的痴迷于红梅，我在这里不由怔住了，校园东北侧的红梅悄然苏醒，在枝头上点染嫣红。人们都说早春红梅额外浓艳，果真如此。</description><pubDate>Sun, 09 Mar 2025 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;《早春红梅，点燃梦想的火焰》&lt;/h2&gt;
&lt;p&gt;从未如此的痴迷于红梅，我在这里不由怔住了，校园东北侧的红梅悄然苏醒，在枝头上点染嫣红。人们都说早春红梅额外浓艳，果真如此。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://image.rainafter.cn/i/2025/03/11/67cfb75ced072.jpeg&quot; alt=&quot;红梅1&quot; /&gt;&lt;/p&gt;
&lt;p&gt;红梅色彩的炽热，如同燃烧的灯笼，在山野间熠熠生辉；如同繁星点缀寒冷的夜空；如同战旗在荒原上迎风飘扬，宣告着春天的来临。&lt;/p&gt;
&lt;p&gt;曾经读过一篇杂志文章，提到不同的梅花藏着七十二般变化似的风情。宫粉梅最是热闹，重瓣堆叠似绯云坠枝；朱砂梅像工笔勾勒的红珊瑚，暗香在冷冽空气里格外清冽；绿萼梅则如初学丹青的少女，怯生生擎着青白绢花。而眼前这盛开的这片红梅，火焰般盛开的花朵如同在困境中砥砺前行的人们。生活中的挫折如同暗流，时而冲击我们的信念，时而考验我们的勇气，时而磨损我们的意志。我不由得心想，倘若能心怀红梅般的坚韧，在风雨中坚守、在泥泞中奋进、在黑暗中守望，或许也就能在困境中绽放生命的花朵吧！&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://image.rainafter.cn/i/2025/03/11/67cfb78c920a2.jpeg&quot; alt=&quot;红梅2&quot; /&gt;&lt;/p&gt;
&lt;p&gt;梅香是清冷的，需得屏息凝神才能够捕捉，梅影摇曳，我轻轻地闭上了眼。心中畏惧失败的阴翳经着微风飘荡，和梅香一起升向了天空，努力向未来的新念头尚在云端酝酿，而满树的红梅，已经点燃了整个春天与我的梦想。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://image.rainafter.cn/i/2025/03/11/67cfb7a55fe45.jpeg&quot; alt=&quot;红梅3&quot; /&gt;&lt;/p&gt;
&lt;h2&gt;《苏幕遮 • 咏梅》&lt;/h2&gt;
&lt;p&gt;写至结尾时，意犹未尽，遂作此词。&lt;/p&gt;
&lt;p&gt;:::poem{title=&quot;《苏幕遮 • 咏梅》&quot; author=&quot;宇文Teacher&quot;}
梅凝枝，香氛沁。飛雪玉花，獨綻寒中韻。額點壽陽妝未褪，歲晚燈籠，焰影映春信。&lt;/p&gt;
&lt;p&gt;百劫身，千古印。逋老孤山，鶴子梅妻問。素心不共羣芳燼，待折梅枝，寄取春風訊。
:::&lt;/p&gt;
</content:encoded></item><item><title>Arch降级AUR软件源中的软件</title><link>https://www.rainafter.cn/posts/arch%E9%99%8D%E7%BA%A7aur%E8%BD%AF%E4%BB%B6%E6%BA%90%E4%B8%AD%E7%9A%84%E8%BD%AF%E4%BB%B6/</link><guid isPermaLink="true">https://www.rainafter.cn/posts/arch%E9%99%8D%E7%BA%A7aur%E8%BD%AF%E4%BB%B6%E6%BA%90%E4%B8%AD%E7%9A%84%E8%BD%AF%E4%BB%B6/</guid><description>最近Typora突然不能用了，提示我需要激活，后来看了Yporaject的说明才发现原来不支持1.10版本的Typora，遂准备开始降级。</description><pubDate>Fri, 07 Mar 2025 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;一、起因&lt;/h2&gt;
&lt;p&gt;本来用Arch Linux的系统一直挺好，Typora也一直用的好好的，当然，还是靠的是&lt;a href=&quot;https://github.com/hazukieq/Yporaject&quot;&gt;Yporaject&lt;/a&gt;来激活，但是最近Typora突然不能用了，提示我需要激活，后来看了Yporaject的说明才发现原来不支持1.10版本的Typora，遂准备开始降级。
::github{repo=&quot;hazukieq/Yporaject&quot;}&lt;/p&gt;
&lt;h2&gt;二、过程&lt;/h2&gt;
&lt;h3&gt;2.1 遇到的坑&lt;/h3&gt;
&lt;p&gt;我的Typora是通过AUR源安装的1.10.8-1版本，所以需要降级到1.9.x版本。&lt;/p&gt;
&lt;p&gt;查了下关于Arch Linux降级软件包的方法，发现了一个叫Downgrade的工具，遂准备尝试一下。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://resource.rainafter.cn/img/20250308201648474.jpg&quot; alt=&quot;Downgrade&quot; /&gt;&lt;/p&gt;
&lt;p&gt;结果很不巧，AUR源只有当前最新版的Typeora安装包，而我的本地也没有老版本的缓存，只好想办法通过AUR源的Typora仓库直接手动构建一个低版本的安装包&lt;/p&gt;
&lt;h3&gt;2.2 解决方案&lt;/h3&gt;
&lt;p&gt;在 Arch Linux 中降级 AUR（Arch User Repository）软件包的步骤如下：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;查找旧版本的包&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;首先，找到想要降级到的旧版本的 AUR 包。可以通过 AUR 网站或 Git 仓库找到旧版本的 PKGBUILD 文件。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;下载旧版本的 PKGBUILD 文件&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;可以通过访问 AUR 网站，找到您要降级的软件包，然后在“Package Actions”部分找到“View Changes”或“Git Clone URL”。&lt;/li&gt;
&lt;li&gt;通过以下命令克隆 AUR 仓库：&lt;pre&gt;&lt;code&gt;git clone https://aur.archlinux.org/your-package-name.git
cd your-package-name
git log
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;通过 &lt;code&gt;git log&lt;/code&gt; 找到想要的旧版本的提交哈希，然后检出该版本：&lt;pre&gt;&lt;code&gt;git checkout &amp;lt;commit-hash&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;构建和安装旧版本的软件包&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;使用 &lt;code&gt;makepkg&lt;/code&gt; 命令构建包：&lt;pre&gt;&lt;code&gt;makepkg -si
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;这将根据旧版本的 PKGBUILD 文件构建并安装软件包。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;锁定软件包版本&lt;/strong&gt;（可选）：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;为了防止软件包在下次系统更新时被升级，可以在 &lt;code&gt;/etc/pacman.conf&lt;/code&gt; 文件中添加以下行来忽略该包的更新：&lt;pre&gt;&lt;code&gt;IgnorePkg=your-package-name
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;三、最后&lt;/h2&gt;
&lt;p&gt;最后也是终于看到了激活成功的动画。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://resource.rainafter.cn/img/20250308202153890.png&quot; alt=&quot;成功&quot; /&gt;&lt;/p&gt;
</content:encoded></item><item><title>赠友人词五首</title><link>https://www.rainafter.cn/posts/%E8%B5%A0%E5%8F%8B%E4%BA%BA%E8%AF%8D%E4%BA%94%E9%A6%96/</link><guid isPermaLink="true">https://www.rainafter.cn/posts/%E8%B5%A0%E5%8F%8B%E4%BA%BA%E8%AF%8D%E4%BA%94%E9%A6%96/</guid><description>人生旅途，总有聚散离合之时。临别之际，心中感慨万千，回忆过往的点滴欢笑与深厚情谊，愿以此五首小词，表达对友人的不舍与祝愿。每一字句皆寄托着对友人未来的美好期盼，愿彼此在各自的征程中勇敢前行，终有再聚之日。</description><pubDate>Mon, 03 Mar 2025 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;:::note
人生旅途，总有聚散离合之时。临别之际，心中感慨万千，回忆过往的点滴欢笑与深厚情谊，愿以此五首小词，表达对友人的不舍与祝愿。每一字句皆寄托着对友人未来的美好期盼，愿彼此在各自的征程中勇敢前行，终有再聚之日。
:::&lt;/p&gt;
&lt;h2&gt;《采桑子 · 赠友人其一》&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;宇文 Teacher&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;辕行渐远人将去，依依惜别，难舍深谊，回首笑谈忆旧盟。&lt;/p&gt;
&lt;p&gt;腊梅傲雪迎新岁，愿友前程，锦绣光明，来日春风再叙情。&lt;/p&gt;
&lt;h2&gt;《长相思 · 赠友人其二》&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;宇文 Teacher&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;松如君，竹如君，阵阵风声送别离，寒冬友长青。&lt;/p&gt;
&lt;p&gt;江南路，杭州路，万水千山远路迷，重聚终有期。&lt;/p&gt;
&lt;h2&gt;《醉花阴 · 赠友人其三》&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;宇文 Teacher&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;冬日暖阳谁与度，莫问前程路，执手话离别，寒风轻拂，愿君志长驻。&lt;/p&gt;
&lt;p&gt;天涯海角无停步，回首梦无数，壮志自高远，长袖迎春，前程莫辜负。&lt;/p&gt;
&lt;h2&gt;《阮郎归 · 赠友人其四》&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;宇文 Teacher&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;寒风送别暮云沉，孤影映雪痕，往昔共语暖如春，泪洒千里尘。&lt;/p&gt;
&lt;p&gt;风渐冷，松柏青，年年冬复晴，愿君志远勇前行，再启新征程。&lt;/p&gt;
&lt;h2&gt;《点绛唇 · 赠友人其五》&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;宇文 Teacher&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;寒日西斜，离情恰似风萧绕。执杯言笑，前路多祈好 。&lt;/p&gt;
&lt;p&gt;前路漫漫，莫惧风霜泪。回首处，梦如星耀 ，处处皆春早 。&lt;/p&gt;
</content:encoded></item><item><title>C++差分算法的应用</title><link>https://www.rainafter.cn/posts/c%E5%B7%AE%E5%88%86%E7%AE%97%E6%B3%95%E7%9A%84%E5%BA%94%E7%94%A8/</link><guid isPermaLink="true">https://www.rainafter.cn/posts/c%E5%B7%AE%E5%88%86%E7%AE%97%E6%B3%95%E7%9A%84%E5%BA%94%E7%94%A8/</guid><description>在处理将一个数组中某个区间内的所有值均加上或减去一个常数时，为了进一步的简化程序计算量，我们可以考虑差分算法的应用。</description><pubDate>Thu, 20 Jan 2022 00:00:00 GMT</pubDate><content:encoded>&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;blockquote&gt;
&lt;p&gt;若给定区间[l , r]，并将数组a中的[ l, r]区间中的每一个数都加上c&lt;/p&gt;
&lt;p&gt;即 a[l]+c , a[l + 1]+c , a[l + 2]+c ……a[r]+c&lt;/p&gt;
&lt;p&gt;传统模拟暴力做法是使用for循环，从l循环到r，时间复杂度O(n)，如果我们需要对原数组执行m次这样的操作，时间复杂度就会变成O(n*m)。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;通过差分算法，我们可以将O(m*n)的时间复杂度简化至O(n)，显著简化计算。&lt;/p&gt;
&lt;h2&gt;二、差分数组的定义&lt;/h2&gt;
&lt;p&gt;我们可以将差分看作为：数组中每个元素与其前一个元素的差。&lt;/p&gt;
&lt;p&gt;例：&lt;/p&gt;
&lt;p&gt;首先给定一个原数组a：a[1], a[2], a[3]……a[n]&lt;/p&gt;
&lt;p&gt;然后我们构造一个数组b ： b[1] ,b[2] , b[3]……b[i]&lt;/p&gt;
&lt;p&gt;使得 a[i] = b[1]+b[2 ]+ b[3] +……+ b[i]&lt;/p&gt;
&lt;p&gt;我们把 数组b 叫做 数组a 的差分数组。&lt;/p&gt;
&lt;p&gt;换句话说，每一个a[i]都是数组b中从首项开始到第i项的一段区间和。&lt;/p&gt;
&lt;h2&gt;三、差分数组的生成&lt;/h2&gt;
&lt;p&gt;由差分数组的定义易得：&lt;/p&gt;
&lt;p&gt;a[0 ]= 0&lt;/p&gt;
&lt;p&gt;b[1] = a[1] - a[0]&lt;/p&gt;
&lt;p&gt;b[2] = a[2] - a[1]&lt;/p&gt;
&lt;p&gt;b[3] =a [3] - a[2]&lt;/p&gt;
&lt;p&gt;……&lt;/p&gt;
&lt;p&gt;b[n] = a[n] - a[n-1]&lt;/p&gt;
&lt;h2&gt;四、差分数组的应用&lt;/h2&gt;
&lt;p&gt;始终要记得，数组a是数组b的前缀和数组，比如对数组b的b[i]的修改，会影响到数组a中从a[i]及往后的每一个数。&lt;/p&gt;
&lt;p&gt;首先让差分数组b中的 b[l]+c ，&lt;/p&gt;
&lt;p&gt;使得，数组a变成 a[l]+c ,a[l + 1]+c……a[n]+c&lt;/p&gt;
&lt;p&gt;然后我们新引入一个b[r + 1] - c，&lt;/p&gt;
&lt;p&gt;使得，数组a变成 a[r + 1] - c,a[r + 2] - c……a[n]-c&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://resource.rainafter.cn/img/%E5%9B%BE%E7%A4%BA.png&quot; alt=&quot;b[l] + c的原因&quot; /&gt;&lt;/p&gt;
&lt;p&gt;新引入的 b[l] + c 使得数组a中 a[l]及以后的数都加上了c(红色部分)，&lt;/p&gt;
&lt;p&gt;但我们只要求l到r区间加上c, 因此还需要执行 b[r+1] - c,让数组a中a[r+1]及往后的区间再减去c(绿色部分)，&lt;/p&gt;
&lt;p&gt;这样对于a[r] 以后区间的数相当于没有发生改变。&lt;/p&gt;
&lt;p&gt;因此我们不难看出：给数组a中的[ l, r]区间中的每一个数都加上c,只需对差分数组b做 b[l] + = c, b[r+1] - = c。时间复杂度为O(1), 大大提高了效率。&lt;/p&gt;
&lt;h2&gt;五、方法&lt;/h2&gt;
&lt;p&gt;对数组a区间[l,r]同时加上c的操作可转化为：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;for(int i=1;i&amp;lt;=n;i++)//输入源数组a，n为数组a长度
{
	cin&amp;gt;&amp;gt;a[i];
	b[i]=a[i]-a[i-1];
}

int j,k,c;
while(m--)//生成差分数组b，m次操作
{
	cin&amp;gt;&amp;gt;j&amp;gt;&amp;gt;k&amp;gt;&amp;gt;c;
	b[j]+=c;
	b[k+1]-=c;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;再对数组b求前缀和即可得到原数组a：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;for(int i=1;i&amp;lt;=n;i++)//根据数组b前缀和，还原源数组a
{
    a[i]=b[i]+a[i-1];
    cout&amp;lt;&amp;lt;a[i]&amp;lt;&amp;lt;&apos; &apos;;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;六、例题（摘选自AcWing 797）&lt;/h2&gt;
&lt;p&gt;&lt;img src=&quot;https://resource.rainafter.cn/img/AcWing.797-%E5%B7%AE%E5%88%86.png&quot; alt=&quot;AcWing 797&quot; /&gt;&lt;/p&gt;
&lt;p&gt;AC示例：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;#include &amp;lt;iostream&amp;gt;
using namespace std;
const int N=1e5+1;
int a[N],b[N];

int main()
{
    int n,m;
    cin&amp;gt;&amp;gt;n&amp;gt;&amp;gt;m;

    for(int i=1;i&amp;lt;=n;i++)//输入源数组a
    {
        cin&amp;gt;&amp;gt;a[i];
        b[i]=a[i]-a[i-1];
    }
    
    int j,k,c;
    while(m--)//生成差分数组b
    {
        cin&amp;gt;&amp;gt;j&amp;gt;&amp;gt;k&amp;gt;&amp;gt;c;
        b[j]+=c;
        b[k+1]-=c;
    }
    
    for(int i=1;i&amp;lt;=n;i++)//根据数组b前缀和，还原源数组a
    {
        a[i]=b[i]+a[i-1];
        cout&amp;lt;&amp;lt;a[i]&amp;lt;&amp;lt;&apos; &apos;;
    }
    
    cout&amp;lt;&amp;lt;endl;
    return 0;
}
&lt;/code&gt;&lt;/pre&gt;
</content:encoded></item></channel></rss>