<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
  <id>https://www.refact.cc/</id>
  <title type="text">Firefly</title>
  <subtitle type="text">Refac7</subtitle>
  <updated>2026-09-09T00:00:00.000Z</updated>
  <author><name>Refac7</name></author>
  <link rel="alternate" href="https://www.refact.cc/"/>
  <link rel="self" href="https://www.refact.cc/atom.xml"/>
  <generator uri="https://github.com/CuteLeaf/Firefly">Firefly v6.16.8</generator>
    <entry>
      <id>https://www.refact.cc/posts/diy-estradiol-gel/</id>
      <title type="text">如何自制雌二醇凝胶</title>
      <published>2026-09-09T00:00:00.000Z</published>
      <updated>2026-09-09T00:00:00.000Z</updated>
      <author><name>Refac7</name></author>
      <link rel="alternate" href="https://www.refact.cc/posts/diy-estradiol-gel/"/>
      <summary type="text">记录一下我自制雌二醇凝胶的方法</summary>
      <content type="html"><![CDATA[<blockquote><p>本文档非专业医疗文档，此文档不完整、不精确、且不具备可重复性，仅供参考。此行为可能会对机体造成重大影响，本文不对任何人的任何不良反应负责，若你不同意以上内容，请立即退出本文。</p></blockquote>
<section><h2>本配方可能用到的器械（无序列表）：<a href="#本配方可能用到的器械无序列表"><span>#</span></a></h2><ul>
<li>毫克称；</li>
<li>克称或量筒；</li>
<li>烧杯若干；</li>
<li>胶头滴管；</li>
<li>药匙；</li>
<li>玻璃棒或磁力搅拌器；</li>
<li>pH试纸；</li>
</ul></section>
<section><h2>本配方可能用到的材料及其价格（单位均为人民币，按必要性排序）：<a href="#本配方可能用到的材料及其价格单位均为人民币按必要性排序"><span>#</span></a></h2>

<table><thead><tr><th>项目名</th><th>价格</th></tr></thead><tbody><tr><td>β-雌二醇</td><td>30元/g</td></tr><tr><td>95%乙醇(v/V)</td><td>5元/500mL</td></tr><tr><td>卡波姆980</td><td>13元/50g</td></tr><tr><td>三乙醇胺</td><td>5元/50g</td></tr><tr><td>氮酮（可选）</td><td>8元/500g</td></tr><tr><td>丙二醇（可选）</td><td>6元/100mL</td></tr><tr><td>纯化水</td><td>2元/500mL</td></tr></tbody></table></section>
<section><h2>以下为100g质量分数为0.10%凝胶的制作步骤：<a href="#以下为100g质量分数为010凝胶的制作步骤"><span>#</span></a></h2><ol>
<li>用克称量取<strong>63.0g</strong>乙醇溶液（95%(v/V)），倒入烧杯；</li>
<li>用毫克称量取<strong>0.100g</strong>雌二醇粉末，加入烧杯，搅拌使其完全溶解，此时溶液无色透明状态；</li>
<li>用克称量取<strong>28.5g</strong>纯化水，加入烧杯，搅拌均匀；</li>
<li>用毫克称量取<strong>1.000g</strong>卡波姆980粉末，少量多次加入烧杯，避免出现结块，搅拌一段时间后，停顿，再搅拌，重复若干次，使其溶解，如果非磁力搅拌器，可能无法完全溶解，此时溶液应半透明偏白；</li>
<li>（可选）用毫克称量取<strong>5.000g</strong>丙二醇，加入烧杯，搅拌均匀；</li>
<li>（可选）用毫克称量取<strong>1.000g</strong>氮酮，加入烧杯，搅拌均匀；</li>
<li>用毫克称量取<strong>1.350g</strong>三乙醇胺溶液，加入烧杯，搅拌均匀，搅拌过程中，烧杯内溶液逐渐变黏稠，至此，我们制得约<strong>100g</strong>雌二醇凝胶(0.1%)，市售凝胶为(0.06%)，必须根据定期检验报告，自身吸收效率，促渗剂比例等调整质量分数。</li>
</ol><p>用pH试纸测试pH，应在6.5附近。卡波姆980的最佳中和pH值为6.0-6.5。pH若低于5.5，凝胶会变稀且严重刺激皮肤导致红肿溃烂；pH若高于7.5，雌二醇在碱性环境下极易降解失效。本配方无防腐剂，凝胶可能发霉导致皮肤感染，需要了解该风险。</p><p>最后提示：<strong>自行调配含雌二醇的外用制剂，属于自制药品</strong>。根据《中华人民共和国药品管理法》，未经许可配制药品属于违法行为；如果对外出售，会按生产假药 / 劣药处罚。以及…请务必珍视自身健康。</p><p>参考文章：</p><ul>
<li><a href="https://github.com/lydlid/estrogel-diy-guide-zh_CN" target="_blank">https://github.com/lydlid/estrogel-diy-guide-zh_CN</a></li>
<li><a href="https://github.com/Bluse-liu50/DIY-Estradiol-Gel-Guide" target="_blank">https://github.com/Bluse-liu50/DIY-Estradiol-Gel-Guide</a></li>
</ul></section>]]></content>
    </entry>
    <entry>
      <id>https://www.refact.cc/posts/hrt-one-month/</id>
      <title type="text">HRT一个月以来的变化</title>
      <published>2026-07-07T00:00:00.000Z</published>
      <updated>2026-07-07T00:00:00.000Z</updated>
      <author><name>Refac7</name></author>
      <link rel="alternate" href="https://www.refact.cc/posts/hrt-one-month/"/>
      <summary type="text">一个月的时间，不算长，但已经能看到一些可以被记录的变化。</summary>
      <content type="html"><![CDATA[<p>我是从6月1日正式开始HRT的。没有仪式，没有告知任何人，只是在那天早上像往常一样起身，然后做了个决定。我的用量如下表所示：</p>

<table><thead><tr><th>时间段</th><th>用法</th></tr></thead><tbody><tr><td>1~7d</td><td>每日2mg雌二醇凝胶（早晚各4泵）+4*20mg螺内酯</td></tr><tr><td>7~14d</td><td>每日2mg雌二醇凝胶（早晚各4泵）+0.25*50mg醋酸环丙孕酮</td></tr><tr><td>14d~28d</td><td>每日4mg雌二醇凝胶（早晚各8泵）+0.125*50mg醋酸环丙孕酮</td></tr><tr><td>28d~至今</td><td>每日3mg雌二醇凝胶（早晚各6泵）+0.125*50mg醋酸环丙孕酮</td></tr></tbody></table>
<p>螺内酯后来被替换了，因为它的作用不够强，而且我总会忘记自己是否已经吃过。换用醋酸环丙孕酮之后，我知道自己每天只需要取四分之一片，不需要再数药片了。</p>
<section><h2>身体反应<a href="#身体反应"><span>#</span></a></h2><p>皮肤变化（定性分析）：皮肤出油减少了很多。以前到中午就会泛一层油光，现在一整张脸可以维持到傍晚不反光。身上的肉摸起来更软了，像松开的土壤。皮肤细腻程度和体毛没有明显变化，这两样是我想改变但暂时还没出现的部分。</p><p>腺体变化（定性分析）：乳腺在第23天开始有触痛感。那种感觉很奇怪，像皮肤下面有什么东西在松动。乳晕略微增大，颜色变得更浅了一些，不是特别明显，但我注意到了。</p></section>
<section><h2>精神反应<a href="#精神反应"><span>#</span></a></h2><p>食欲变化：总体上食欲增加了，但食物偏好也变了。以前吃东西是为了填饱肚子，现在会想吃甜的东西。小蛋糕、布丁、带糖浆的咖啡。</p><p>性欲变化：最后一次勃起反应记录在HRT第5天。之后就没有了。那种感觉像是某种长期存在的背景噪音被静音了。</p><p>精神医学：情绪变得比之前更可感。不一定是更高兴，但能更清楚地知道自己在难过，在高兴，或者只是平静。似乎开始从”知道自己在感受什么”慢慢变成”接受自己这样感受”。</p></section>
<section><h2>医学检验报告<a href="#医学检验报告"><span>#</span></a></h2><p>检验于HRT第30天。上午抽血，下午收到结果。数值比我预期的更高一些，但也不算意外。</p>

<table><thead><tr><th>检验项目</th><th>英文缩写</th><th>检验结果</th><th>单位</th><th>参考范围</th><th>检验方法</th></tr></thead><tbody><tr><td>雌二醇E2</td><td>E2</td><td>↑319.36</td><td>pg/mL</td><td>0‑84 pg/mL</td><td>化学发光法</td></tr><tr><td>睾酮</td><td>TESTO</td><td>↓&lt;0.025</td><td>ng/mL</td><td>1.37‑8.25 ng/mL</td><td>化学发光法</td></tr></tbody></table></section>
<section><h2>一些还没有结论的感想<a href="#一些还没有结论的感想"><span>#</span></a></h2><p>一个月不长，但感觉已经过了很长时间。身体在慢慢变化，有些变化我已经能感知到，有些可能还需要更久才显现。我不知道这条路会不会通向我想去的方向，但我知道自己已经不想回到之前的生活了。</p><p>8月23日 第一次修改</p></section>]]></content>
    </entry>
    <entry>
      <id>https://www.refact.cc/posts/rhythm-doctor/</id>
      <title type="text">节奏医生入门指南</title>
      <published>2026-03-22T00:00:00.000Z</published>
      <updated>2026-03-22T00:00:00.000Z</updated>
      <author><name>Refac7</name></author>
      <link rel="alternate" href="https://www.refact.cc/posts/rhythm-doctor/"/>
      <summary type="text">因为我也没什么乐理知识，所以就是经验总结</summary>
      <content type="html"><![CDATA[<p>好久不见，转眼两个月没写文章了。因为这个学期的课真的多到离谱，五天早六+五天基本满课</p>
<p>还有就是最近沉迷于《节奏医生》这款游戏。由7BG(7th Beat Games)开发，与《冰与火之舞》(adofai)同门。不同的是，这款游戏包含了大量剧情，有兴趣的可以去Steam购买游玩（原价58R）。这是一款单键音游，包含十余种节拍类型。虽然游戏玩法相对简单，只需要在第七拍按下即可，且对手速没有很高的要求。但是会存在同时治疗多位病人，窗口移动等视觉干扰，相比adofai会简单很多，但是又不会食之无味。</p>
<p>入门指南视频：<a href="https://www.bilibili.com/video/BV1ifAuzyEBM" target="_blank">Bilibili - BV1ifAuzyEBM</a></p>
<section><h2>校准（很重要）<a href="#校准很重要"><span>#</span></a></h2><p>因为节奏医生这款游戏的判定标准相对其他音游是偏高的，只有good和bad两种评定，没有perfect、good等，比如喵斯快跑在±120ms内都认定为中了，而这是节奏医生简单难度的判定标准。所以在开始之前我们必须进行校准，这十分影响游戏体验。包含视效偏移和输入偏移。在第一次进行游戏时，伊恩医生会强制要求你进行校准，后续可以在设置中重复校准。</p><section><h3>视效偏移<a href="#视效偏移"><span>#</span></a></h3><p>这通常是为了平衡渲染延迟（画面从 GPU 算完到显示的时间），一般电脑在8～25ms内。在看到第七个小方块闪烁时按下空格。</p></section><section><h3>输入偏移<a href="#输入偏移"><span>#</span></a></h3><p>包含两部分，键盘输入延迟和耳机输出延迟。在听到第七拍时按下空格。</p><p>校准完成之后可以把设置-&gt;高级设置中的”显示判定数值”和”详细关卡结算”。开启”详细关卡结算”后，正常打一首歌，在显示评级时，你偏早和偏晚的帧数会显示在屏幕底部。理想情况下，偏早和偏晚的帧数应该是基本相同的，这样基本就校准好了。如果你后续更换了外设，如键盘、耳机等，都必须重新校准。</p><p>下表为有关难度配置的判定标准（1.0.5实际测试）</p>

<table><thead><tr><th>难度</th><th>判定标准(ms)</th></tr></thead><tbody><tr><td>困难</td><td>±40</td></tr><tr><td>标准</td><td>±80</td></tr><tr><td>简单</td><td>±120</td></tr><tr><td>极简</td><td>±200</td></tr><tr><td>按了就中</td><td>—</td></tr></tbody></table></section></section>
<section><h2>节拍类型<a href="#节拍类型"><span>#</span></a></h2><p>注：因为我也没什么乐理知识，所以就是经验总结，每个”-“代表等待间隔。</p><p>标准七拍子：以角色”武士”等为代表。第一关1-1（东方电舞曲）和1-X（东方不眠夜）最为典型，在第七拍按下即可。1-X包括1-XN因”网好卡菌”有严重的视觉干扰，需要必要的数拍。</p><p>标准二拍子：在第二章首次出现，代表为室上速病房的传奇音乐人”科尔”和”妮可”。2-2N（Unreachable）好听。此时护士会有”Rea—dy—ge—set—go”的节拍提示，100X100X（1为鼓点，0为空拍，在X处按下）即节拍间距。标准二拍子的第一拍和第二拍之间的间隔和上个第二拍的间距相同，匀速地按就好。</p><p>摇摆二拍子：也是以”科尔”和”妮可”为代表，2-3（缭绕），2-3N（嗅炸弹的博美犬），这里难度会升高很多，是标准二拍和摇摆二拍的混合，需要特别注意节拍转换。需要熟练掌握摇摆二拍子的技巧并多加练习方能通过。摇摆二拍子和标准略有不同，这里以2-3的节拍为例，护士的提示音会变为<strong>长 - 短 - 长 - 短</strong> ，所以每双数拍内的间隔会变短，双拍之间的间隔会变长。还有二拍子变速，在2-2的教程中出现，在2-X和2-XN(Bitter Times)会使用。</p><p>摇摆七拍子：在第三章出现，以”史蒂文森夫妇”和总统”理查德·休”为代表。3-2（典雅），3-3（分离二重奏）使用，拍型为1-2—3-4—5-6—7以及1—2-3—4-5—6-7两种类型。第一种在第六拍再等一拍，第二种在第六拍就要立即按了，这章总体难度不高。</p><p>长按拍子：在第四章来到了火车上，新人物”矿工大叔”。拍型为1—2-3—4-5—6-7—8（例），在第七拍按下，在第八拍松开。有时也和二拍子混合使用，如7-1（模糊）。要注意提示”咔”的一声，不能短按，这种拍型会在以后的章节频繁出现。</p><p>细分二拍子：一种新型的二拍子，在第五章（理疗病房）首次出现，以棒球手”路吉”为代表，5-1（路吉摔了一跤）为准。一般一次以两个及以上出现，护士会用英文提示”Two!”，然后砰+（细分拍长）+砰+（拍长）+啪+（细分拍长）+啪。细分拍长就是把原本 1 拍的时间，均匀拆成 2 小拍，按 2 次按键。在啪声处按下即可，在主线剧情中，最多的是八连拍（7-X2）。</p><p>冻结拍子：在5-2(Lo-fi Beats For Patients To Chill To)出现，音效（两节，提示音在第一节出现）提示为铿+（冻结拍长）+咔+（拍长）+啪+铿+（拍长）+（冻结拍长）+啪。需要注意的是，因为上一节的拍长变化，所以下一节需要提前一个冻结拍长按键，这是比较难的地方。</p><p>灼热拍子：在且仅在5-2N（无以为继的伤心欲绝）中出现，与冻结拍子相反，需要在第二拍的拍长减去灼热拍长，而5-2N将冻结和灼热拍子相结合（森林冰火人），难度很高，需要背板才能打下来。</p><p>冲击拍子：在5-X（梦想不停歇）中首次出现，在听到哨声时就意味着要出现冲击拍子了，哨声之间的间隔为标准拍长，所以每一拍都要按键，难点是前后的衔接和匀速按键。</p><p>切分拍子：第六章我们来到了病历间，在6-1（想把心事和你说）首次出现，在轨道上显示的是并列的三角，伴有提示音”唧”，一般是在标准七拍中掺入，在经过这个拍子时，拍长会变为原本的一半，需要提前按键，尤其是在第六拍切分时。</p><p>五拍子：在主线剧情中仅在患者”马克·布克”身上出现（即6-2，6-X，7-1），因为先前遭受的心理创伤，他的轨道变短了，需要在第五拍按下，所以这类关卡的按键频率会高一些。</p><p>还有其他拍子如摇摆五拍子（5-2），分裂三连音的二拍子（6-X）等就不展开介绍了，可以在教程中反复练习以上所有种类的拍型。</p></section>
<section><h2>社区关卡<a href="#社区关卡"><span>#</span></a></h2><p>除了官方制作的主线关卡之外，游戏内置的关卡编辑器允许玩家制作自定义关卡，可以在Steam创意工坊和 <a href="https://rhythm.cafe/" target="_blank">https://rhythm.cafe/</a> 获取这些社区关卡。社区关卡和官方关卡相比，可能存在不符合标准的提示音和过多的视觉干扰，导致几乎无法读谱。</p></section>
<section><h2>参考文献<a href="#参考文献"><span>#</span></a></h2><p>Bilibili-春望之人BEP：<a href="https://space.bilibili.com/179800587/lists/3844590" target="_blank">https://space.bilibili.com/179800587/lists/3844590</a></p><p>Rhythm Doctor Wiki：<a href="https://wiki.rhythm.cafe" target="_blank">https://wiki.rhythm.cafe</a></p></section>]]></content>
    </entry>
    <entry>
      <id>https://www.refact.cc/posts/javascript-electron/</id>
      <title type="text">从 JavaScript 前端开发到 Electron 框架跨平台开发</title>
      <published>2026-01-02T00:00:00.000Z</published>
      <updated>2026-01-02T00:00:00.000Z</updated>
      <author><name>Refac7</name></author>
      <link rel="alternate" href="https://www.refact.cc/posts/javascript-electron/"/>
      <summary type="text">本文主要描述了 JavaScript 和 Electron 的发展，关系，以及对于软件开发和原生开发可能存在的影响进行了分析。</summary>
      <content type="html"><![CDATA[<p><strong>关键词</strong>：全栈开发、JavaScript、Electron等。</p>
<p><strong>引言</strong> ：要说互联网网页的三大基石，那毋庸置疑是HTML、CSS、JavaScript。其中控制整个页面逻辑的就是JavaScript，是进行人机交互不可或缺的接口。而最初的JavaScript只能在浏览器中运行，也就是说，那时的JavaScript只能作为一个前端语言来使用。而之后，Node.js 横空出世，使得JavaScript可以作为一门后端语言来使用。此外，使用Electron 框架可以通过前端语言来创建跨平台应用，囊括了 Windows、macOS、Linux。</p>
<section><h2><strong>什么是JavaScript？(1)</strong><a href="#什么是javascript1"><span>#</span></a></h2><p>实际上，JavaScript 和 Java 没有实质上的关系，甚至可以说是毫不相干的。</p><p>当时，主流的浏览器之一是网景浏览器（Netscape Navigator），网页主要由超文本标记语言（HTML）构成，交互能力非常有限。用户在浏览网页时大多只能被动查看，主要的交互操作仅能通过点击超链接实现，这显然有很大的局限性。</p><p>因此，在 1995 年 9 月，JavaScript（最初称为 Mocha，后改名 LiveScript，最终定为 JavaScript）被引入，它提供了一系列可在页面内执行的脚本功能。这些脚本通过 <code>&lt;script&gt;</code> 标签嵌入 HTML，能够在页面加载或用户交互时运行。其核心思想是实现动态的页面行为，类似于如今局部刷新的交互模式，例如：通过浏览器 API 弹出对话框、控制页面滚动，或是读取浏览器提供的部分信息（如用户代理、操作系统等），并基于这些信息执行相应逻辑。</p><p>当然，早期的 JavaScript 并不完善，存在诸多兼容性与功能上的问题，需要各个浏览器厂商逐步实现统一的标准支持。</p><p>JavaScript 的标准化进程由 ECMA International 组织推进，1997 年首次发布 ECMAScript 标准。2009 年发布的 ES5 是重要的里程碑，提升了语言稳定性与功能性。与此同时，2008 年 Google 发布的 Chrome 浏览器搭载了 V8 引擎，极大提高了 JavaScript 的执行性能。随着现代浏览器对标准更加一致的支持，基于 JavaScript 的生态系统迅速发展壮大，其中包括服务器端的 Node.js（2009 年发布）以及桌面应用框架 Electron（2013 年发布）等，进一步拓展了 JavaScript 的应用领域与社区影响力。</p></section>
<section><h2><strong>什么是Electron框架？(2)</strong><a href="#什么是electron框架2"><span>#</span></a></h2><p>Electron 最初由 GitHub（在微软收购之前）开发，是一个基于 Node.js 和 Chromium 的开源应用程序框架。该框架本身主要由 C++ 编写，其核心原理是将网页（前端技术）与 Node.js 后端能力结合，封装成一个独立的桌面应用程序。Electron 本身提供了一系列原生 API，使开发者能够与操作系统进行交互和调用系统功能。</p><p>这一点与 Windows 10 时期引入的 Web UI（如 UWP (4)中使用 WebView 的界面）不同，因为两者所处的架构层次和权限级别存在差异。Windows 系统自身的 UI 组件是在系统层面统一实现和管理的，同一组件在不同应用间行为一致，且通常由操作系统直接调度与绘制。而 Electron 则允许通过其底层接口调用操作系统提供的原生 UI 元素，例如窗口、对话框、菜单等，并可以深入访问系统级功能。</p><p>Chromium 在 Electron 中主要负责网页的渲染、网络请求的处理（如解析与发送数据包），以及提供现代的浏览器能力。其底层通过 C++ 实现，使得 Electron 应用能获得比一般 Web 应用（如运行在浏览器沙盒环境中的 Web UI）更高的系统资源访问权限。</p><p>当然，这种架构也存在一些不足。由于每个 Electron 应用都内置了完整的 Chromium 渲染引擎和 Node.js 运行时，其内存占用通常比纯粹的原生应用更高，这也是其常见的性能权衡之一。</p><p>目前，已经有不少知名软件采用 Electron 来实现跨平台开发，例如微软的 Visual Studio Code、腾讯的 QQ NT 以及 Discord 等。这些应用在保持良好跨平台兼容性的同时，也提供了丰富的现代交互体验。</p></section>
<section><h2><strong>这是如何实现的？(5)</strong><a href="#这是如何实现的5"><span>#</span></a></h2><p>·   <strong>Chromium</strong>：用于渲染用户界面，每个窗口都是一个独立的 Chromium 渲染进程，支持现代 Web 标准。</p><p>·   <strong>Node.js</strong>：在主进程中运行，提供文件系统、网络等操作系统 API 的访问能力。</p><p>而Electron 框架就是起到了一个桥接的作用，将Web层面的操作翻译给后端处理，再将处理后的结果传给前端。</p><p>而对于系统API的调用，内存管理，文件操作等大多是由后端的Node.js处理的。在实际软件开发中，需要用类似 Electron Forge 的工具，将软件打包为 tar.gz(Linux)/dmg(macOS)/exe(Windows)来进行后续的分发操作。</p>

<table><thead><tr><th>Web 技术层 (HTML/CSS/JS)</th></tr></thead><tbody><tr><td><code>Electron 桥接层</code></td></tr><tr><td><code>Chromium + Node.js 运行时环境</code></td></tr></tbody></table><p>我们知道，不同的操作系统的操作有所差异比如Windows上的Alt键对应着macOS的Option键，文件系统目录<code>/Macintosh HD/Application Supports</code>（macOS）对应了<code>C:\Program Files</code>(Windows)，所以Electron程序会根据所打包的程序类型转换按键绑定和文件系统定位等，而开发者也需要在各个平台进行充分测试和调试。</p></section>
<section><h2><strong>对于软件开发的影响力</strong><a href="#对于软件开发的影响力"><span>#</span></a></h2><p>为什么选择 Electron？在进行软件开发时，开发者必须考虑其多平台兼容性，如果应用程序不支持目标用户的操作系统，会直接导致客户的流失。而对于主流平台独占的软件开发框架，如dotnet(Windows)、Swift（macOS）、GTK（Linux）。这种专业性要求和学习成本极高的软件开发策略显然不是最优解。</p><p>而HTML、CSS、JavaScript作为前端开发工程师必备的技能，只需要相对较低的学习成本就可以开发三端互通的应用程序。所以近年来，使用Electron 框架的公司数量也在稳步增长。</p></section>
<section><h2><strong>那原生开发怎么办？</strong><a href="#那原生开发怎么办"><span>#</span></a></h2><p>我们一开始就提到了，相比于原生开发，Electron也是存在弊端的，这点在官方文档（5）也有提及。</p><p>首先是在资源受限的环境，我们之前提到了，由于软件的运行需要多一层翻译，所以转换效率肯定不如原生语言直接输出到显示器。也包括内存资源的占用。这在类似物联网设备上是绝对的红线。</p><p>其次，Electron 通过将最新版本的 Chromium、V8 和 Node.js 直接与应用程序二进制文件打包在一起，安装包自带浏览器内核。所以，打包后的包体一般不会小于100MiB。即使在操作系统有内嵌浏览器的情况下，据开发者所述（4），这是为了稳定性、安全性和性能。因此，对于一些磁盘占用敏感的情况下，我们不得不选择原生开发，因为可以调用系统自带的API。</p><p>再者，显而易见，Web技术并不适合用于编写游戏。因为往往Unity、虚幻引擎、DirectX/OpenGL 等原生框架可以提供更出色的性能，并能更直接地访问图形硬件。</p></section>
<section><h2><strong>参考文献</strong><a href="#参考文献"><span>#</span></a></h2><p>1.维基百科 - <a href="https://zh.wikipedia.org/wiki/JavaScript" target="_blank">https://zh.wikipedia.org/wiki/JavaScript</a></p><p>2.维基百科 - <a href="https://zh.wikipedia.org/wiki/Electron" target="_blank">https://zh.wikipedia.org/wiki/Electron</a></p><p>3.Electron 官方文档 - <a href="https://www.electronjs.org/zh/docs/latest" target="_blank">https://www.electronjs.org/zh/docs/latest</a></p><p>4.WIKIPEDIA - <a href="https://en.wikipedia.org/wiki/Universal_Windows_Platform" target="_blank">https://en.wikipedia.org/wiki/Universal_Windows_Platform</a></p><p>5.Electron 官方文档 - <a href="https://www.electronjs.org/zh/docs/latest/why-electon" target="_blank">https://www.electronjs.org/zh/docs/latest/why-electon</a></p><p>12月19日 起稿</p><p>1月2日 初稿</p><p>1月3日 第一次修改</p></section>]]></content>
    </entry>
    <entry>
      <id>https://www.refact.cc/posts/imgbed-setup/</id>
      <title type="text">图床配置指南</title>
      <published>2025-12-30T00:00:00.000Z</published>
      <updated>2025-12-30T00:00:00.000Z</updated>
      <author><name>Refac7</name></author>
      <link rel="alternate" href="https://www.refact.cc/posts/imgbed-setup/"/>
      <summary type="text">如何在 RefactX v1.6中使用图床以及在Waline 评论系统中启用图片上传</summary>
      <content type="html"><![CDATA[<p>注意：启用图床将会禁用图片优化功能。如果你强制在<code>./astro.config.mjs</code>的52行处修改为你的图床地址，那大概会被防盗链功能拦截，所以图片优化仅限静态资源。</p>
<p>首先需要的是S3对象存储，任何提供商理论上都支持，由于涉及到接口的编写，所以以CF R2和Worker举例。</p>
<p>本方法来自 <a href="https://tbbbk.com/archives/cloudflare-r2-imgbed" target="_blank">https://tbbbk.com/archives/cloudflare-r2-imgbed</a> 但其中的内容稍有纰漏，所以我决定重写这一部分。</p>
<section><h2>开通对象存储<a href="#开通对象存储"><span>#</span></a></h2><p>CF主页 &gt; 存储和数据库 &gt; R2对象存储 &gt; 概述 &gt; 创建储存桶</p><p>名称为 imgs ，位置选亚太地区，标准类型。</p></section>
<section><h2>配置Worker<a href="#配置worker"><span>#</span></a></h2><p>CF主页 &gt; 计算和AI &gt; Workers 和 Pages &gt; 创建应用程序</p><p>选择从 Hello World! 开始，Worker name填 imgctl ，然后部署。</p><p>进入创建好的 Worker ，编辑代码。</p><p>Cloudflare worker 配置如下：</p><p>你需要在allowedDomains块中引入你的源域名，也就是你需要从哪里访问这些资源。</p><div><div><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>export</span><span> </span><span>default</span><span> {</span></div></div><div><div><div>2</div></div><div><span>  </span><span>async</span><span> </span><span>fetch</span><span><span>(</span><span>request</span><span>, </span><span>env</span><span>) {</span></span></div></div><div><div><div>3</div></div><div><span>    </span><span>const</span><span> </span><span>url</span><span> </span><span>=</span><span> </span><span>new</span><span> </span><span>URL</span><span><span>(</span><span>request</span><span>.</span></span><span>url</span><span>);</span></div></div><div><div><div>4</div></div><div><span>    </span><span>const</span><span> </span><span>pathname</span><span> </span><span>=</span><span><span> </span><span>url</span><span>.</span></span><span>pathname</span><span>;</span></div></div><div><div><div>5</div></div><div>
</div></div><div><div><div>6</div></div><div><span>    </span><span>const</span><span> </span><span>referer</span><span> </span><span>=</span><span><span> </span><span>request</span><span>.</span></span><span>headers</span><span>.</span><span>get</span><span>(</span><span>'Referer'</span><span>) </span><span>||</span><span> </span><span>''</span><span>;</span></div></div><div><div><div>7</div></div><div><span>    </span><span>const</span><span> </span><span>allowedDomains</span><span> </span><span>=</span><span> [</span><span>'refact.cc'</span><span>, </span><span>'www.refact.cc'</span><span>, </span><span>'xlog.app'</span><span>, </span><span>'refact.xlog.app'</span><span>, </span><span>'localhost'</span><span>];</span></div></div><div><div><div>8</div></div><div>
</div></div><div><div><div>9</div></div><div><span>    </span><span>// -----------------------------</span></div></div><div><div><div>10</div></div><div><span>    </span><span>// ✅ CORS 预检</span></div></div><div><div><div>11</div></div><div><span>    </span><span>// -----------------------------</span></div></div><div><div><div>12</div></div><div><span>    </span><span>if</span><span><span> (</span><span>request</span><span>.</span></span><span>method</span><span> </span><span>===</span><span> </span><span>'OPTIONS'</span><span>) {</span></div></div><div><div><div>13</div></div><div><span>      </span><span>return</span><span> </span><span>new</span><span> </span><span>Response</span><span>(</span><span>null</span><span>, {</span></div></div><div><div><div>14</div></div><div><span>        </span><span>status</span><span>:</span><span> </span><span>204</span><span>,</span></div></div><div><div><div>15</div></div><div><span>        </span><span>headers</span><span>:</span><span> {</span></div></div><div><div><div>16</div></div><div><span>          </span><span>'Access-Control-Allow-Origin'</span><span>:</span><span> </span><span>'*'</span><span>,</span></div></div><div><div><div>17</div></div><div><span>          </span><span>'Access-Control-Allow-Methods'</span><span>:</span><span> </span><span>'GET, POST, OPTIONS'</span><span>,</span></div></div><div><div><div>18</div></div><div><span>          </span><span>'Access-Control-Allow-Headers'</span><span>:</span><span> </span><span>'Content-Type, Authorization'</span><span>,</span></div></div><div><div><div>19</div></div><div><span>          </span><span>'Access-Control-Max-Age'</span><span>:</span><span> </span><span>'86400'</span></div></div><div><div><div>20</div></div><div><span><span>        </span></span><span>}</span></div></div><div><div><div>21</div></div><div><span><span>      </span></span><span>});</span></div></div><div><div><div>22</div></div><div><span><span>    </span></span><span>}</span></div></div><div><div><div>23</div></div><div>
</div></div><div><div><div>24</div></div><div><span>    </span><span>const</span><span> </span><span>corsHeaders</span><span> </span><span>=</span><span> {</span></div></div><div><div><div>25</div></div><div><span>      </span><span>'Access-Control-Allow-Origin'</span><span>:</span><span> </span><span>'*'</span><span>,</span></div></div><div><div><div>26</div></div><div><span>      </span><span>'Access-Control-Allow-Methods'</span><span>:</span><span> </span><span>'GET, POST, OPTIONS'</span><span>,</span></div></div><div><div><div>27</div></div><div><span>      </span><span>'Access-Control-Allow-Headers'</span><span>:</span><span> </span><span>'Content-Type, Authorization'</span></div></div><div><div><div>28</div></div><div><span><span>    </span></span><span>};</span></div></div><div><div><div>29</div></div><div>
</div></div><div><div><div>30</div></div><div><span>    </span><span>// -----------------------------</span></div></div><div><div><div>31</div></div><div><span>    </span><span>// ✅ POST 上传（使用 Token 校验）</span></div></div><div><div><div>32</div></div><div><span>    </span><span>// -----------------------------</span></div></div><div><div><div>33</div></div><div><span>    </span><span>if</span><span><span> (</span><span>request</span><span>.</span></span><span>method</span><span> </span><span>===</span><span> </span><span>'POST'</span><span> </span><span>&amp;&amp;</span><span> </span><span>pathname</span><span> </span><span>===</span><span> </span><span>'/upload'</span><span>) {</span></div></div><div><div><div>34</div></div><div><span>      </span><span>try</span><span> {</span></div></div><div><div><div>35</div></div><div><span>        </span><span>const</span><span> </span><span>auth</span><span> </span><span>=</span><span><span> </span><span>request</span><span>.</span></span><span>headers</span><span>.</span><span>get</span><span>(</span><span>'Authorization'</span><span>);</span></div></div><div><div><div>36</div></div><div><span>        </span><span>if</span><span> (</span><span>!</span><span>auth</span><span> </span><span>||</span><span> </span><span>auth</span><span> </span><span>!==</span><span> </span><span>`Bearer </span><span>${</span><span>env</span><span>.</span><span>UPLOAD_TOKEN</span><span>}</span><span>`</span><span>) {</span></div></div><div><div><div>37</div></div><div><span>          </span><span>return</span><span> </span><span>new</span><span> </span><span>Response</span><span>(</span></div></div><div><div><div>38</div></div><div><span>            </span><span>JSON</span><span>.</span><span>stringify</span><span>({ </span><span>error</span><span>:</span><span> </span><span>'Invalid upload token'</span><span> }),</span></div></div><div><div><div>39</div></div><div><span><span>            </span></span><span>{ </span><span>status</span><span>:</span><span> </span><span>403</span><span>, </span><span>headers</span><span>:</span><span> { </span><span>'Content-Type'</span><span>:</span><span> </span><span>'application/json'</span><span>, </span><span>...</span><span>corsHeaders</span><span> } }</span></div></div><div><div><div>40</div></div><div><span><span>          </span></span><span>);</span></div></div><div><div><div>41</div></div><div><span><span>        </span></span><span>}</span></div></div><div><div><div>42</div></div><div>
</div></div><div><div><div>43</div></div><div><span>        </span><span>const</span><span> </span><span>formData</span><span> </span><span>=</span><span> </span><span>await</span><span><span> </span><span>request</span><span>.</span></span><span>formData</span><span>();</span></div></div><div><div><div>44</div></div><div><span>        </span><span>const</span><span> </span><span>file</span><span> </span><span>=</span><span><span> </span><span>formData</span><span>.</span></span><span>get</span><span>(</span><span>'file'</span><span>);</span></div></div><div><div><div>45</div></div><div>
</div></div><div><div><div>46</div></div><div><span>        </span><span>if</span><span> (</span><span>!</span><span>file</span><span>) {</span></div></div><div><div><div>47</div></div><div><span>          </span><span>return</span><span> </span><span>new</span><span> </span><span>Response</span><span>(</span></div></div><div><div><div>48</div></div><div><span>            </span><span>JSON</span><span>.</span><span>stringify</span><span>({ </span><span>error</span><span>:</span><span> </span><span>'No file uploaded'</span><span> }),</span></div></div><div><div><div>49</div></div><div><span><span>            </span></span><span>{ </span><span>status</span><span>:</span><span> </span><span>400</span><span>, </span><span>headers</span><span>:</span><span> { </span><span>'Content-Type'</span><span>:</span><span> </span><span>'application/json'</span><span>, </span><span>...</span><span>corsHeaders</span><span> } }</span></div></div><div><div><div>50</div></div><div><span><span>          </span></span><span>);</span></div></div><div><div><div>51</div></div><div><span><span>        </span></span><span>}</span></div></div><div><div><div>52</div></div><div>
</div></div><div><div><div>53</div></div><div><span>        </span><span>// 1. 获取 URL 参数 ?path=xxx</span></div></div><div><div><div>54</div></div><div><span>        </span><span>const</span><span> </span><span>pathType</span><span> </span><span>=</span><span><span> </span><span>url</span><span>.</span></span><span>searchParams</span><span>.</span><span>get</span><span>(</span><span>'path'</span><span>);</span></div></div><div><div><div>55</div></div><div>
</div></div><div><div><div>56</div></div><div><span>        </span><span>// 2. 生成基础随机文件名</span></div></div><div><div><div>57</div></div><div><span>        </span><span>const</span><span> </span><span>ext</span><span> </span><span>=</span><span><span> </span><span>file</span><span>.</span></span><span>name</span><span>.</span><span>substring</span><span><span>(</span><span>file</span><span>.</span></span><span>name</span><span>.</span><span>lastIndexOf</span><span>(</span><span>'.'</span><span>)) </span><span>||</span><span> </span><span>''</span><span>;</span></div></div><div><div><div>58</div></div><div><span>        </span><span>const</span><span> </span><span>uniqueName</span><span> </span><span>=</span><span> </span><span>`</span><span>${</span><span>Date</span><span>.</span><span>now</span><span>()</span><span>}</span><span>-</span><span>${</span><span>Math</span><span>.</span><span>random</span><span>()</span><span>.</span><span>toString</span><span>(</span><span>36</span><span>)</span><span>.</span><span>slice</span><span>(</span><span>2</span><span>)</span><span>}${</span><span>ext</span><span>}</span><span>`</span><span>;</span></div></div><div><div><div>59</div></div><div>
</div></div><div><div><div>60</div></div><div><span>        </span><span>// 3. 根据参数决定最终路径</span></div></div><div><div><div>61</div></div><div><span>        </span><span>let</span><span> </span><span>filename</span><span>;</span></div></div><div><div><div>62</div></div><div><span>        </span><span>if</span><span> (</span><span>pathType</span><span> </span><span>===</span><span> </span><span>'root'</span><span>) {</span></div></div><div><div><div>63</div></div><div><span>          </span><span>// 如果参数是 root，直接放在根目录 (Admin 上传)</span></div></div><div><div><div>64</div></div><div><span>          </span><span>filename</span><span> </span><span>=</span><span> </span><span>uniqueName</span><span>;</span></div></div><div><div><div>65</div></div><div><span><span>        </span></span><span>} </span><span>else</span><span> {</span></div></div><div><div><div>66</div></div><div><span>          </span><span>// 默认情况，放在 comment 文件夹下 (Waline 评论)</span></div></div><div><div><div>67</div></div><div><span>          </span><span>filename</span><span> </span><span>=</span><span> </span><span>`comment/</span><span>${</span><span>uniqueName</span><span>}</span><span>`</span><span>;</span></div></div><div><div><div>68</div></div><div><span><span>        </span></span><span>}</span></div></div><div><div><div>69</div></div><div>
</div></div><div><div><div>70</div></div><div><span>        </span><span>const</span><span> </span><span>buffer</span><span> </span><span>=</span><span> </span><span>await</span><span><span> </span><span>file</span><span>.</span></span><span>arrayBuffer</span><span>();</span></div></div><div><div><div>71</div></div><div>
</div></div><div><div><div>72</div></div><div><span>        </span><span>await</span><span><span> </span><span>env</span><span>.</span></span><span>MY_BUCKET</span><span>.</span><span>put</span><span>(</span><span>filename</span><span>, </span><span>buffer</span><span>, {</span></div></div><div><div><div>73</div></div><div><span>          </span><span>httpMetadata</span><span>:</span><span> { </span><span>contentType</span><span>:</span><span><span> </span><span>file</span><span>.</span></span><span>type</span><span> }</span></div></div><div><div><div>74</div></div><div><span><span>        </span></span><span>});</span></div></div><div><div><div>75</div></div><div>
</div></div><div><div><div>76</div></div><div><span>        </span><span>return</span><span> </span><span>new</span><span> </span><span>Response</span><span>(</span></div></div><div><div><div>77</div></div><div><span>          </span><span>JSON</span><span>.</span><span>stringify</span><span>({ </span><span>url</span><span>:</span><span> </span><span>`https://img.refact.cc/</span><span>${</span><span>filename</span><span>}</span><span>`</span><span> }),</span></div></div><div><div><div>78</div></div><div><span><span>          </span></span><span>{ </span><span>status</span><span>:</span><span> </span><span>200</span><span>, </span><span>headers</span><span>:</span><span> { </span><span>'Content-Type'</span><span>:</span><span> </span><span>'application/json'</span><span>, </span><span>...</span><span>corsHeaders</span><span> } }</span></div></div><div><div><div>79</div></div><div><span><span>        </span></span><span>);</span></div></div><div><div><div>80</div></div><div><span><span>      </span></span><span>} </span><span>catch</span><span> (</span><span>err</span><span>) {</span></div></div><div><div><div>81</div></div><div><span>        </span><span>return</span><span> </span><span>new</span><span> </span><span>Response</span><span>(</span></div></div><div><div><div>82</div></div><div><span>          </span><span>JSON</span><span>.</span><span>stringify</span><span>({ </span><span>error</span><span>:</span><span><span> </span><span>err</span><span>.</span></span><span>message</span><span> }),</span></div></div><div><div><div>83</div></div><div><span><span>          </span></span><span>{ </span><span>status</span><span>:</span><span> </span><span>500</span><span>, </span><span>headers</span><span>:</span><span> { </span><span>'Content-Type'</span><span>:</span><span> </span><span>'application/json'</span><span>, </span><span>...</span><span>corsHeaders</span><span> } }</span></div></div><div><div><div>84</div></div><div><span><span>        </span></span><span>);</span></div></div><div><div><div>85</div></div><div><span><span>      </span></span><span>}</span></div></div><div><div><div>86</div></div><div><span><span>    </span></span><span>}</span></div></div><div><div><div>87</div></div><div>
</div></div><div><div><div>88</div></div><div><span>    </span><span>// -----------------------------</span></div></div><div><div><div>89</div></div><div><span>    </span><span>// ✅ GET 图片（Referer 防盗链）</span></div></div><div><div><div>90</div></div><div><span>    </span><span>// -----------------------------</span></div></div><div><div><div>91</div></div><div><span>    </span><span>if</span><span><span> (</span><span>request</span><span>.</span></span><span>method</span><span> </span><span>===</span><span> </span><span>'GET'</span><span>) {</span></div></div><div><div><div>92</div></div><div><span>      </span><span>let</span><span> </span><span>allowed</span><span> </span><span>=</span><span> </span><span>false</span><span>;</span></div></div><div><div><div>93</div></div><div>
</div></div><div><div><div>94</div></div><div><span>      </span><span>// 如果路径以 /base/ 开头，直接允许，不检查 Referer</span></div></div><div><div><div>95</div></div><div><span>      </span><span>if</span><span><span> (</span><span>pathname</span><span>.</span></span><span>startsWith</span><span>(</span><span>'/base/'</span><span>)) {</span></div></div><div><div><div>96</div></div><div><span>        </span><span>allowed</span><span> </span><span>=</span><span> </span><span>true</span><span>;</span></div></div><div><div><div>97</div></div><div><span><span>      </span></span><span>}</span></div></div><div><div><div>98</div></div><div><span>      </span><span>else</span><span> {</span></div></div><div><div><div>99</div></div><div><span>        </span><span>// 否则，执行常规的 Referer 检查</span></div></div><div><div><div>100</div></div><div><span>        </span><span>for</span><span> (</span><span>const</span><span> </span><span>domain</span><span> </span><span>of</span><span> </span><span>allowedDomains</span><span>) {</span></div></div><div><div><div>101</div></div><div><span>          </span><span>if</span><span><span> (</span><span>referer</span><span>.</span></span><span>includes</span><span>(</span><span>domain</span><span>)) {</span></div></div><div><div><div>102</div></div><div><span>            </span><span>allowed</span><span> </span><span>=</span><span> </span><span>true</span><span>;</span></div></div><div><div><div>103</div></div><div><span>            </span><span>break</span><span>;</span></div></div><div><div><div>104</div></div><div><span><span>          </span></span><span>}</span></div></div><div><div><div>105</div></div><div><span><span>        </span></span><span>}</span></div></div><div><div><div>106</div></div><div><span><span>      </span></span><span>}</span></div></div><div><div><div>107</div></div><div>
</div></div><div><div><div>108</div></div><div>
</div></div><div><div><div>109</div></div><div><span>      </span><span>if</span><span> (</span><span>!</span><span>allowed</span><span>) {</span></div></div><div><div><div>110</div></div><div><span>        </span><span>// 如果想允许直接浏览器访问（无 Referer），可以把下面的注释打开</span></div></div><div><div><div>111</div></div><div><span>        </span><span>// if (!referer) allowed = true;</span></div></div><div><div><div>112</div></div><div>
</div></div><div><div><div>113</div></div><div><span>        </span><span>if</span><span> (</span><span>!</span><span>allowed</span><span>) {</span></div></div><div><div><div>114</div></div><div><span>          </span><span>const</span><span> </span><span>warning</span><span> </span><span>=</span><span> </span><span>await</span><span><span> </span><span>env</span><span>.</span></span><span>MY_BUCKET</span><span>.</span><span>get</span><span>(</span><span>'hotlink-warning.png'</span><span>);</span></div></div><div><div><div>115</div></div><div><span>          </span><span>if</span><span> (</span><span>warning</span><span>) {</span></div></div><div><div><div>116</div></div><div><span>            </span><span>return</span><span> </span><span>new</span><span> </span><span>Response</span><span><span>(</span><span>warning</span><span>.</span></span><span>body</span><span>, {</span></div></div><div><div><div>117</div></div><div><span>              </span><span>status</span><span>:</span><span> </span><span>200</span><span>,</span></div></div><div><div><div>118</div></div><div><span>              </span><span>headers</span><span>:</span><span> {</span></div></div><div><div><div>119</div></div><div><span>                </span><span>'Content-Type'</span><span>:</span><span> </span><span>'image/png'</span><span>,</span></div></div><div><div><div>120</div></div><div><span>                </span><span>'Cache-Control'</span><span>:</span><span> </span><span>'public, max-age=3600'</span><span>,</span></div></div><div><div><div>121</div></div><div><span>                </span><span>'X-Robots-Tag'</span><span>:</span><span> </span><span>'noindex'</span></div></div><div><div><div>122</div></div><div><span><span>              </span></span><span>}</span></div></div><div><div><div>123</div></div><div><span><span>            </span></span><span>});</span></div></div><div><div><div>124</div></div><div><span><span>          </span></span><span>}</span></div></div><div><div><div>125</div></div><div><span>          </span><span>return</span><span> </span><span>new</span><span> </span><span>Response</span><span>(</span><span>'Hotlinking not allowed'</span><span>, {</span></div></div><div><div><div>126</div></div><div><span>            </span><span>status</span><span>:</span><span> </span><span>403</span><span>,</span></div></div><div><div><div>127</div></div><div><span>            </span><span>headers</span><span>:</span><span> { </span><span>'Content-Type'</span><span>:</span><span> </span><span>'text/plain'</span><span>, </span><span>'X-Robots-Tag'</span><span>:</span><span> </span><span>'noindex'</span><span> }</span></div></div><div><div><div>128</div></div><div><span><span>          </span></span><span>});</span></div></div><div><div><div>129</div></div><div><span><span>        </span></span><span>}</span></div></div><div><div><div>130</div></div><div><span><span>      </span></span><span>}</span></div></div><div><div><div>131</div></div><div>
</div></div><div><div><div>132</div></div><div><span>      </span><span>const</span><span> </span><span>key</span><span> </span><span>=</span><span><span> </span><span>pathname</span><span>.</span></span><span>slice</span><span>(</span><span>1</span><span>);</span></div></div><div><div><div>133</div></div><div><span>      </span><span>if</span><span> (</span><span>!</span><span>key</span><span>) </span><span>return</span><span> </span><span>new</span><span> </span><span>Response</span><span>(</span><span>'Bad Request'</span><span>, { </span><span>status</span><span>:</span><span> </span><span>400</span><span> });</span></div></div><div><div><div>134</div></div><div>
</div></div><div><div><div>135</div></div><div><span>      </span><span>const</span><span> </span><span>object</span><span> </span><span>=</span><span> </span><span>await</span><span><span> </span><span>env</span><span>.</span></span><span>MY_BUCKET</span><span>.</span><span>get</span><span>(</span><span>key</span><span>);</span></div></div><div><div><div>136</div></div><div><span>      </span><span>if</span><span> (</span><span>!</span><span>object</span><span>) </span><span>return</span><span> </span><span>new</span><span> </span><span>Response</span><span>(</span><span>'Not Found'</span><span>, { </span><span>status</span><span>:</span><span> </span><span>404</span><span> });</span></div></div><div><div><div>137</div></div><div>
</div></div><div><div><div>138</div></div><div><span>      </span><span>const</span><span> </span><span>headers</span><span> </span><span>=</span><span> </span><span>new</span><span> </span><span>Headers</span><span>();</span></div></div><div><div><div>139</div></div><div><span><span>      </span></span><span>object</span><span>.</span><span>writeHttpMetadata</span><span>(</span><span>headers</span><span>);</span></div></div><div><div><div>140</div></div><div><span><span>      </span></span><span>headers</span><span>.</span><span>set</span><span>(</span><span>'etag'</span><span><span>, </span><span>object</span><span>.</span></span><span>httpEtag</span><span>);</span></div></div><div><div><div>141</div></div><div><span><span>      </span></span><span>headers</span><span>.</span><span>set</span><span>(</span><span>'Cache-Control'</span><span>, </span><span>'public, max-age=31536000, immutable'</span><span>);</span></div></div><div><div><div>142</div></div><div><span><span>      </span></span><span>headers</span><span>.</span><span>set</span><span>(</span><span>'Access-Control-Allow-Origin'</span><span>, </span><span>'*'</span><span>);</span></div></div><div><div><div>143</div></div><div>
</div></div><div><div><div>144</div></div><div><span>      </span><span>return</span><span> </span><span>new</span><span> </span><span>Response</span><span><span>(</span><span>object</span><span>.</span></span><span>body</span><span>, { </span><span>status</span><span>:</span><span> </span><span>200</span><span>, </span><span>headers</span><span> });</span></div></div><div><div><div>145</div></div><div><span><span>    </span></span><span>}</span></div></div><div><div><div>146</div></div><div>
</div></div><div><div><div>147</div></div><div><span>    </span><span>return</span><span> </span><span>new</span><span> </span><span>Response</span><span>(</span><span>'Method Not Allowed'</span><span>, { </span><span>status</span><span>:</span><span> </span><span>405</span><span> });</span></div></div><div><div><div>148</div></div><div><span><span>  </span></span><span>}</span></div></div><div><div><div>149</div></div><div><span>};</span></div></div></code></pre><div><div></div><div></div></div></figure><div></div></div><span>展开</span><span>收起</span></div></div><p>接下来绑定储存桶，添加绑定。选择R2 储存桶。变量名称必须填 MY_BUCKET 。R2 储存桶选imgs 。</p><p>下一步绑定域名，设置 &gt; 域和路由，你需要添加一个自定义域，方便在文章中引入。</p><p>转到设置 &gt; 变量和机密，你还需要加入一个名为UPLOAD_TOKEN的环境变量</p>

<table><thead><tr><th>变量名</th><th><code>UPLOAD_TOKEN</code></th></tr></thead><tbody><tr><td>值</td><td>自行设置，复杂一些，如果不使用Waline图片上传，可以忽略这一步。</td></tr></tbody></table><section><h3>配置PicGo或PicList<a href="#配置picgo或piclist"><span>#</span></a></h3><p>在这两款软件中，驱动选S3对象存储就可以。</p><p>CF主页 &gt; 计算和AI &gt; Workers 和 Pages &gt; 概述</p><p>在Account detials 中 API Token栏右侧的 Manage按钮，创建 Account API 令牌，名称自拟，权限是对象读和写，其他保持默认。</p><p>复制Access Key ID 和 Secret Access Key（只显示一次），桶名就填imgs, 设定自定义节点，就填创建令牌时显示的S3 API。概述页最底部显示的内容。你还可以自定义上传路径。其他配置保持不变。</p><p>本文描述相对简洁，如果有疑问可以去看原文，如果问题是由本项目导致的，请提交issues。</p></section></section>
<section><h2>在Waline 评论系统中启用图片上传<a href="#在waline-评论系统中启用图片上传"><span>#</span></a></h2><p>在./src/config.ts 配置文件的末尾，是有关waline评论系统的配置项，其中新增了三个配置项：是否启用图片上传功能、Waline 图片上传地址和Waline 上传 Token。这三个选项必须同时设置。</p><p>Waline评论上传的图片默认统一保存在comment文件夹下。</p><p>根据Worker的默认配置，图片上传地址应为<a href="https://img.example.com/upload" target="_blank">https://img.example.com/upload</a> 注意不要填错了。至此，配置完成，你在写文章时，通过PicGo/PicList上传至数据库，然后在Markdown文件中按照和以前一样的格式从图床引用就好。</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>// 带描述的图片</span></div></div><div><div><div>2</div></div><div><span>![</span><span>lighthouse</span><span>]</span><span><span>(</span><span>https://img.refact.cc/lighthouse.png</span><span>)</span></span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>更多用法，请到 <a href="https://refact-x-template-git-galaxy-msrefs-projects.vercel.app/posts/markdown-extension-syntax" target="_blank">https://refact-x-template-git-galaxy-msrefs-projects.vercel.app/posts/markdown-extension-syntax</a> 学习。</p></section>
<section><h2>外部访问提示<a href="#外部访问提示"><span>#</span></a></h2><p>你可以在存储桶的根目录放置一张提示图,命名为<code>hotlink-warning.png</code>，大概就是提示禁止从外部访问，不放也会有相应的文字提示。</p><p>12月26日 起稿</p><p>12月30日 初稿</p><p>1月4日 第一次修改</p><p>2月12日 第二次修改</p><p>4月10日 第三次修改</p></section>]]></content>
    </entry>
    <entry>
      <id>https://www.refact.cc/posts/2025-year-summary/</id>
      <title type="text">2025年度总结</title>
      <published>2025-12-28T00:00:00.000Z</published>
      <updated>2025-12-28T00:00:00.000Z</updated>
      <author><name>Refac7</name></author>
      <link rel="alternate" href="https://www.refact.cc/posts/2025-year-summary/"/>
      <summary type="text">本文从多方面总结了今年的经历，大学生活，和自己的感受。</summary>
      <content type="html"><![CDATA[<p>这是我第一次写年度总结。</p>
<p>总的来说，今年是特殊的一年，而这个博客也是从今年7月26日才开始运行的。而也是从今年年初，我才开始系统性地的学习前端技术，现在开始自称开发者确实有点名不副实了，但我还是会不断学习下去的。</p>
<section><h2>时间线<a href="#时间线"><span>#</span></a></h2><section><h3>今年二月中旬-偶遇NotionNext<a href="#今年二月中旬-偶遇notionnext"><span>#</span></a></h3><p>偶然间发现 tangly1024 大佬开发的 NotionNext 项目，核心是”零基础建站”，我对此深感兴趣。于是便按照教程一步步地创建了第一个博客，主要就是Minecraft的内容，没有什么有价值的产出。</p><p>而我也借此机会学习了一部分tailwind CSS样式修改，即现在的Reborn仓库。</p></section><section><h3>今年四月初-第一次独立项目<a href="#今年四月初-第一次独立项目"><span>#</span></a></h3><p>我创建了一个名为MSW(Minecraft status widget)的项目，使用的是Next.js框架，虽说都是AIGC，不过我也认识了Next.js的基本结构，这比看文档有趣多了。</p></section><section><h3>今年五月中旬-开始使用Vitepress<a href="#今年五月中旬-开始使用vitepress"><span>#</span></a></h3><p>这是我第一次构建一个静态站点。从一个模版开始，也渐渐熟悉了基本的CRUD操作，不过我还有很多东西需要学习。</p></section><section><h3>今年六月至七月-了解xLog<a href="#今年六月至七月-了解xlog"><span>#</span></a></h3><p>确实很方便，但那并不自由。</p></section><section><h3>今年七月中旬-第二次建站<a href="#今年七月中旬-第二次建站"><span>#</span></a></h3><p>高考后在家昏迷了一个星期，后续到这个时间点之前，我都在对之前写的项目进行维护，写点文档什么的（虽然没人看）。</p><p>我发现Astro网站上有很多博客模版可用，于是就选了一个<a href="https://github.com/kieranwv/astro-theme-vitesse" target="_blank">https://github.com/kieranwv/astro-theme-vitesse</a> 作为主题，但后续我发现这个主题扩展性有限，还有一些不可名状的显示Bug，虽然做了很多修改，但感觉效果还是差强人意，于是就渐渐不再维护这个项目了。</p></section><section><h3>今年7月26日至今-第三次建站<a href="#今年7月26日至今-第三次建站"><span>#</span></a></h3><p>如果你能看到这篇文章，那你所见的就是我称作”RefactX Project”的项目，它是基于<a href="https://github.com/Dnzzk2/Litos" target="_blank">https://github.com/Dnzzk2/Litos</a> 修改得来的。有关这个项目的更多信息，你可以去看第一篇文章。</p></section><section><h3>总结<a href="#总结"><span>#</span></a></h3><p>总的来说，我将大部分时间花在了这个项目上，以后也会继续边学习边维护下去。因为迁移的成本实在是太高了。</p></section></section>
<section><h2>大学生活<a href="#大学生活"><span>#</span></a></h2><p>首先就是军训，其实有关这部分的内容我在十月初就已经写出来了，但是一直没有发出来。</p><div><div><div></div><div>Note</div></div><div><p>我并不是有意放大学校的问题所在，更多的是对事实的阐述和揭露那些不合理的制度。</p></div></div><section><h3>军训<a href="#军训"><span>#</span></a></h3><p>军训是对人身体和精神上的双重折磨，给人更多的是一种无力感。</p><p>军训的核心就是<strong>服从命令</strong>，这四个字是贯穿了整个20天军训的始终的。同时又伴有一些完整的，有计划的体罚制度，让你不得不去服从他人的命令。</p><p>早上六点半上训，这意味着需要六点起床，实际上我每天很少睡够五个小时。一天总共训练九个小时左右，反正就是很忙，就是不知道在忙些什么。</p><p></p><figure><img src="https://img.refact.cc/8-1.jpeg" alt="军训" /><figcaption>军训</figcaption></figure><p></p></section><section><h3>反抗？<a href="#反抗"><span>#</span></a></h3><p>这就不得不提到这个墨守成规的连坐制度了，不管你是不是故意的，他们都会把个人层面的事上升到群体层面，连带着你们的同学一起惩罚。</p><p>这种制度不仅仅激化了学生与教官的矛盾，也激化了学生之间的矛盾，是极不利于团结的，处罚后的”有效”现象，更大程度上是出于对处罚的畏惧罢了。</p><p>就在这种制度之下，队伍里当然会出现不和谐的声音，如果这种声音被教官听到了，那么迎来的将是更严重的惩罚，最后学生的身体会受到损害，而这就是反抗的后果。</p><p>我真的很累很累，这是军训给我留下的唯一的感受。</p><p></p><figure><img src="https://img.refact.cc/8-2.jpeg" alt="军训" /><figcaption>军训</figcaption></figure><p></p></section></section>
<section><h2>欺骗<a href="#欺骗"><span>#</span></a></h2><p>河南农村的小孩从小就一直被灌输”上了大学就轻松了”的思想，于是和一百多万考生厮杀，不足一半的学生踏入了梦寐以求的本科院校。然而到了大学之后一切的幻想都化作泡影。</p><section><h3>环境<a href="#环境"><span>#</span></a></h3><p>而这种感受其实在开学那天就发现了。我的宿舍是混合的，并且不是同一专业，甚至不是同一年级的，他们是土木工程的二年级学生。这也间接导致了我到现在还认不出我们班的哪怕一个学生，我也因此总是一个人行动。</p><p>我是在系统一开放就选了的，然而我当时可选的四个宿舍全是混合的，我没得选。</p><p>我们宿舍看上去也是很有年代感，包浆级别的，我擦了十几张湿巾还是黑的。没有独立卫浴，只有走廊尽头的洗漱间，包括澡堂。还有很多人根本就不冲厕所，恶心别人。</p><p>舍友不喜欢戴耳机，总是外放，说话很大声。打游戏时还会大吼大叫，降噪耳机根本没用。每到这时，深深的无力感再次席卷而来。</p></section><section><h3>教学<a href="#教学"><span>#</span></a></h3><p>根据教学内容和考核方式，课程被学生选出了一类”水课”，也就是相对不那么重要的课。在这类课程中，学生大多不愿意认真听讲，在使用手机、跑神、或是直接睡觉，于是老师也不再认真讲课了，都是为了完成既定的教学任务罢了。</p></section><section><h3>数学<a href="#数学"><span>#</span></a></h3><p>如果你看过我之前的文章，就知道数学是我的绝对劣势，所以说我现在上数学课总是在一个小本上写写画画。</p><p></p><figure><img src="https://img.refact.cc/8-4.jpeg" alt="数学" /><figcaption>数学</figcaption></figure><p></p></section><section><h3>排球课<a href="#排球课"><span>#</span></a></h3><p>这学期的体育大概率也是要挂科了。我选的是排球，当时选课系统上也确实没什么可选了。直到练了几天之后发现还是一个都垫不出去，于是我的搭档也就被我赶走去找别人练了。前些天测试，因为我没有搭档，所以自然没有成绩。1000米那天我发烧没去，所以也没有成绩。至于校园跑，我们学校规定是140公里，当然大部分学生是靠骑电动车和自行车刷的。每个人都心知肚明，我写这篇文章的时候，我的校园跑数据是六公里。</p><p></p><figure><img src="https://img.refact.cc/8-3.jpeg" alt="排球课" /><figcaption>排球课</figcaption></figure><p></p></section></section>
<section><h2>以后呢？<a href="#以后呢"><span>#</span></a></h2><p>直到我写这篇文章的时候，我仍不认识宿舍内的哪怕任何一个同学。</p><p>大概率是不会有变化了，因为我已经开始厌恶这个地方了。</p><p>同时我也是一个不喜欢受规划所束缚的人，以后的事情现在暂时不做考量。</p><p>2025年12月25日起稿</p><p>2025年12月28日初稿</p></section>]]></content>
    </entry>
    <entry>
      <id>https://www.refact.cc/posts/campus-network/</id>
      <title type="text">校园网有线多设备上网方案</title>
      <published>2025-11-28T00:00:00.000Z</published>
      <updated>2025-11-28T00:00:00.000Z</updated>
      <author><name>Refac7</name></author>
      <link rel="alternate" href="https://www.refact.cc/posts/campus-network/"/>
      <summary type="text">本文重点指出了河南某高校在校园网连接数上卡学生脖子的解决方案。</summary>
      <content type="html"><![CDATA[<section><h2>概述<a href="#概述"><span>#</span></a></h2><p>我有很多台网络设备，手机、电脑、打印机、Debian服务器、其他Mesh设备等等需要联网使用。但是无线校园网仅支持一台设备在线，包括我在<code>pnpm dev --host</code>时也不能通过内网连接的手机实时调试网页在移动端的显示效果。于是我必须得想办法解决这个瓶颈。在网络上检索相关的教程，但其有效内容甚少，并无一个成体系的解决方案，甚至上升到需要刷openwrt或者老毛子固件，或者去购买专门的的路由器（非常贵）。最后我还是尝试自己解决。</p><section><h3>无线校园网<a href="#无线校园网"><span>#</span></a></h3><p>我使用的是电信校园卡附带的校园网服务包。其<strong>无线网络</strong>通过内网Web页面认证学号和密码（DHCP + Web Portal）。经过测试，这种方案仅支持一台设备在线（MAC地址克隆无效）。每次连接都需要输入帐密认证身份。</p></section><section><h3>有线接入点（AP）<a href="#有线接入点ap"><span>#</span></a></h3><p>我们的接入点是在宿舍门的上方，有五个RJ45的网口。花费8元购置一根十米超五类网线，完成物理布线。</p></section></section>
<section><h2>尝试<a href="#尝试"><span>#</span></a></h2><section><h3>直连电脑<a href="#直连电脑"><span>#</span></a></h3><section><h4>DHCP直连<a href="#dhcp直连"><span>#</span></a></h4><p>无法正常分配IP地址，说明AP并不是通过DHCP + Web Portal连接的。</p></section></section><section><h3>连接路由器<a href="#连接路由器"><span>#</span></a></h3><p>这个路由器是我在并少少买的28块的二手中兴Z500。</p><section><h4>桥接(LAN)<a href="#桥接lan"><span>#</span></a></h4><p>将路由器设置为桥接/AP模式后，电脑连接到路由器LAN口。电脑依然无法从校园网DHCP服务器获取到IP地址。这证明网络接入层面（而非仅Web认证层面）就对设备或拓扑进行了限制（很可能基于接入端口的MAC地址绑定）。退一步说，即使桥接成功，该模式也无法实现我所需的”单一入口、多设备共享”的NAT转换功能。</p></section><section><h4>DHCP(WAN&amp;LAN)<a href="#dhcpwanlan"><span>#</span></a></h4><p>无法识别WAN口和LAN口网线。</p><p>后来从运营商处了解到有线网络并不是通过DHCP + Web Portal认证的，不管是电脑还是路由器，都需要（学号）+@telecom这个账号PPPoE拨号上网。</p></section><section><h4>PPPoE(WAN)<a href="#pppoewan"><span>#</span></a></h4><p>ISP连接超时。（未知错误）或认证失败。我去运营商处咨询后得到正确的帐密登陆。成功连接。</p></section></section><section><h3>新问题<a href="#新问题"><span>#</span></a></h3><p>连接速度慢的离谱，通过日志可以看出是卡在分配IP地址这一步。查阅资料发现我在某个教程的指引下（似乎是桥接方法那里）关闭了DHCP服务器，当DHCP过程失败时，操作系统在等待DHCP响应超时后，会<strong>自动启用一个备用机制</strong>，即给自己分配一个 <strong>“链路本地地址”</strong>（这个地址无法上网）。重新启用DHCP服务器即可。</p></section></section>
<section><h2>结语<a href="#结语"><span>#</span></a></h2><p>我终于摆脱了每次打开电脑输入密码的时代。</p><p></p><figure><img src="https://img.refact.cc/post-6-1.png" alt="多设备连接" /><figcaption>多设备连接</figcaption></figure><p></p><p></p><figure><img src="https://img.refact.cc/post-6-2.jpg" alt="5GHz频段测速" /><figcaption>5GHz频段测速</figcaption></figure><p></p><p>我不理解学校的这种行为，我们学校的可分配网段应该是10.209.0.0/16.也就是说足足可分配65025个IP地址，即使全校学生同时上网也是完全够用的。我们学校对学生的限制已经够多了，所以这次我必须打破这个规则。</p><p><strong>总936词</strong></p><p>2025年11月28日 初稿</p><p>在 Typora | macOS Tahoe 26.1 编辑</p></section>]]></content>
    </entry>
    <entry>
      <id>https://www.refact.cc/posts/waline-comments/</id>
      <title type="text">Waline评论系统的部署与配置</title>
      <published>2025-09-26T00:00:00.000Z</published>
      <updated>2025-09-26T00:00:00.000Z</updated>
      <author><name>Refac7</name></author>
      <link rel="alternate" href="https://www.refact.cc/posts/waline-comments/"/>
      <summary type="text">本文重点指出了在部署 Waline 评论系统时可能遇到的问题。</summary>
      <content type="html"><![CDATA[<p>本文使用的是前后端分离的方式，当然可以使用完全嵌入的方式，不过我没成功。</p>
<section><h2>后端配置<a href="#后端配置"><span>#</span></a></h2><p>推荐使用 vercel 无服务器部署，使用<code>@waline/vercel</code> 包进行部署</p><p><a href="https://vercel.com/new/clone?repository-url=https%3A%2F%2Fgithub.com%2Fwalinejs%2Fwaline%2Ftree%2Fmain%2Fexample" target="_blank">https://vercel.com/new/clone?repository-url=https%3A%2F%2Fgithub.com%2Fwalinejs%2Fwaline%2Ftree%2Fmain%2Fexample</a></p><p>点击以上链接跳转到 vercel 进行部署，如果未注册，可使用GitHub账户注册。</p><section><h3>添加数据库环境变量<a href="#添加数据库环境变量"><span>#</span></a></h3><p>除了官方推荐的 Leancloud 之外，也支持 MySQL, PostgreSQL, SQLite 以及 MongoDB。</p><p>相关环境变量键值可以在官方文档查看 <a href="https://waline.js.org/guide/database.html" target="_blank">https://waline.js.org/guide/database.html</a></p><div><div><div></div><div>Warning</div></div><div><p>官方提示 MySQL, PostgreSQL, SQLite 数据库要提前创建表结构</p></div></div><p><a href="https://github.com/walinejs/waline/blob/main/assets/waline.sqlite" target="_blank">waline.sqlite</a></p></section><section><h3>部署和后续工作<a href="#部署和后续工作"><span>#</span></a></h3><p>通过 vercel 部署完成后，需要绑定自己的域名使用，因为 vercel.app 在大陆不可访问。在此之后，你才可以直接访问，并且看到评论窗口，然后你可以直接发送一个实例评论，然后在数据库查看是否写入键值。</p></section></section>
<section><h2>前端配置<a href="#前端配置"><span>#</span></a></h2><p>对于未集成评论系统的静态博客系统，我们一般要先创建一个 泛用组件，然后在博客页模版相应位置引入即可。</p><p>下面我给出一个 Astro静态博客的示例组件</p><div><div><div><figure><figcaption><span>/src/components/posts/base/Comments.astro</span><span>展开</span><span>收起</span></figcaption><pre><code><div><div><div>1</div></div><div><span>&lt;</span><span>div</span><span> </span><span>id</span><span>=</span><span>"waline"</span><span>&gt;&lt;/</span><span>div</span><span>&gt;</span></div></div><div><div><div>2</div></div><div>
</div></div><div><div><div>3</div></div><div><span>&lt;</span><span>link</span></div></div><div><div><div>4</div></div><div><span>  </span><span>rel</span><span>=</span><span>"stylesheet"</span></div></div><div><div><div>5</div></div><div><span>  </span><span>href</span><span>=</span><span>"https://unpkg.com/@waline/client@v3/dist/waline.css"</span></div></div><div><div><div>6</div></div><div><span>/&gt;</span></div></div><div><div><div>7</div></div><div>
</div></div><div><div><div>8</div></div><div><span>&lt;</span><span>style</span><span> </span><span>is:global</span><span>&gt;</span></div></div><div><div><div>9</div></div><div><span>  </span><span>/* 去掉 Waline 所有圆角 */</span></div></div><div><div><div>10</div></div><div><span>  </span><span>#waline</span><span> </span><span>.wl-editor</span><span>,</span></div></div><div><div><div>11</div></div><div><span>  </span><span>#waline</span><span> </span><span>.wl-input</span><span>,</span></div></div><div><div><div>12</div></div><div><span>  </span><span>#waline</span><span> </span><span>span</span><span>.wl-badge</span><span>,</span></div></div><div><div><div>13</div></div><div><span>  </span><span>#waline</span><span> </span><span>.wl-btn</span><span>,</span></div></div><div><div><div>14</div></div><div><span>  </span><span>#waline</span><span> </span><span>.wl-comment</span><span>,</span></div></div><div><div><div>15</div></div><div><span>  </span><span>#waline</span><span> </span><span>.wl-card</span><span>,</span></div></div><div><div><div>16</div></div><div><span>  </span><span>#waline</span><span> </span><span>.wl-panel</span><span>,</span></div></div><div><div><div>17</div></div><div><span>  </span><span>#waline</span><span> </span><span>.wl-header</span><span>,</span></div></div><div><div><div>18</div></div><div><span>  </span><span>#waline</span><span> </span><span>.wl-gif-popup</span><span>,</span></div></div><div><div><div>19</div></div><div><span>  </span><span>#waline</span><span> </span><span>.wl-emoji-popup</span><span>,</span></div></div><div><div><div>20</div></div><div><span>  </span><span>#waline</span><span> </span><span>.wl-footer</span><span> {</span></div></div><div><div><div>21</div></div><div><span><span>    </span></span><span>border-radius: </span><span>0</span><span> </span><span>!important</span><span>;</span></div></div><div><div><div>22</div></div><div><span><span>    </span></span><span>font-family: </span><span>'ShangguSansSC Variable'</span><span>, </span><span>system-ui</span><span>, </span><span>-apple-system</span><span>, </span><span>sans-serif</span><span> </span><span>!important</span><span>;</span></div></div><div><div><div>23</div></div><div><span><span>  </span></span><span>}</span></div></div><div><div><div>24</div></div><div>
</div></div><div><div><div>25</div></div><div><span>  </span><span>.wl-content</span><span>,</span></div></div><div><div><div>26</div></div><div><span>  </span><span>.wl-content</span><span> </span><span>*</span><span> {</span></div></div><div><div><div>27</div></div><div><span><span>    </span></span><span>white-space: </span><span>normal</span><span> </span><span>!important</span><span>;</span></div></div><div><div><div>28</div></div><div><span><span>    </span></span><span>word-break: </span><span>break-word</span><span> </span><span>!important</span><span>;</span></div></div><div><div><div>29</div></div><div><span><span>    </span></span><span>overflow-wrap: anywhere </span><span>!important</span><span>;</span></div></div><div><div><div>30</div></div><div><span><span>  </span></span><span>}</span></div></div><div><div><div>31</div></div><div>
</div></div><div><div><div>32</div></div><div><span>  </span><span>/* 浅色模式下的颜色设置 */</span></div></div><div><div><div>33</div></div><div><span>  </span><span>#waline</span><span> {</span></div></div><div><div><div>34</div></div><div><span>    </span><span>--waline-theme-color</span><span>: </span><span>#</span><span>000000</span><span> </span><span>!important</span><span>;</span></div></div><div><div><div>35</div></div><div><span>    </span><span>--waline-active-color</span><span>: </span><span>#</span><span>dc2626</span><span> </span><span>!important</span><span>;</span></div></div><div><div><div>36</div></div><div><span>    </span><span>/* 设置字体 */</span></div></div><div><div><div>37</div></div><div><span>    </span><span>--waline-font-size</span><span>: </span><span><span>1</span><span>rem</span></span><span> </span><span>!important</span><span>;</span></div></div><div><div><div>38</div></div><div><span>    </span><span>--waline-font-family</span><span>: </span><span>'Onest Variable'</span><span>, </span><span>'Geist Variable'</span><span>, </span><span>'ShangguSansSC Variable'</span><span>, </span><span>system-ui</span><span>, </span><span>-apple-system</span><span>, </span><span>sans-serif</span><span> </span><span>!important</span><span>;</span></div></div><div><div><div>39</div></div><div><span><span>  </span></span><span>}</span></div></div><div><div><div>40</div></div><div>
</div></div><div><div><div>41</div></div><div><span>  </span><span>/* 深色模式下的颜色和背景设置 */</span></div></div><div><div><div>42</div></div><div><span>  </span><span>html</span><span>.dark</span><span> </span><span>#waline</span><span> {</span></div></div><div><div><div>43</div></div><div><span>    </span><span>--waline-theme-color</span><span>: </span><span>#</span><span>ffffff</span><span> </span><span>!important</span><span>;</span></div></div><div><div><div>44</div></div><div><span>    </span><span>--waline-active-color</span><span>: </span><span>#</span><span>dc2626</span><span> </span><span>!important</span><span>;</span></div></div><div><div><div>45</div></div><div><span><span>  </span></span><span>}</span></div></div><div><div><div>46</div></div><div>
</div></div><div><div><div>47</div></div><div><span>  </span><span>html</span><span>.dark</span><span> </span><span>#waline</span><span> </span><span>.wl-panel</span><span> {</span></div></div><div><div><div>48</div></div><div><span>    </span><span>--waline-bgcolor</span><span>: </span><span>#</span><span>101018</span><span> </span><span>!important</span><span>;</span></div></div><div><div><div>49</div></div><div><span><span>    </span></span><span>background-color: </span><span>#</span><span>101018</span><span> </span><span>!important</span><span>;</span></div></div><div><div><div>50</div></div><div><span><span>  </span></span><span>}</span></div></div><div><div><div>51</div></div><div>
</div></div><div><div><div>52</div></div><div><span>  </span><span>/* 禁用输入框获得焦点时的样式变化 */</span></div></div><div><div><div>53</div></div><div><span>  </span><span>#waline</span><span> </span><span><span>.wl-editor</span><span>:focus</span></span><span>,</span></div></div><div><div><div>54</div></div><div><span>  </span><span>#waline</span><span> </span><span><span>.wl-editor</span><span>:focus-within</span></span><span>,</span></div></div><div><div><div>55</div></div><div><span>  </span><span>#waline</span><span> </span><span><span>.wl-input</span><span>:focus</span></span><span> {</span></div></div><div><div><div>56</div></div><div><span><span>    </span></span><span>background-color: </span><span>var</span><span>(</span><span>--waline-bgcolor</span><span>) </span><span>!important</span><span>;</span></div></div><div><div><div>57</div></div><div><span><span>    </span></span><span>box-shadow: </span><span>none</span><span> </span><span>!important</span><span>;</span></div></div><div><div><div>58</div></div><div><span><span>    </span></span><span>outline: </span><span>none</span><span> </span><span>!important</span><span>;</span></div></div><div><div><div>59</div></div><div><span><span>  </span></span><span>}</span></div></div><div><div><div>60</div></div><div><span>&lt;/</span><span>style</span><span>&gt;</span></div></div><div><div><div>61</div></div><div>
</div></div><div><div><div>62</div></div><div><span>&lt;</span><span>script</span><span> </span><span>src</span><span>=</span><span>"https://unpkg.com/@waline/client@v3/dist/waline.umd.js"</span><span> </span><span>defer</span><span>&gt;&lt;/</span><span>script</span><span>&gt;</span></div></div><div><div><div>63</div></div><div><span>&lt;</span><span>script</span><span>&gt;</span></div></div><div><div><div>64</div></div><div><span>function</span><span> </span><span>loadWaline</span><span>() {</span></div></div><div><div><div>65</div></div><div><span>  </span><span>const</span><span> </span><span>container</span><span> </span><span>=</span><span><span> </span><span>document</span><span>.</span></span><span>querySelector</span><span>(</span><span>'#waline'</span><span>);</span></div></div><div><div><div>66</div></div><div><span>  </span><span>if</span><span> (</span><span>!</span><span><span>container</span><span>) </span></span><span>return</span><span>; </span><span>// 容器不存在就不初始化</span></div></div><div><div><div>67</div></div><div>
</div></div><div><div><div>68</div></div><div><span>  </span><span>const</span><span> </span><span>w</span><span> </span><span>=</span><span><span> (</span><span>window</span><span> </span><span>as</span><span> </span></span><span>any</span><span>).</span><span>Waline</span><span>;</span></div></div><div><div><div>69</div></div><div><span>  </span><span>if</span><span> (</span><span>!</span><span><span>w</span><span>) </span></span><span>return</span><span>;</span></div></div><div><div><div>70</div></div><div>
</div></div><div><div><div>71</div></div><div><span>  </span><span>// 如果已有实例，先安全销毁</span></div></div><div><div><div>72</div></div><div><span>  </span><span>const</span><span> </span><span>oldInstance</span><span> </span><span>=</span><span><span> (</span><span>window</span><span> </span><span>as</span><span> </span></span><span>any</span><span>).</span><span>__walineInstance</span><span>;</span></div></div><div><div><div>73</div></div><div><span>  </span><span>if</span><span><span> (</span><span>oldInstance</span><span> </span></span><span>&amp;&amp;</span><span> </span><span>typeof</span><span><span> </span><span>oldInstance</span><span>.</span></span><span>destroy</span><span> </span><span>===</span><span> </span><span>'function'</span><span>) {</span></div></div><div><div><div>74</div></div><div><span>    </span><span>try</span><span> {</span></div></div><div><div><div>75</div></div><div><span>      </span><span>// 只有当挂载点还在 DOM 中时才销毁</span></div></div><div><div><div>76</div></div><div><span>      </span><span>if</span><span><span> (</span><span>container</span><span>.</span></span><span>contains</span><span><span>(</span><span>container</span><span>.</span></span><span>firstChild</span><span>) </span><span>||</span><span><span> </span><span>container</span><span>.</span></span><span>childNodes</span><span>.</span><span>length</span><span> </span><span>&gt;</span><span> </span><span>0</span><span>) {</span></div></div><div><div><div>77</div></div><div><span><span>        </span></span><span>oldInstance</span><span>.</span><span>destroy</span><span>();</span></div></div><div><div><div>78</div></div><div><span><span>      </span></span><span>}</span></div></div><div><div><div>79</div></div><div><span><span>    </span></span><span>} </span><span>catch</span><span><span> (</span><span>e</span><span>) {</span></span></div></div><div><div><div>80</div></div><div><span><span>      </span></span><span>console</span><span>.</span><span>warn</span><span>(</span><span>'Waline destroy failed:'</span><span><span>, </span><span>e</span><span>);</span></span></div></div><div><div><div>81</div></div><div><span><span>    </span></span><span>}</span></div></div><div><div><div>82</div></div><div><span><span>  </span></span><span>}</span></div></div><div><div><div>83</div></div><div>
</div></div><div><div><div>84</div></div><div><span>  </span><span>// 重新初始化并保存实例</span></div></div><div><div><div>85</div></div><div><span><span>  </span></span><span>(</span><span>window</span><span> </span><span>as</span><span> </span><span>any</span><span>).</span><span>__walineInstance</span><span> </span><span>=</span><span><span> </span><span>w</span><span>.</span></span><span>init</span><span>({</span></div></div><div><div><div>86</div></div><div><span>    </span><span>el</span><span>:</span><span><span> </span><span>container</span><span>,</span></span></div></div><div><div><div>87</div></div><div><span>    </span><span>serverURL</span><span>:</span><span> </span><span>'https://waline.refact.cc/'</span><span>,</span></div></div><div><div><div>88</div></div><div><span>    </span><span>path</span><span>:</span><span><span> </span><span>location</span><span>.</span></span><span>pathname</span><span>.</span><span>replace</span><span>(</span><span><span>/</span><span>\/</span></span><span>$</span><span>/</span><span>, </span><span>''</span><span>),</span></div></div><div><div><div>89</div></div><div><span>    </span><span>lang</span><span>:</span><span> </span><span>'zh-CN'</span><span>,</span></div></div><div><div><div>90</div></div><div><span>    </span><span>pageview</span><span>:</span><span> </span><span>true</span><span>,</span></div></div><div><div><div>91</div></div><div><span>    </span><span>dark</span><span>:</span><span> </span><span>'html.dark'</span><span>,</span></div></div><div><div><div>92</div></div><div><span>    </span><span>requiredMeta</span><span>:</span><span> [</span><span>'nick'</span><span>, </span><span>'mail'</span><span>],</span></div></div><div><div><div>93</div></div><div><span>    </span><span>locale</span><span>:</span><span> { </span><span>placeholder</span><span>:</span><span> </span><span>'写点什么吧…'</span><span> }</span></div></div><div><div><div>94</div></div><div><span><span>  </span></span><span>});</span></div></div><div><div><div>95</div></div><div><span>}</span></div></div><div><div><div>96</div></div><div>
</div></div><div><div><div>97</div></div><div>
</div></div><div><div><div>98</div></div><div><span>  </span><span>// 首次加载</span></div></div><div><div><div>99</div></div><div><span><span>  </span></span><span>window</span><span>.</span><span>addEventListener</span><span>(</span><span>'DOMContentLoaded'</span><span><span>, </span><span>loadWaline</span><span>);</span></span></div></div><div><div><div>100</div></div><div>
</div></div><div><div><div>101</div></div><div><span>  </span><span>// PJAX 完成后</span></div></div><div><div><div>102</div></div><div><span><span>  </span></span><span>document</span><span>.</span><span>addEventListener</span><span>(</span><span>'pjax:complete'</span><span><span>, </span><span>loadWaline</span><span>);</span></span></div></div><div><div><div>103</div></div><div>
</div></div><div><div><div>104</div></div><div><span>  </span><span>// Astro View Transitions 完成后</span></div></div><div><div><div>105</div></div><div><span><span>  </span></span><span>document</span><span>.</span><span>addEventListener</span><span>(</span><span>'astro:page-load'</span><span><span>, </span><span>loadWaline</span><span>);</span></span></div></div><div><div><div>106</div></div><div><span>&lt;/</span><span>script</span><span>&gt;</span></div></div></code></pre><div><div></div><div></div></div></figure><div></div></div><span>展开</span><span>收起</span></div></div><p>你需要把其中的 serverURL 类改为真实的后端地址，你可以在其中的 css样式树上更改主题色等配置来适应自己的博客系统。</p><div><div><div></div><div>Note</div></div><div><p>这里引用了两个 unpkg 的库，你可以把它下载到本地以获得更高的性能。</p></div></div><p>最后在 /src/pages/posts/[…slug].astro 的layout标签中引入即可</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>import Comments from '~/components/posts/base/Comments.astro';</span></div></div><div><div><div>2</div></div><div><span>&lt;!-- 其它导入 --&gt;</span></div></div><div><div><div>3</div></div><div><span>&lt;</span><span>layout</span><span>&gt;</span></div></div><div><div><div>4</div></div><div><span>&lt;!-- 其它代码 --&gt;</span></div></div><div><div><div>5</div></div><div><span>    </span><span>&lt;!-- 评论区 --&gt;</span></div></div><div><div><div>6</div></div><div><span><span>    </span></span><span>&lt;</span><span>div</span><span> </span><span>class</span><span>=</span><span>"px-4 sm:px-6 pb-6 sm:pb-8"</span><span>&gt;</span></div></div><div><div><div>7</div></div><div><span><span>      </span></span><span>&lt;</span><span>Comments</span><span> /&gt;</span></div></div><div><div><div>8</div></div><div><span><span>    </span></span><span>&lt;/</span><span>div</span><span>&gt;</span></div></div><div><div><div>9</div></div><div><span>&lt;/</span><span>layout</span><span>&gt;</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>这样，评论系统的最基本功能就基本成型了。</p></section>
<section><h2>启用邮件通知功能<a href="#启用邮件通知功能"><span>#</span></a></h2><p>你可以在后端仓库的 index.cjs 中直接修改来生效，或者创建 .env 文件来配置环境变量。不过我更推荐直接在添加 vercel 环境变量中添加，这样方便后期修改。</p><section><h4>你必须添加以下环境变量<a href="#你必须添加以下环境变量"><span>#</span></a></h4><ol>
<li>SMTP服务器，SMTP_SERVICE（一般支持主流的邮箱 <a href="https://github.com/nodemailer/nodemailer/blob/master/lib/well-known/services.json%EF%BC%89" target="_blank">https://github.com/nodemailer/nodemailer/blob/master/lib/well-known/services.json）</a></li>
<li>SMTP用户，SMTP_USER（一般是你的邮箱地址）</li>
<li>SMTP密码，SMTP_PASS（获取方法请自行百度）</li>
<li>网站名称，SITE_NAME（站点名称，用于通知邮件中的名字显示）</li>
<li>网站地址，SITE_URL（站点链接，用于通知邮件中的超链接）</li>
<li>你执行评论时用的邮箱，AUTHOR_EMAIL（如果是自己发的评论，不会邮件通知）</li>
<li>SMTP安全连接，SMTP_SECURE（一般情况下设置为 true 就好）</li>
</ol></section><section><h4>我推荐设置的可选环境变量<a href="#我推荐设置的可选环境变量"><span>#</span></a></h4><ol>
<li>发件人名称和发件人邮箱，SENDER_NAME 和 SENDER_EMAIL（必须同时添加，是邮件中显示的发件人名称和邮箱，邮箱的值必须和自己的邮箱地址保持完全一致）</li>
<li>邮件的样式，MAIL_TEMPLATE 和 MAIL_TEMPLATE_ADMIN（两者可以一致，用Html编写，压缩反义成单行的格式）这一步使用AI工具会快得多</li>
</ol><p>这里展开来讲， waline 和 Twikoo 的变量名不完全一样，如果直接把 Twikoo 的代码直接用于 waline ，大概会出现变量没被替换的情况。</p>

<table><thead><tr><th>变量名</th><th>Waline</th><th>Twikoo</th></tr></thead><tbody><tr><td>网站名称</td><td>{{site.name}}</td><td>${SITE_NAME}</td></tr><tr><td>评论者</td><td>{{parent.nick}}</td><td>${PARENT_NICK}</td></tr><tr><td>评论内容</td><td>{{parent.comment}}</td><td>${PARENT_COMMENT}</td></tr><tr><td>回复者</td><td>{{self.nick}}</td><td>${NICK}</td></tr><tr><td>回复内容</td><td>{{self.comment}}</td><td>${COMMENT}</td></tr><tr><td>文章链接</td><td>{{site.postUrl}}</td><td>${POST_URL}</td></tr></tbody></table><p>你需要注意以上变量名，防止出现乱码等情况。</p><p>我这边再给出一个实例代码</p><p>MAIL_TEMPLATE</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>&lt;</span><span>div</span><span> </span><span>style</span><span>=</span><span>"</span><span>font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;max-width:640px;margin:0 auto;padding:32px 24px;background:#fff;color:#000;line-height:1.6;</span><span>"</span><span>&gt;&lt;</span><span>div</span><span> </span><span>style</span><span>=</span><span>"</span><span>display:flex;align-items:center;margin-bottom:40px;</span><span>"</span><span>&gt;&lt;</span><span>img</span><span> </span><span>src</span><span>=</span><span>"https://www.refact.cc/avatar.png"</span><span> </span><span>alt</span><span>=</span><span>"{{site.name}}"</span><span> </span><span>style</span><span>=</span><span>"</span><span>width:48px;height:48px;object-fit:contain;margin-right:12px;</span><span>"</span><span>&gt;&lt;</span><span>span</span><span> </span><span>style</span><span>=</span><span>"</span><span>font-size:22px;font-weight:700;color:#000;</span><span>"</span><span>&gt;{{site.name}}&lt;/</span><span>span</span><span>&gt;&lt;/</span><span>div</span><span>&gt;&lt;</span><span>h1</span><span> </span><span>style</span><span>=</span><span>"</span><span>margin:0 0 24px 0;font-size:28px;font-weight:700;color:#000;</span><span>"</span><span>&gt;Hi &lt;</span><span>span</span><span> </span><span>style</span><span>=</span><span>"</span><span>color:#e63946;</span><span>"</span><span>&gt;{{self.nick}}&lt;/</span><span>span</span><span>&gt;&lt;/</span><span>h1</span><span>&gt;&lt;</span><span>p</span><span> </span><span>style</span><span>=</span><span>"</span><span>margin:0 0 32px 0;font-size:16px;color:#333;</span><span>"</span><span>&gt;您在 &lt;</span><span>a</span><span> </span><span>href</span><span>=</span><span>"{{site.url}}"</span><span> </span><span>style</span><span>=</span><span>"</span><span>color:#e63946;text-decoration:none;font-weight:600;</span><span>"</span><span>&gt;{{site.name}}&lt;/</span><span>a</span><span>&gt; 的评论收到了作者的回复：&lt;/</span><span>p</span><span>&gt;&lt;</span><span>div</span><span> </span><span>style</span><span>=</span><span>"</span><span>margin-bottom:28px;</span><span>"</span><span>&gt;&lt;</span><span>h2</span><span> </span><span>style</span><span>=</span><span>"</span><span>margin:0 0 8px 0;font-size:18px;font-weight:700;color:#000;</span><span>"</span><span>&gt;您发表的评论&lt;/</span><span>h2</span><span>&gt;&lt;</span><span>p</span><span> </span><span>style</span><span>=</span><span>"</span><span>margin:0;font-size:15px;color:#000;</span><span>"</span><span>&gt;{{self.comment|safe}}&lt;/</span><span>p</span><span>&gt;&lt;/</span><span>div</span><span>&gt;&lt;</span><span>div</span><span> </span><span>style</span><span>=</span><span>"</span><span>margin-bottom:40px;</span><span>"</span><span>&gt;&lt;</span><span>h2</span><span> </span><span>style</span><span>=</span><span>"</span><span>margin:0 0 8px 0;font-size:18px;font-weight:700;color:#000;</span><span>"</span><span>&gt;作者的回复&lt;/</span><span>h2</span><span>&gt;&lt;</span><span>p</span><span> </span><span>style</span><span>=</span><span>"</span><span>margin:0;font-size:15px;color:#000;</span><span>"</span><span>&gt;{{parent.comment|safe}}&lt;/</span><span>p</span><span>&gt;&lt;/</span><span>div</span><span>&gt;&lt;</span><span>a</span><span> </span><span>href</span><span>=</span><span>"{{site.postUrl}}"</span><span> </span><span>style</span><span>=</span><span>"</span><span>display:inline-block;padding:14px 28px;background:#000;color:#fff;text-decoration:none;font-weight:700;font-size:15px;</span><span>"</span><span>&gt;查看详情&lt;/</span><span>a</span><span>&gt;&lt;</span><span>hr</span><span> </span><span>style</span><span>=</span><span>"</span><span>border:none;border-top:1px solid #000;margin:48px 0 16px 0;</span><span>"</span><span> /&gt;&lt;</span><span>p</span><span> </span><span>style</span><span>=</span><span>"</span><span>font-size:12px;color:#666;margin:0;</span><span>"</span><span>&gt;此邮件由系统发送，请勿回复。&lt;/</span><span>p</span><span>&gt;&lt;/</span><span>div</span><span>&gt;</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>MAIL_TEMPLATE_ADMIN</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>&lt;</span><span>div</span><span> </span><span>style</span><span>=</span><span>"</span><span>font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;max-width:640px;margin:0 auto;padding:32px 24px;background:#fff;color:#000;line-height:1.6;</span><span>"</span><span>&gt;&lt;</span><span>div</span><span> </span><span>style</span><span>=</span><span>"</span><span>display:flex;align-items:center;margin-bottom:40px;</span><span>"</span><span>&gt;&lt;</span><span>img</span><span> </span><span>src</span><span>=</span><span>"https://www.refact.cc/avatar.png"</span><span> </span><span>alt</span><span>=</span><span>"{{site.name}}"</span><span> </span><span>style</span><span>=</span><span>"</span><span>width:48px;height:48px;object-fit:contain;margin-right:12px;</span><span>"</span><span>&gt;&lt;</span><span>span</span><span> </span><span>style</span><span>=</span><span>"</span><span>font-size:22px;font-weight:700;color:#000;</span><span>"</span><span>&gt;{{site.name}}&lt;/</span><span>span</span><span>&gt;&lt;/</span><span>div</span><span>&gt;&lt;</span><span>h1</span><span> </span><span>style</span><span>=</span><span>"</span><span>margin:0 0 24px 0;font-size:28px;font-weight:700;color:#000;</span><span>"</span><span>&gt;Hi &lt;</span><span>span</span><span> </span><span>style</span><span>=</span><span>"</span><span>color:#e63946;</span><span>"</span><span>&gt;{{parent.nick}}&lt;/</span><span>span</span><span>&gt;&lt;/</span><span>h1</span><span>&gt;&lt;</span><span>p</span><span> </span><span>style</span><span>=</span><span>"</span><span>margin:0 0 32px 0;font-size:16px;color:#333;</span><span>"</span><span>&gt;您的 &lt;</span><span>a</span><span> </span><span>href</span><span>=</span><span>"{{site.url}}"</span><span> </span><span>style</span><span>=</span><span>"</span><span>color:#e63946;text-decoration:none;font-weight:600;</span><span>"</span><span>&gt;{{site.name}}&lt;/</span><span>a</span><span>&gt; 网站收到一条新评论：&lt;/</span><span>p</span><span>&gt;&lt;</span><span>div</span><span> </span><span>style</span><span>=</span><span>"</span><span>margin-bottom:40px;</span><span>"</span><span>&gt;&lt;</span><span>h2</span><span> </span><span>style</span><span>=</span><span>"</span><span>margin:0 0 8px 0;font-size:18px;font-weight:700;color:#000;</span><span>"</span><span>&gt;您收到的评论&lt;/</span><span>h2</span><span>&gt;&lt;</span><span>p</span><span> </span><span>style</span><span>=</span><span>"</span><span>margin:0;font-size:15px;color:#000;</span><span>"</span><span>&gt;{{self.comment|safe}}&lt;/</span><span>p</span><span>&gt;&lt;/</span><span>div</span><span>&gt;&lt;</span><span>a</span><span> </span><span>href</span><span>=</span><span>"{{site.postUrl}}"</span><span> </span><span>style</span><span>=</span><span>"</span><span>display:inline-block;padding:14px 28px;background:#000;color:#fff;text-decoration:none;font-weight:700;font-size:15px;</span><span>"</span><span>&gt;查看并回复&lt;/</span><span>a</span><span>&gt;&lt;</span><span>hr</span><span> </span><span>style</span><span>=</span><span>"</span><span>border:none;border-top:1px solid #000;margin:48px 0 16px 0;</span><span>"</span><span> /&gt;&lt;</span><span>p</span><span> </span><span>style</span><span>=</span><span>"</span><span>font-size:12px;color:#666;margin:0;</span><span>"</span><span>&gt;此邮件由系统发送，请勿回复。&lt;/</span><span>p</span><span>&gt;&lt;/</span><span>div</span><span>&gt;</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>这个模版的效果如下，你必须更改其中的logo链接，或者是直接删除图标。</p><p></p><figure><img src="https://img.refact.cc/comment.png" alt="评论系统实例图" /><figcaption>评论系统实例图</figcaption></figure><p></p></section></section>
<section><h2>结尾<a href="#结尾"><span>#</span></a></h2><p>这样就完成了一个较为专业的评论系统的部署，还有很多细节，本文并未列出。你可以访问官方文档来查询更多细节。</p><section><h3>参考文献<a href="#参考文献"><span>#</span></a></h3><p><a href="https://waline.js.org/guide/get-started/" target="_blank">Waline 官方文档</a></p><p><a href="https://oragekk.me/blog/waline-mail.html" target="_blank">oragekk的博客</a></p><p><strong>共 1859 词</strong></p><p>2025年9月26日 在macOS Tahoe 26.0 ｜ Typora 编辑</p><p>2025年11月22日 第一次修改</p></section></section>]]></content>
    </entry>
</feed>
