<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/">
  <channel>
    <title>跑路的duck</title>
    <link>https://www.pldduck.com/</link>
    <description>跑路的duck的技术分享和生活随笔</description>
    <language>zh-CN</language>
    <lastBuildDate>Fri, 25 Sep 2026 00:00:00 GMT</lastBuildDate>
    <atom:link href="https://www.pldduck.com/feed.xml" rel="self" type="application/rss+xml" />
    <item>
      <title><![CDATA[iOS 27 & iPadOS 27 内置壁纸]]></title>
      <link>https://www.pldduck.com/post/ios-ipados-27-wallpaper</link>
      <guid isPermaLink="true">https://www.pldduck.com/post/ios-ipados-27-wallpaper</guid>
      <description><![CDATA[iOS 27 & iPadOS 27 内置壁纸 包含横向和纵向]]></description>
      <content:encoded><![CDATA[<article><h1>简介</h1>
<p>来自 iOS 27 和 iPadOS 27 的内置壁纸，其中 iOS 27 内含 5 种颜色的壁纸，iPadOS 27 内含 2 种颜色的壁纸 (但 iPadOS 提供横向和纵向版)</p>
<h1>预览图</h1>
<p>++(拼接压缩图 不要直接下载这里的 下载地址在最下面)++</p>
<p><img src="https://img.pldduck.com/20260925095150607.webp" alt="预览图1"><br>
<img src="https://img.pldduck.com/20260925095210084.webp" alt="预览图2">
<img src="https://img.pldduck.com/20260925095220212.webp" alt="预览图3"><br>
<img src="https://img.pldduck.com/20260925095227803.webp" alt="预览图4"></p>
<h1>下载</h1>
<p><a href="https://1drv.ms/f/c/777bf5ca0164e4ef/IgDurBx3owy9TajOt-96AyXcASuqw3lOzS_VvhLPrJnwVps?e=2kpgCm" target="_blank" rel="noopener noreferrer">OneDrive (免登录 慢)</a></p>
<p><a href="https://yun.139.com/shareweb/#/w/i/2xTrMmrGS58bu" target="_blank" rel="noopener noreferrer">移动云盘 (需登录 不限速)</a></p></article>]]></content:encoded>
      <pubDate>Fri, 25 Sep 2026 00:00:00 GMT</pubDate>
      <atom:updated>2026-09-25T00:00:00.000Z</atom:updated>
      <category domain="category">分享</category>
      <category domain="tag">iOS</category>
      <category domain="tag">iPadOS</category>
      <category domain="tag">WallPaper</category>
      <author>跑路的duck</author>
    </item>
    <item>
      <title><![CDATA[OPPO Sans 4.0 字体免费商用]]></title>
      <link>https://www.pldduck.com/post/oppo-sans-4-0</link>
      <guid isPermaLink="true">https://www.pldduck.com/post/oppo-sans-4-0</guid>
      <description><![CDATA[OPPO免费商用字体 OPPO Sans 4.0 ]]></description>
      <content:encoded><![CDATA[<article><h1>字体介绍</h1>
<p>2024年 10 月 23 日消息，OPPO 与汉仪字库联合开发的 OPPO Sans 4.0 字体于 10 月 18 日正式发布，搭载于 ColorOS 15 系统中，优化了整体笔划曲线。<br>
目前 OPPO Sans 4.0 字体已在 OPPO 开放平台提供下载，继续免费授权给全社会使用（含商用）。</p>
<h1>OPPO Sans 4.0 使用条款</h1>
<p>OPPO Sans 允许个人或企业免费使用，含商业用途，版权归 OPPO 广东移动通信有限公司所有。使用过程中，您应遵守以下条款，违者将会被追究法律责任：<br>
1、不对字体进行改编或二次开发；<br>
2、不对外售卖字体；<br>
3、不向他方提供其他下载渠道；<br>
4、不用于违法用途。</p>
<h1>字体展示</h1>
<p><img src="https://img.pldduck.com/20260925151010520.png" alt="OPPO Sans 4.0"></p>
<h1>字体下载</h1>
<p><a href="https://1drv.ms/f/c/777bf5ca0164e4ef/IgD-wRrXwTzITaML8i8Ol-oLAbtozq_aKC3wYT1RzOvyF0Y?e=xSIlb3" target="_blank" rel="noopener noreferrer">OneDrive(免登录 慢)</a></p>
<p><a href="https://yun.139.com/shareweb/#/w/i/2xTrLonrhSenu" target="_blank" rel="noopener noreferrer">移动云盘(需登录 不限速)</a></p></article>]]></content:encoded>
      <pubDate>Fri, 25 Sep 2026 00:00:00 GMT</pubDate>
      <atom:updated>2026-09-25T00:00:00.000Z</atom:updated>
      <category domain="category">分享</category>
      <category domain="tag">字体</category>
      <category domain="tag">font</category>
      <category domain="tag">OPPO</category>
      <author>跑路的duck</author>
    </item>
    <item>
      <title><![CDATA[照片拍摄地点工具 - GeoSpy]]></title>
      <link>https://www.pldduck.com/post/geospy</link>
      <guid isPermaLink="true">https://www.pldduck.com/post/geospy</guid>
      <description><![CDATA[一个利用 AI 识别照片拍摄地点的在线小工具 - GeoSpy]]></description>
      <content:encoded><![CDATA[<article><h1>网站介绍</h1>
<p>网上刷到好看的地标，不知道是哪个地方？现在可以用这款利用 AI 识别照片拍摄地点的免费在线工具 <a href="https://www.geospy.info/zh" target="_blank" rel="noopener noreferrer">GeoSpy</a> 上传图片，让 AI 分析建筑风格、地标、环境特征、标志和文化指标以识别位置。支持最大 10MB 的 JPG、PNG 和 WEBP 格式。</p>
<h1>网站展示</h1>
<p><img src="https://img.pldduck.com/20260919132536695.png" alt="网站展示"></p>
<h1>网站地址</h1>
<p><a href="https://www.geospy.info/zh" target="_blank" rel="noopener noreferrer">https://www.geospy.info/zh</a></p></article>]]></content:encoded>
      <pubDate>Sat, 19 Sep 2026 00:00:00 GMT</pubDate>
      <atom:updated>2026-09-19T00:00:00.000Z</atom:updated>
      <category domain="category">分享</category>
      <category domain="tag">网页</category>
      <category domain="tag">在线小工具</category>
      <author>跑路的duck</author>
    </item>
    <item>
      <title><![CDATA[MirrorZ - 中国教育网联合镜像站]]></title>
      <link>https://www.pldduck.com/post/mirrorz</link>
      <guid isPermaLink="true">https://www.pldduck.com/post/mirrorz</guid>
      <description><![CDATA[中国教育网联合镜像站正式上线 聚合多所高校镜像 自动访问最快节点]]></description>
      <content:encoded><![CDATA[<article><h1>总述</h1>
<p>2026 年 8 月 22 日，在南京大学 <em>e-Science</em> 中心主办的第四届开源软件论坛期间，教育网联合镜像站 <strong>MirrorZ</strong> 正式发布上线，该项目最初由清华大学开源软件镜像站在 2020 年发起，获得中国教育和科研计算机网 (CERNET) 网络中心在计算资源、域名以及网络基础设施方面的支持。</p>
<p><img src="https://img.pldduck.com/20260919131306204.png" alt="上线合影"></p>
<h1>用处</h1>
<p>长期以来国内高校和科研机构普遍搭建自己的开源软件镜像站用于加速 Linux 发行版、开发工具、编程语言生态以及各类开源项目的下载。不过各个镜像站都是独立运行的，因此在用户实际使用过程中可能会遇到多种问题：<strong>不同站点同步的软件仓库数量不同、访问速度受地域和网络环境影响、用户通常需要手动寻找合适的镜像地址。</strong></p>
<p>MirrorZ 联合镜像站本身并不存储所有开源软件镜像，而是作为调度入口将多个高校和科研机构已有的镜像资源进行整合，用户只需要访问 MirrorZ 联合镜像站地址即可获得最佳的镜像节点。</p>
<h1>使用方法</h1>
<h2>1.直接访问联合镜像站地址</h2>
<blockquote>
<p><a href="https://mirrors.cernet.edu.cn/" target="_blank" rel="noopener noreferrer">https://mirrors.cernet.edu.cn/</a></p>
</blockquote>
<p>访问该地址时联合镜像站将根据用户所在地域和网络环境选择状态最佳的镜像站，实际为用户提供下载的有可能是清华大学镜像站、上海交大镜像站或其他镜像站。需要说明的是，联合镜像站也采用反爬措施，所以有可能会触发短暂的自动验证。</p>
<h2>2.优先选择特定镜像站</h2>
<blockquote>
<p><a href="https://ustc.mirrors.cernet.edu.cn/" target="_blank" rel="noopener noreferrer">https://ustc.mirrors.cernet.edu.cn/</a></p>
</blockquote>
<p>部分用户习惯于使用特定的高校镜像站，因此用户也可以通过增加前缀的方式选择特定镜像站，例如在地址前缀增加 ustc 代表选择中国科学技术大学镜像站，实际访问时也会优先通过指定的镜像站获取资源。但请注意：如果镜像站状态异常或存在其他问题，联合镜像站也会自动选择其他可用的镜像站。</p>
<h2>3.排除特定镜像站</h2>
<blockquote>
<p><a href="https://avoidnju.mirrors.cernet.edu.cn/" target="_blank" rel="noopener noreferrer">https://avoidnju.mirrors.cernet.edu.cn/</a></p>
</blockquote>
<p>联合镜像站支持使用参数 avoid + 镜像站名称排除特定镜像站，例如 avoidnju 代表不使用南京大学镜像站，如果某个镜像站临时出现异常且联合镜像站未发现异常时，用户就可以通过排除参数来使用其他镜像站继续获取资源。</p>
<h1>查看各个镜像站状态</h1>
<p><img src="https://img.pldduck.com/20260919131650460.png" alt="教育网联合镜像站 - 站点页截屏"></p>
<p>用户可以在 <a href="https://mirrors.cernet.edu.cn/site" target="_blank" rel="noopener noreferrer">教育网联合镜像站 - 站点</a> 查看每个镜像站的近期成功同步数量、异常同步数量、未知同步数量，以及镜像站所属高校、名称、主页、联系方式等。</p>
<h1>其他</h1>
<p>项目主页：<a href="https://github.com/mirrorz-org" target="_blank" rel="noopener noreferrer">https://github.com/mirrorz-org</a></p></article>]]></content:encoded>
      <pubDate>Sat, 19 Sep 2026 00:00:00 GMT</pubDate>
      <atom:updated>2026-09-19T00:00:00.000Z</atom:updated>
      <category domain="category">技术</category>
      <category domain="tag">镜像站</category>
      <category domain="tag">Mirror</category>
      <author>跑路的duck</author>
    </item>
    <item>
      <title><![CDATA[Chrome HSTS 的小彩蛋]]></title>
      <link>https://www.pldduck.com/post/chrome-hsts</link>
      <guid isPermaLink="true">https://www.pldduck.com/post/chrome-hsts</guid>
      <description><![CDATA[用了这么多年Chrome，被一些网站的HTTPS卡了这么久，为什么不早告诉我可以这样忽略HSTS！]]></description>
      <content:encoded><![CDATA[<article><h1>引言</h1>
<p>你是不是也经常遇到下面的情况</p>
<p><img src="https://img.pldduck.com/20260906141619679.png" alt="HSTS报错"></p>
<p>当 Chrome 出现这个页面的时候
过去大多数人都以为这个网站访问不了了，只能等他修好(包括我自己)<br>
但有时候有些网站当时又特别急着要用(Edge还好可以高级选项直接进入)</p>
<h1>正文</h1>
<p>但是今天，我偶然看到一条帖子下面的评论。<br>
说遇到这种情况有一种办法可以直接解决。</p>
<h2>方法</h2>
<p>方法就是在这个时候 用键盘盲输一个 <strong>thisisunsafe</strong><br>
这样 Chrome 就可以临时加载这个网页，忽略 Https 问题。</p>
<h2>解释</h2>
<p>基于安全考虑目前很多网站都已经部署 HTTPS 加密连接，但有时候网站服务器出现问题时可能会退回到 HTTP 明文连接。而谷歌浏览器采用的 HSTS 严格传输安全机制，会强制浏览器和服务器之间必须通过 HTTPS 加密连接。</p>
<p>遇到 HSTS 导致的无法加载时，正常情况下对访问者来说应该直接关闭页面，因为这种情况要么是网站服务器出现问题，要么是用户到该网站之间的连接遭到劫持，因此继续尝试访问存在潜在的安全隐患。</p>
<p>不过谷歌也在 Chrome 中预留小彩蛋，只需要通过键盘盲打 thisisunsafe 即可绕过 HSTS 继续加载，输入 thisisunsafe 时页面不会有任何提示，当完成输入时 Chrome 就会自动重新加载这个页面。</p>
<p>正常情况这个功能应该是给程序员临时测试网站用的，但是有些时候普通用户也可以使用，相当于用户已经知道这个连接不安全但签了个免责协议，所以谷歌会重新允许用户进行加载。</p>
<h1>结语</h1>
<p>理论上这也算不上是一个彩蛋,算是跟开发者之间的一个小秘密，主要是为了方便开发人员测试。<br>
此外，实测 Edge 也可以，但是没必要(因为 Edge 可以直接在高级选项中选择继续服务 bushi)。</p></article>]]></content:encoded>
      <pubDate>Sun, 06 Sep 2026 00:00:00 GMT</pubDate>
      <atom:updated>2026-09-06T00:00:00.000Z</atom:updated>
      <category domain="category">技术</category>
      <category domain="tag">Chrome</category>
      <category domain="tag">HTTPS</category>
      <category domain="tag">HSTS</category>
      <author>跑路的duck</author>
    </item>
    <item>
      <title><![CDATA[暗藏玄机的129.303亿美元收购抱抱脸]]></title>
      <link>https://www.pldduck.com/post/nvidia-huggingface</link>
      <guid isPermaLink="true">https://www.pldduck.com/post/nvidia-huggingface</guid>
      <description><![CDATA[英伟达以129.303亿美元收购抱抱脸，但是我为什么偏偏是129.303呢？这篇文章告诉你答案。]]></description>
      <content:encoded><![CDATA[<article><h1>前言</h1>
<p>英伟达日前正式宣布以 129.303 亿美元收购 AI 模型社区抱抱脸 (Hugging Face)，目前相关协议已经完成签署，英伟达预计该交易最终将在明年上半年完成，前提是没有监管机构阻止这场收购。</p>
<p><img src="https://img.pldduck.com/20260906151120997.png" alt="HuggingFace"></p>
<h1>为什么是 129.303</h1>
<p>英伟达提交的监管文件显示，英伟达将支付 119 亿美元收购抱抱脸当前股东持有的股份 (英伟达本身就是该平台的投资者)，剩余 10 亿美元将作为股权激励用来留住加盟英伟达的抱抱脸现有员工。</p>
<p>通常这类收购都是整数金额，那为什么英伟达开出的价格是 129.303 亿美元呢？收购金额这里<strong>其实暗藏小彩蛋</strong>：</p>
<p>129303 是 Unicode 码位 U+1F917 的十进制表示，若将其转换为十六进制则是 1F917，对应的就是 🤗 这个表情。129303 写成十六进制颜色 #129303 后会得到绿色，这种颜色与英伟达传统使用的绿色品牌色也非常接近。</p>
<p>英伟达承诺收购抱抱脸后不会将其变成英伟达专属平台，开发者仍然可以自行选择模型、框架、云服务、推理服务商和计算平台。抱抱脸也不会被要求必须采用英伟达 GPU，英伟达称平台将继续支持其他公司的芯片。</p></article>]]></content:encoded>
      <pubDate>Sun, 06 Sep 2026 00:00:00 GMT</pubDate>
      <atom:updated>2026-09-06T00:00:00.000Z</atom:updated>
      <category domain="category">随笔</category>
      <category domain="tag">Nvidia</category>
      <category domain="tag">HuggingFace</category>
      <author>跑路的duck</author>
    </item>
    <item>
      <title><![CDATA[Pages部署静态网站分流加速]]></title>
      <link>https://www.pldduck.com/post/pages-shunt</link>
      <guid isPermaLink="true">https://www.pldduck.com/post/pages-shunt</guid>
      <description><![CDATA[本文教你怎么通过多个平台的 Pages 托管服务加上分流 实现像 D-blog 一样的全球低延迟快速访问 备案域名使用效果更佳]]></description>
      <content:encoded><![CDATA[<article><h1>先看看 D-blog 实现后的效果</h1>
<h2>Ping 延迟</h2>
<p><img src="https://img.pldduck.com/20260824125807490.png" alt="Ping 延迟"></p>
<h2>国内外分流</h2>
<blockquote>
<p>国内 EdgeOne Pages (现在叫 Makers)
国外 Cloudfalre Pages</p>
</blockquote>
<p><img src="https://img.pldduck.com/20260824130119432.png" alt="国内外分流"></p>
<h1>教程</h1>
<h2>第一步:确认DNS商支持DNS分流解析</h2>
<p>Cloudflare 不支持DNS解析分流<br>
建议使用 DNSPOD<br>
如果想继续把主域名留在 Cloudflare 或其他服务器<br>
可以选择把子域名 如 blog.pldduck.com 托管到 DNSPOD<br>
只需要在 DNSPOD 里添加并在 CLoudflare 里为子域名添加NS记录即可<br>
如下图</p>
<p><img src="https://img.pldduck.com/20260824135454621.png" alt="D-blog 子域名托管 DNSPOD"><br>
<img src="https://img.pldduck.com/20260824135539736.png" alt="D-blog 子域名DNS记录"></p>
<h2>第二步:部署 Pages</h2>
<h3>Cloudflare Pages 部署</h3>
<p><img src="https://img.pldduck.com/20260824135756559.png" alt="Cloudflare Pages 部署 - 1"><br>
点击 新建应用程序</p>
<p><img src="https://img.pldduck.com/20260824141433249.png" alt="Cloudflare Pages 部署 - 2"><br>
点击 部署Pages?开始使用</p>
<p><img src="https://img.pldduck.com/20260824141607108.png" alt="Cloudflare Pages 部署 - 3">
部署方式自行选择 如果是直接上传源码ZIP文件就选下面的</p>
<p><img src="https://img.pldduck.com/20260824141726416.png" alt="Cloudflare Pages 部署 - 4">
在项目列表选择自定义域并绑定(获取CNAME地址 等下解析用)</p>
<h3>EdgeOne Makers 部署</h3>
<p><img src="https://img.pldduck.com/20260824141859639.png" alt="EdgeOne Makers 部署 - 1">
点击创建项目 同样的 需要绑定Git仓库就选择 导入Git仓库 直接上传源码ZIP就选择直接上传<br>
可用区-自行选择 如果没有备案：选择 全球可用区（不含中国大陆） 如果已备案：选择 全球可用区（含中国大陆）</p>
<p><img src="https://img.pldduck.com/20260824142056859.png" alt="EdgeOne Makers 部署 - 2">
同样来到项目页 点击 域名管理
添加和刚刚在 Cloudflare Pages 添加的一样的域名 并同样记录下CNAME地址</p>
<h2>第三步:分流解析</h2>
<p>打开DNSPOD</p>
<p>如图添加两条记录<br>
一条 默认 指向 Cloudflare Pages 的CNAME<br>
另一条选择 境内 指向 EdgeOne Makers 的CNAME<br>
<img src="https://img.pldduck.com/20260824142443320.png" alt="分流解析"></p>
<h2>第四步:完善SSL</h2>
<p>由于分流原因  EdgeOne Makers 可能无法自动申请SSL<br>
需要使用第三方网页并手动上传部署<br>
我是用的是 <a href="https://certple.zeoseven.com/" target="_blank" rel="noopener noreferrer">Certple</a></p>
<p>获得SSL证书后来到 <a href="https://console.cloud.tencent.com/ssl" target="_blank" rel="noopener noreferrer">腾讯云SSL管理</a> 上传证书</p>
<p>最后回到 EdgeOne Makers 的域名管理</p>
<p><img src="https://img.pldduck.com/20260824143005125.png" alt="EdgeOne Makers SSL配置 - 1">
点击HTTPS下的 配置 按钮</p>
<p><img src="https://img.pldduck.com/20260824143046079.png" alt="EdgeOne Makers SSL配置 - 2"><br>
点击 配置</p>
<p><img src="https://img.pldduck.com/20260824143113253.png" alt="EdgeOne Makers SSL配置 - 3">
选择SSL托管证书 并勾选刚刚上传的证书 然后点击保存</p>
<p>最后等待部署完成就好了</p>
<h2>第五步:验证分流</h2>
<p>最后测试分流是否成功<br>
如果成功 国外节点IP位置应该显示 Cloudflare anycast<br>
国内节点IP位置应在国内(如果在部署时选择的是 全球可用区不含中国大陆 则为其他国家IP)</p>
<h1>总结</h1>
<p>所有内容到此就结束了<br>
通过这样的DNS分流 可以有效加快国内外的访问速度<br>
同时减轻单个平台的压力 避免风控<br>
此教程建议已备案域名使用 备案域名使用效果更佳</p></article>]]></content:encoded>
      <pubDate>Mon, 24 Aug 2026 00:00:00 GMT</pubDate>
      <atom:updated>2026-08-24T00:00:00.000Z</atom:updated>
      <category domain="category">教程</category>
      <category domain="tag">Cloudflare Pages</category>
      <category domain="tag">EdgeOne Pages</category>
      <category domain="tag">网站加速</category>
      <category domain="tag">DNS分流</category>
      <author>跑路的duck</author>
    </item>
    <item>
      <title><![CDATA[通过Cloudflare规则实现纯静态随机图API]]></title>
      <link>https://www.pldduck.com/post/cf-randompic</link>
      <guid isPermaLink="true">https://www.pldduck.com/post/cf-randompic</guid>
      <description><![CDATA[本文教你通过Cloudflare规则实现纯静态随机图API 无需后端服务器 部署在Cloudflare Pages/其他可直接访问的存储(OSS COS)]]></description>
      <content:encoded><![CDATA[<article><h1>通过Cloudflare规则实现纯静态随机图API</h1>
<p>参考项目 <a href="https://github.com/ououduck/D-RandomPIC/" target="_blank" rel="noopener noreferrer">D-RandomPIC</a></p>
<p>基于 Cloudflare Pages 404 规则与 GitHub Actions 自动构建的随机图 API。</p>
<p>访问 <code>https://randompic.pldduck.com/ecy-v</code> 之类的地址即可获得一张随机图片。</p>
<h1>演示</h1>
<p><img src="https://randompic.pldduck.com/ecy-h" alt="随机图演示"></p>
<h1>介绍</h1>
<h2>工作原理</h2>
<p>不依赖任何后端服务，完全由静态文件与 Cloudflare 规则实现随机取图：</p>
<ol>
<li><code>gen_pic.py</code> 扫描 <code>pic/</code> 下的分类子文件夹，将每个分类的图片复制到 <code>dist/&#x3C;分类>/</code> 目录下，并按十六进制编号重命名，生成 <code>000.jpg</code> 到 <code>fff.jpg</code> 共 4096 个文件（对应规则中 3 位十六进制 hash 的全部取值，覆盖所有可能路径，保证不会 404）。</li>
<li>构建产物 <code>dist/</code> 部署到 Cloudflare Pages。</li>
<li>在 Cloudflare 为该 Pages 项目配置 404 规则（见 <code>rule.txt</code>）：</li>
</ol>
<pre><code> concat(http.request.uri.path, "/", substring(uuidv4(cf.random_seed), 0, 3), ".jpg")
</code></pre>
<p>当用户访问 <code>/ecy-v</code> 时，如果该路径不存在（返回 404），规则会使用随机种子生成 3 位十六进制字符串并重写到 <code>/ecy-v/xxx.jpg</code>，从而随机返回该分类下的一张图片。</p>
<h2>D-RandomPIC 目录结构</h2>
<pre><code>D-RandomPIC/
├── gen_pic.py            # 构建脚本：生成 dist/ 下的随机图片文件
├── index.html            # 演示页面（列出各分类 API 地址）
├── 404.html              # 404 响应体
├── rule.txt              # Cloudflare 404 规则表达式
├── pic/                  # 源图片目录（按分类存放）
│   ├── ecy-h/            # 随机二次元图（横屏/电脑）
│   ├── ecy-v/            # 随机二次元图（竖屏/手机）
│   ├── fj/               # 其他分类示例
│   └── ys/               # 其他分类示例
└── dist/                 # 构建产物（由 gen_pic.py 生成，不提交到仓库）
</code></pre>
<blockquote>
<p>新增图片分类时，只需在 <code>pic/</code> 下新建一个子文件夹并放入图片，推送到仓库即可，构建脚本与 Cloudflare 规则无需改动。</p>
</blockquote>
<h2>本地构建测试</h2>
<p>需要 Python 3（仅使用标准库，无需安装任何依赖）。</p>
<pre><code class="hljs language-bash">python gen_pic.py
</code></pre>
<p>构建完成后检查 <code>dist/</code> 目录，每个分类下应生成 4096 个文件（<code>000.jpg</code> 到 <code>fff.jpg</code>）。</p>
<h1>搭建部署</h1>
<h2>部署方法</h2>
<h3>1.Fork <a href="https://github.com/ououduck/D-RandomPIC/" target="_blank" rel="noopener noreferrer">D-RandomPIC</a></h3>
<p>将 <a href="https://github.com/ououduck/D-RandomPIC/" target="_blank" rel="noopener noreferrer">D-RandomPIC</a> Fork一份到你的Github</p>
<h3>2.创建 Pages 项目</h3>
<p>在 Cloudflare Dashboard 中创建 Pages 项目<br>
选择直传项目 Direct Upload</p>
<h3>3.配置 GitHub Actions Secrets</h3>
<table>
<thead>
<tr>
<th>名称</th>
<th>说明</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>CLOUDFLARE_API_TOKEN</code></td>
<td>Cloudflare API Token，需具备 Pages 编辑权限</td>
</tr>
<tr>
<td><code>CLOUDFLARE_ACCOUNT_ID</code></td>
<td>Cloudflare 账号 ID（Dashboard 首页右下角可查看）</td>
</tr>
<tr>
<td><code>CLOUDFLARE_PROJECT_NAME</code></td>
<td>创建Pages时填写的项目名称(如果填写 d-randompic 可忽略此变量)</td>
</tr>
</tbody>
</table>
<h4>CLOUDFLARE_API_TOKEN 获取方法</h4>
<p><img src="https://img.pldduck.com/20260820141442479.png" alt="获取API-1"><br>
点击右上角 支持 右边的 账户图标<br>
<img src="https://img.pldduck.com/20260820141557432.png" alt="获取API-2"><br>
点击配置文件<br>
<img src="https://img.pldduck.com/20260820141715821.png" alt="获取API-3"><br>
选择API令牌 点击右上角创建令牌<br>
<img src="https://img.pldduck.com/20260820141900773.png" alt="获取API-4">
选择创建自定令牌<br>
<img src="https://img.pldduck.com/20260820141803560.png" alt="获取API-5"><br>
按照上图配置权限<br>
最后点击获取令牌就可以得到令牌了</p>
<h4>CLOUDFLARE_ACCOUNT_ID 获取方法</h4>
<p><img src="https://img.pldduck.com/20260820142109415.png" alt="获取账户ID"><br>
红色码住的这一段就是 也可以随便找个域名的页面进去的右侧下滑就有</p>
<h3>4.配置 404 规则</h3>
<p>配置Rules 为 404 状态码配置重写规则 表达式见 <code>rule.txt</code></p>
<p>找到随机图要使用的域名 点击进入<br>
<img src="https://img.pldduck.com/20260820142333847.png" alt="配置Rules-1">
在左侧菜单栏里找到 规则>概述
<img src="https://img.pldduck.com/20260820142520728.png" alt="配置Rules-2"><br>
点击创建规则<br>
<img src="https://img.pldduck.com/20260820142603272.png" alt="配置Rules-3"><br>
选择URL重写规则<br>
<img src="https://img.pldduck.com/20260820142708391.png" alt="配置Rules-4"><br>
选择自定义表达式
<img src="https://img.pldduck.com/20260820142849486.png" alt="配置Rules-5"><br>
按照上图配置
重写到 后面的内容如下</p>
<pre><code>concat(http.request.uri.path, "/", substring(uuidv4(cf.random_seed), 0, 3), ".jpg")
</code></pre>
<p>最后保存即可</p>
<h3>5.触发Action</h3>
<p><img src="https://img.pldduck.com/20260820143052442.png" alt="触发Action"><br>
进入action页面后 找到左侧的Build and Deploy to Cloudflare Pages
点击右侧的Run workflow<br>
再次点击绿色的Run workflow<br>
最后等待部署成功即可</p>
<h2>API 使用</h2>
<ul>
<li><code>GET /ecy-v</code>：随机二次元图（竖屏/手机）</li>
<li><code>GET /ecy-h</code>：随机二次元图（横屏/电脑）</li>
<li>新增分类 <code>xxx</code> 后，<code>GET /xxx</code> 即生效</li>
</ul>
<p>示例：</p>
<pre><code class="hljs language-bash">curl -L https://randompic.pldduck.com/ecy-v
</code></pre>
<p>在 HTML 中直接使用：</p>
<pre><code class="hljs language-html"><span class="hljs-tag">&#x3C;<span class="hljs-name">img</span> <span class="hljs-attr">src</span>=<span class="hljs-string">"https://randompic.pldduck.com/ecy-v"</span> <span class="hljs-attr">alt</span>=<span class="hljs-string">"随机图片"</span>></span>
</code></pre></article>]]></content:encoded>
      <pubDate>Thu, 20 Aug 2026 00:00:00 GMT</pubDate>
      <atom:updated>2026-08-20T00:00:00.000Z</atom:updated>
      <category domain="category">教程</category>
      <category domain="tag">Cloudflare</category>
      <category domain="tag">Cloudflare Pages</category>
      <category domain="tag">Cloudfalre Rule</category>
      <category domain="tag">RandomPIC</category>
      <category domain="tag">随机图</category>
      <author>跑路的duck</author>
    </item>
    <item>
      <title><![CDATA[接入不蒜子统计]]></title>
      <link>https://www.pldduck.com/post/busuanzi</link>
      <guid isPermaLink="true">https://www.pldduck.com/post/busuanzi</guid>
      <description><![CDATA[D-blog接入不蒜子统计-静态博客的福音]]></description>
      <content:encoded><![CDATA[<article><h1>D-blog 接入不蒜子统计</h1>
<p>不蒜子是一款轻量极简的网站访问量统计工具<br>
通过引入脚本和标签代码，即可实现网站访问量(PV)、访客数(UV)和页面阅读量的访问数据实时统计<br>
由于不需要后端 简直是静态博客的福音</p>
<h1>不蒜子统计首页截图</h1>
<p><img src="https://img.pldduck.com/20260812212236934.png" alt="不蒜子首页截图"></p>
<h1>D-blog 的不蒜子统计</h1>
<p>你可以通过一下两个方式查看 D-blog 的不蒜子统计</p>
<ul>
<li><a href="https://www.pldduck.com/stats" title="D-blog 统计页">D-blog 统计页</a></li>
<li><a href="https://www.busuanzi.cc/count.php?search=www.pldduck.com" title="不蒜子统计页" target="_blank" rel="noopener noreferrer">不蒜子统计页</a></li>
</ul></article>]]></content:encoded>
      <pubDate>Wed, 12 Aug 2026 00:00:00 GMT</pubDate>
      <atom:updated>2026-08-12T00:00:00.000Z</atom:updated>
      <category domain="category">随笔</category>
      <category domain="tag">D-blog</category>
      <category domain="tag">不蒜子统计</category>
      <category domain="tag">网页统计</category>
      <author>跑路的duck</author>
    </item>
    <item>
      <title><![CDATA[使用 giscus 和 akismet 实现静态博客评论及垃圾审查功能]]></title>
      <link>https://www.pldduck.com/post/giscus-akisme</link>
      <guid isPermaLink="true">https://www.pldduck.com/post/giscus-akisme</guid>
      <description><![CDATA[使用 GitHub Discussions 承载静态博客评论，再结合 Akismet 和 GitHub Actions 自动识别、删除垃圾评论，不需要自建评论后端。]]></description>
      <content:encoded><![CDATA[<article><h1>使用 giscus 和 akismet 实现静态博客评论及垃圾审查功能</h1>
<p>静态博客的优点是部署简单、访问速度快，也不需要维护数据库和后端服务。不过，评论功能往往会把问题带回来：评论数据放在哪里？访客如何登录？垃圾评论如何处理？如果为这些问题单独搭建一套服务，系统复杂度很快就会超过博客本身。</p>
<p><code>giscus</code> 提供了一个轻量的解决方案：它使用 GitHub Discussions 保存评论，博客只需要加载一段客户端脚本。再配合 <code>Akismet</code> 和 GitHub Actions，就可以在评论创建后自动进行垃圾检测，并删除被判定为垃圾的评论。</p>
<p>这套方案适合源码托管在 GitHub、文章以 Markdown 管理的个人静态博客。评论的存储、登录和通知由 GitHub 负责，垃圾审核交给 Action 执行，博客本身不需要增加评论后端。</p>
<h2>工作原理</h2>
<p>评论系统由三个部分组成：</p>
<ol>
<li><code>giscus</code> 把网页评论映射到 GitHub Discussions。</li>
<li>GitHub Discussion 负责保存评论，并提供 GitHub 登录能力。</li>
<li><code>discussion_comment</code> 事件触发 GitHub Actions，Action 将评论内容提交给 Akismet；如果返回垃圾评论结果，则通过 GraphQL 删除评论。</li>
</ol>
<pre><code class="hljs language-mermaid">flowchart LR
  A[文章页面] --> B[giscus 客户端脚本]
  B --> C[GitHub Discussion]
  C --> D[discussion_comment 事件]
  D --> E[GitHub Actions]
  E --> F[Akismet 检测]
  F -->|正常| G[保留评论]
  F -->|垃圾| H[GraphQL 删除评论]
</code></pre>
<h2>配置 giscus</h2>
<h3>1. 准备 GitHub 仓库</h3>
<p>首先准备一个<strong>公开的 GitHub 仓库</strong>作为评论仓库。可以直接使用博客源码仓库，也可以创建一个单独的空仓库。评论仓库需要开启 GitHub Discussions 功能：</p>
<ol>
<li>打开仓库的 <code>Settings</code>。</li>
<li>在 <code>Features</code> 中启用 <code>Discussions</code>。</li>
<li>创建一个用于评论的分类，例如 <code>Announcements</code>。</li>
</ol>
<p><img src="https://img.pldduck.com/20260812115924127.webp" alt="开启 GitHub Discussions 功能"></p>
<h3>2. 在 giscus 中生成配置</h3>
<p>打开 <a href="https://giscus.app/zh-CN" target="_blank" rel="noopener noreferrer">giscus 中文配置页面</a>，填写仓库名称并完成检查。随后选择评论映射方式。</p>
<p><img src="https://img.pldduck.com/20260812115924128.webp" alt="填写 giscus 仓库信息"></p>
<p>最推荐使用 <code>pathname</code> 映射。假设文章地址是 <code>/post/hello-world</code>，只要这个路径保持不变，评论就会一直关联到同一个 Discussion。</p>
<p>常见映射方式的区别如下：</p>
<table>
<thead>
<tr>
<th>映射方式</th>
<th>关联依据</th>
<th>需要注意的问题</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>pathname</code></td>
<td>页面路径</td>
<td>推荐，域名变化不会影响评论关联</td>
</tr>
<tr>
<td><code>url</code></td>
<td>完整 URL</td>
<td>更换域名后可能无法命中原评论</td>
</tr>
<tr>
<td><code>title</code></td>
<td>文章标题</td>
<td>修改标题后可能产生新的评论串</td>
</tr>
</tbody>
</table>
<p><img src="https://img.pldduck.com/20260812115924129.webp" alt="选择评论映射方式"></p>
<p>建议同时开启严格匹配。严格匹配会为页面和 Discussion 建立额外的哈希标识，可以降低不同页面评论串台的概率。</p>
<p><img src="https://img.pldduck.com/20260812115924130.png" alt="开启严格标题匹配"></p>
<p>如果需要把页面重新映射到一个已经存在的 Discussion，可以根据 giscus 生成的页面标识计算 SHA-1，然后把对应标识写入目标 Discussion 的正文。一般情况下不需要手动处理，首次加载时让 giscus 自动创建即可。</p>
<h3>3. 选择分类和功能</h3>
<p>评论分类可以选择 <code>Announcements</code>（公告）等适合公开讨论的分类。表情反应、输入框位置、语言和主题根据博客需要选择。</p>
<p><img src="https://img.pldduck.com/20260812115924131.webp" alt="选择评论分类"></p>
<p><img src="https://img.pldduck.com/20260812115924132.webp" alt="选择 giscus 功能"></p>
<p><img src="https://img.pldduck.com/20260812115924133.webp" alt="选择 giscus 主题"></p>
<p>生成的脚本大致如下：</p>
<pre><code class="hljs language-html"><span class="hljs-tag">&#x3C;<span class="hljs-name">script</span> <span class="hljs-attr">src</span>=<span class="hljs-string">"https://giscus.app/client.js"</span>
        <span class="hljs-attr">data-repo</span>=<span class="hljs-string">"OWNER/REPOSITORY"</span>
        <span class="hljs-attr">data-repo-id</span>=<span class="hljs-string">"REPOSITORY_ID"</span>
        <span class="hljs-attr">data-category</span>=<span class="hljs-string">"Announcements"</span>
        <span class="hljs-attr">data-category-id</span>=<span class="hljs-string">"CATEGORY_ID"</span>
        <span class="hljs-attr">data-mapping</span>=<span class="hljs-string">"pathname"</span>
        <span class="hljs-attr">data-strict</span>=<span class="hljs-string">"1"</span>
        <span class="hljs-attr">data-reactions-enabled</span>=<span class="hljs-string">"1"</span>
        <span class="hljs-attr">data-emit-metadata</span>=<span class="hljs-string">"0"</span>
        <span class="hljs-attr">data-input-position</span>=<span class="hljs-string">"top"</span>
        <span class="hljs-attr">data-theme</span>=<span class="hljs-string">"preferred_color_scheme"</span>
        <span class="hljs-attr">data-lang</span>=<span class="hljs-string">"zh-CN"</span>
        <span class="hljs-attr">data-loading</span>=<span class="hljs-string">"lazy"</span>
        <span class="hljs-attr">crossorigin</span>=<span class="hljs-string">"anonymous"</span>
        <span class="hljs-attr">async</span>></span>
<span class="hljs-tag">&#x3C;/<span class="hljs-name">script</span>></span>
</code></pre>
<p>把脚本放在文章正文下方的评论区容器中即可。React、Vue 或其他前端框架项目应在组件挂载后创建脚本，避免 SSR 阶段访问 <code>document</code>。</p>
<p>对于 SPA，还要注意两个问题：切换文章时需要重新加载脚本，让 giscus 读取新的 <code>pathname</code>；站点切换深浅主题时，需要向 giscus iframe 发送主题更新消息，不能只在首次加载时设置主题。</p>
<p><img src="https://img.pldduck.com/20260812115924134.webp" alt="将 giscus 脚本放入博客"></p>
<h2>配置 Akismet</h2>
<h3>1. 获取 API Key</h3>
<p>打开 <a href="https://akismet.com/" target="_blank" rel="noopener noreferrer">Akismet 官网</a> 注册账号，选择适合个人博客的订阅方案。个人方案可以将价格调整为 <code>$0</code>，完成注册后在账户页面获取 Akismet API Key。</p>
<p><img src="https://img.pldduck.com/20260812115924135.webp" alt="注册 Akismet"></p>
<p>Akismet 的检测接口会接收评论作者、评论内容、来源页面等信息，并返回 <code>true</code> 或 <code>false</code>。<code>true</code> 表示 Akismet 认为评论是垃圾内容，<code>false</code> 表示评论可以保留。</p>
<h3>2. 创建 GitHub Token</h3>
<p>Action 需要删除 Discussion 评论，因此默认的 <code>GITHUB_TOKEN</code> 可能不一定满足仓库权限要求。可以在 <a href="https://github.com/settings/tokens" target="_blank" rel="noopener noreferrer">GitHub Token 设置页面</a> 创建一个专用令牌，并按照实际仓库设置授予必要的 Discussions 写权限。</p>
<p>令牌应保存为仓库 Secret，例如：</p>
<ul>
<li><code>AKISMET_API_KEY</code>：Akismet API Key。</li>
<li><code>GH_TOKEN</code>：用于调用 GitHub GraphQL API 的令牌。</li>
</ul>
<p>不要把这两个值直接写进 workflow 文件，也不要在日志中输出完整令牌。</p>
<p><img src="https://img.pldduck.com/20260812115924136.webp" alt="配置 GitHub Actions Secret"></p>
<h2>创建垃圾评论检测 Action</h2>
<p>在启用了 giscus 的仓库中创建 <code>.github/workflows/akismet-discussion-comment-check.yml</code>：</p>
<pre><code class="hljs language-yaml"><span class="hljs-attr">name:</span> <span class="hljs-string">Akismet</span> <span class="hljs-string">Discussion</span> <span class="hljs-string">Comment</span> <span class="hljs-string">Checker</span>

<span class="hljs-attr">on:</span>
  <span class="hljs-attr">discussion_comment:</span>
    <span class="hljs-attr">types:</span> [<span class="hljs-string">created</span>]

<span class="hljs-attr">permissions:</span>
  <span class="hljs-attr">contents:</span> <span class="hljs-string">read</span>
  <span class="hljs-attr">discussions:</span> <span class="hljs-string">write</span>

<span class="hljs-attr">jobs:</span>
  <span class="hljs-attr">check-akismet:</span>
    <span class="hljs-attr">runs-on:</span> <span class="hljs-string">ubuntu-latest</span>
    <span class="hljs-attr">steps:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">Check</span> <span class="hljs-string">and</span> <span class="hljs-string">delete</span> <span class="hljs-string">spam</span> <span class="hljs-string">comments</span>
        <span class="hljs-attr">uses:</span> <span class="hljs-string">actions/github-script@v8</span>
        <span class="hljs-attr">env:</span>
          <span class="hljs-attr">AKISMET_API_KEY:</span> <span class="hljs-string">${{</span> <span class="hljs-string">secrets.AKISMET_API_KEY</span> <span class="hljs-string">}}</span>
        <span class="hljs-attr">with:</span>
          <span class="hljs-attr">github-token:</span> <span class="hljs-string">${{</span> <span class="hljs-string">secrets.GH_TOKEN</span> <span class="hljs-string">}}</span>
          <span class="hljs-attr">script:</span> <span class="hljs-string">|
            const apiKey = process.env.AKISMET_API_KEY;
            const comment = context.payload.comment;
            const discussion = context.payload.discussion;
            const repository = context.payload.repository;
</span>
            <span class="hljs-string">const</span> <span class="hljs-string">params</span> <span class="hljs-string">=</span> <span class="hljs-string">new</span> <span class="hljs-string">URLSearchParams({</span>
              <span class="hljs-attr">blog:</span> <span class="hljs-string">repository.html_url,</span>
              <span class="hljs-attr">user_ip:</span> <span class="hljs-string">'127.0.0.1'</span><span class="hljs-string">,</span>
              <span class="hljs-attr">user_agent:</span> <span class="hljs-string">'D-blog Akismet Action/1.0'</span><span class="hljs-string">,</span>
              <span class="hljs-attr">referrer:</span> <span class="hljs-string">discussion.html_url,</span>
              <span class="hljs-attr">comment_type:</span> <span class="hljs-string">'comment'</span><span class="hljs-string">,</span>
              <span class="hljs-attr">comment_author:</span> <span class="hljs-string">comment.user.login,</span>
              <span class="hljs-attr">comment_author_url:</span> <span class="hljs-string">comment.user.html_url,</span>
              <span class="hljs-attr">comment_content:</span> <span class="hljs-string">comment.body,</span>
            <span class="hljs-string">});</span>

            <span class="hljs-string">const</span> <span class="hljs-string">response</span> <span class="hljs-string">=</span> <span class="hljs-string">await</span> <span class="hljs-string">fetch(</span>
              <span class="hljs-string">`https://${apiKey}.rest.akismet.com/1.1/comment-check`,</span>
              {
                <span class="hljs-attr">method:</span> <span class="hljs-string">'POST'</span>,
                <span class="hljs-attr">headers:</span> {
                  <span class="hljs-attr">'Content-Type':</span> <span class="hljs-string">'application/x-www-form-urlencoded'</span>,
                  <span class="hljs-attr">'User-Agent':</span> <span class="hljs-string">'D-blog Akismet Action/1.0'</span>,
                },
                <span class="hljs-attr">body:</span> <span class="hljs-string">params</span>,
              }<span class="hljs-string">,</span>
            <span class="hljs-string">);</span>
            <span class="hljs-string">const</span> <span class="hljs-string">result</span> <span class="hljs-string">=</span> <span class="hljs-string">(await</span> <span class="hljs-string">response.text()).trim();</span>

            <span class="hljs-string">if</span> <span class="hljs-string">(!response.ok</span> <span class="hljs-string">||</span> <span class="hljs-type">!['true',</span> <span class="hljs-string">'false'</span><span class="hljs-string">].includes(result))</span> {
              <span class="hljs-string">core.setFailed(`Akismet</span> <span class="hljs-string">returned</span> <span class="hljs-string">HTTP</span> <span class="hljs-string">$</span>{<span class="hljs-string">response.status</span>}<span class="hljs-string">`);</span>
              <span class="hljs-string">return;</span>
            }

            <span class="hljs-string">if</span> <span class="hljs-string">(result</span> <span class="hljs-string">===</span> <span class="hljs-string">'true'</span><span class="hljs-string">)</span> {
              <span class="hljs-string">await</span> <span class="hljs-string">github.graphql(</span>
                <span class="hljs-string">`mutation</span> <span class="hljs-string">DeleteDiscussionComment($commentId:</span> <span class="hljs-string">ID!)</span> {
                  <span class="hljs-attr">deleteDiscussionComment(input:</span> { <span class="hljs-attr">id:</span> <span class="hljs-string">$commentId</span> }<span class="hljs-string">)</span> {
                    <span class="hljs-string">clientMutationId</span>
                  }
                }<span class="hljs-string">`</span>,
                { <span class="hljs-attr">commentId:</span> <span class="hljs-string">comment.node_id</span> },
              <span class="hljs-string">);</span>
              <span class="hljs-string">core.info('Spam</span> <span class="hljs-string">comment</span> <span class="hljs-string">deleted.');</span>
            }
</code></pre>
<p>这个 workflow 的关键点有三个：</p>
<ol>
<li>监听 <code>discussion_comment.created</code>，只在新评论产生时执行。</li>
<li>使用 <code>github-script</code> 直接读取事件对象，评论中的换行、引号和特殊字符不会破坏 shell 命令。</li>
<li>删除评论使用 GraphQL 的 <code>deleteDiscussionComment</code> mutation，因为 giscus 评论对应的是 Discussion comment，而不是普通 Issue comment。</li>
</ol>
<p><img src="https://img.pldduck.com/20260812115924137.webp" alt="Akismet 垃圾评论检测 Action"></p>
<h2>测试垃圾评论检测</h2>
<p>完成配置后，可以发布一条测试评论验证流程。Akismet 提供了专门的测试字符串：</p>
<pre><code class="hljs language-text">viagra-test-123
</code></pre>
<p>这条评论应当被判定为垃圾评论。发布后进入仓库的 <code>Actions</code> 页面，检查 workflow 是否成功运行，并确认评论是否被删除。</p>
<p><img src="https://img.pldduck.com/20260812115924138.webp" alt="查看 Action 检测结果"></p>
<p>测试完成后不要继续保留测试评论，也不要在生产环境中把测试字符串当作普通内容使用。</p>
<h2>处理持续刷屏的用户</h2>
<p>Akismet 适合处理自动化垃圾内容，但它不是完整的社区管理系统。如果某个用户持续发布刷屏内容，可以在 GitHub 中打开该用户的个人资料页，手动阻止用户。</p>
<p>具体操作可以参考 GitHub 文档：<a href="https://docs.github.com/zh/communities/maintaining-your-safety-on-github/blocking-a-user-from-your-personal-account#blocking-a-user-from-their-profile-page" target="_blank" rel="noopener noreferrer">阻止用户访问您的个人帐户</a>。</p>
<h2>防止新用户刷评论</h2>
<p>如果垃圾评论来自大量新注册账号，可以配置仓库的交互限制：</p>
<pre><code class="hljs language-text">https://github.com/你的用户名/你的仓库/settings/interaction_limits
</code></pre>
<p>根据博客的访问情况，可以临时限制新用户创建 Discussion、发表评论或进行其他交互。限制不应长期设置得过于严格，否则可能误伤正常读者。</p>
<h2>注意事项</h2>
<p>这套方案虽然简单，但仍有几个边界需要提前了解：</p>
<ul>
<li>giscus 依赖 GitHub，GitHub 登录或 Discussions 服务异常时，评论区也会受到影响。</li>
<li><code>pathname</code> 是评论关联的关键，文章 ID 和文章路径不应随意修改。</li>
<li>Action 删除评论需要足够的 Discussions 写权限，Secret 令牌应使用最小必要权限。</li>
<li>Akismet 的判断结果不是绝对准确，重要博客仍应保留人工复核能力。</li>
<li>不要在 workflow 日志中打印评论正文、API Key 或完整 GitHub Token。</li>
<li>评论区脚本来自第三方服务，应在站点隐私政策和 Cookie 策略中说明相关数据处理方式。</li>
</ul>
<h2>总结</h2>
<p>对于使用 GitHub 托管源码的静态博客，giscus 可以用很低的接入成本提供完整的评论体验，Akismet 则负责过滤常见的垃圾内容。两者结合后，博客不需要搭建数据库、用户系统或单独的评论后端，就能拥有 GitHub 登录、Discussion 存储、评论通知和自动垃圾审查功能。</p>
<p>本文参考并沿用了<a href="https://2x.nz/posts/giscus-akismet" target="_blank" rel="noopener noreferrer">二叉树树博客原文章</a>的配图。<br>
此外，请注意，本文采用了AI润色，请注意辨别。</p></article>]]></content:encoded>
      <pubDate>Thu, 06 Aug 2026 00:00:00 GMT</pubDate>
      <atom:updated>2026-08-06T00:00:00.000Z</atom:updated>
      <category domain="category">教程</category>
      <category domain="tag">giscus</category>
      <category domain="tag">Akismet</category>
      <category domain="tag">GitHub Discussions</category>
      <category domain="tag">GitHub Actions</category>
      <category domain="tag">静态博客</category>
      <category domain="tag">评论系统</category>
      <author>跑路的duck</author>
    </item>
    <item>
      <title><![CDATA[D-blog 最佳 Pagespeed Insights 结果]]></title>
      <link>https://www.pldduck.com/post/best-pagespeed-insights</link>
      <guid isPermaLink="true">https://www.pldduck.com/post/best-pagespeed-insights</guid>
      <description><![CDATA[D-blog 最佳 Pagespeed Insights 结果]]></description>
      <content:encoded><![CDATA[<article><h1>测试结果截图(最新)</h1>
<p><img src="https://img.pldduck.com/20260812115903097.png" alt="测试结果截图 - 手机"><br>
<img src="https://img.pldduck.com/20260812115903098.png" alt="测试结果截图 - 电脑"></p>
<h1>测试结果分享链接(最新)</h1>
<p><a href="https://pagespeed.web.dev/analysis/https-blog-pldduck-com/5lweuqcqnc?hl=zh-CN&#x26;form_factor=desktop" target="_blank" rel="noopener noreferrer">测试结果分享链接</a></p>
<h1>历史记录</h1>
<h2>2026-08-05</h2>
<p><img src="https://img.pldduck.com/20260812115903099.png" alt="2026-08-05"></p></article>]]></content:encoded>
      <pubDate>Wed, 05 Aug 2026 00:00:00 GMT</pubDate>
      <atom:updated>2026-08-09T00:00:00.000Z</atom:updated>
      <category domain="category">随笔</category>
      <category domain="tag">D-blog</category>
      <category domain="tag">PageSpeed Insights</category>
      <category domain="tag">网站优化</category>
      <category domain="tag">速度测试</category>
      <author>跑路的duck</author>
    </item>
    <item>
      <title><![CDATA[CodeDrobe Desktop主题商店]]></title>
      <link>https://www.pldduck.com/post/codedrobe-desktop</link>
      <guid isPermaLink="true">https://www.pldduck.com/post/codedrobe-desktop</guid>
      <description><![CDATA[面向 AI 桌面应用的开源主题管理器]]></description>
      <content:encoded><![CDATA[<article><h1>软件介绍</h1>
<p>CodeDrobe Desktop 是一款面向 AI 桌面应用的开源主题管理器，目前支持 OpenAI Codex、WorkBuddy、QoderWork 与 TRAE SOLO，运行于 macOS 和 Windows。在应用内浏览 CodeDrobe 主题商店、一键把主题应用到任意受支持的应用，并可随时恢复原生界面。主题只改变外观，不会修改应用安装包和你的数据。</p>
<h1>软件展示</h1>
<p><img src="https://img.pldduck.com/20260812115915366.png" alt="CodeDrobe Desktop 1">
<img src="https://img.pldduck.com/20260812115915367.png" alt="CodeDrobe Desktop 2"></p>
<h1>软件下载</h1>
<p><a href="https://1drv.ms/f/c/777bf5ca0164e4ef/IgAqfH5aPCCXRZ5v7GRTZgTYAXnPLgvLK3zWVzHO7Zvt4R0?e=ZBABra" target="_blank" rel="noopener noreferrer">OneDrive (免登录 慢)</a></p>
<p><a href="https://yun.139.com/shareweb/#/w/i/2xTrM3qmrTx4e" target="_blank" rel="noopener noreferrer">移动云盘 (需登录 不限速)</a></p></article>]]></content:encoded>
      <pubDate>Mon, 03 Aug 2026 00:00:00 GMT</pubDate>
      <atom:updated>2026-08-03T00:00:00.000Z</atom:updated>
      <category domain="category">分享</category>
      <category domain="tag">AI</category>
      <category domain="tag">美化</category>
      <category domain="tag">工具</category>
      <author>跑路的duck</author>
    </item>
    <item>
      <title><![CDATA[LocalFileEncrypt - 文件加密解密工具]]></title>
      <link>https://www.pldduck.com/post/localfileencrypt</link>
      <guid isPermaLink="true">https://www.pldduck.com/post/localfileencrypt</guid>
      <description><![CDATA[安全的本地加解密工具]]></description>
      <content:encoded><![CDATA[<article><h1>网站介绍</h1>
<p>文件加密解密工具「LocalFileEncrypt」在本地加解密文本、文件或整个文件夹，只需要上传文件加密或者解密。据介绍使用标准密码学方法 AES 加密您的数据，只有掌握密钥的人才能正确解密。</p>
<h1>网站展示</h1>
<p><img src="https://img.pldduck.com/20260812115940828.png" alt="localfileencrypt.com"></p>
<h1>网站地址</h1>
<p><a href="https://localfileencrypt.com/" target="_blank" rel="noopener noreferrer">Local File Encrypt | 在本地加解密文本、文件或整个文件夹</a></p></article>]]></content:encoded>
      <pubDate>Mon, 03 Aug 2026 00:00:00 GMT</pubDate>
      <atom:updated>2026-08-03T00:00:00.000Z</atom:updated>
      <category domain="category">分享</category>
      <category domain="tag">工具</category>
      <category domain="tag">加密</category>
      <category domain="tag">解密</category>
      <category domain="tag">安全</category>
      <author>跑路的duck</author>
    </item>
    <item>
      <title><![CDATA[用 GitHub Issue 和 Actions 为静态博客实现新文章订阅]]></title>
      <link>https://www.pldduck.com/post/github-issue-blog-subscription</link>
      <guid isPermaLink="true">https://www.pldduck.com/post/github-issue-blog-subscription</guid>
      <description><![CDATA[不依赖第三方推送平台，利用 GitHub Issue 的订阅能力和 GitHub Actions，在发布 Markdown 文章后自动向订阅者发送更新提醒。]]></description>
      <content:encoded><![CDATA[<article><h1>用 GitHub Issue 和 Actions 为静态博客实现新文章订阅</h1>
<p>静态博客通常只有 RSS。RSS 很可靠，但读者需要自行准备阅读器，也不一定会及时看到更新。如果博客源码托管在 GitHub，还可以利用一个很容易被忽略的能力：<strong>GitHub Issue 本身可以被用户订阅</strong>。</p>
<p>本篇文章实现一套不依赖第三方推送平台的方案：读者订阅一个公开 Issue；当 <code>posts/</code> 中出现新的 Markdown 文章或文章更新时，GitHub Actions 自动在该 Issue 下发布评论。GitHub 会再根据读者的通知设置，发送站内通知或邮件。</p>
<h2>最终效果</h2>
<p>这套方案完成后有两条清晰的路径：</p>
<ol>
<li>作者向 <code>main</code> 推送文章。</li>
<li>GitHub Actions 识别本次变更的文章，生成标题、摘要、链接和发布日期。</li>
<li>Action 在指定 Issue 下创建一条评论。</li>
<li>已点击 Issue <code>Subscribe</code> 的读者收到 GitHub 通知。</li>
</ol>
<p>它特别适合内容以 Markdown 管理、源码公开托管在 GitHub 的个人博客。</p>
<h2>为什么选择 Issue 订阅</h2>
<p>相较于自建邮件列表、Webhook 服务或即时通讯机器人，这个方案的依赖非常少。</p>
<table>
<thead>
<tr>
<th>方式</th>
<th>优点</th>
<th>限制</th>
</tr>
</thead>
<tbody>
<tr>
<td>GitHub Issue 订阅</td>
<td>无额外服务、读者可自主订阅与取消、GitHub 负责通知</td>
<td>读者需要 GitHub 账号</td>
</tr>
<tr>
<td>RSS</td>
<td>开放标准、无需账号、适合阅读器</td>
<td>需要读者使用 RSS 阅读器</td>
</tr>
<tr>
<td>邮件列表</td>
<td>触达直接</td>
<td>需要处理邮箱、退订、隐私与投递问题</td>
</tr>
<tr>
<td>机器人推送</td>
<td>即时、互动性强</td>
<td>平台 API、Token 和群组管理成本更高</td>
</tr>
</tbody>
</table>
<p>Issue 订阅不取代 RSS，而是补充一种对 GitHub 用户更自然的订阅通道。博客可以同时保留 RSS、页面订阅入口和 Issue 更新通知。</p>
<h2>工作原理</h2>
<p>核心由四个部分组成：GitHub 的 <code>push</code> 事件、文章变更检测、Front Matter 解析，以及 Issue 评论 API。</p>
<pre><code class="hljs language-mermaid">flowchart LR
  A[推送文章到 main] --> B[GitHub Actions]
  B --> C[识别 posts/*.md 变更]
  C --> D[读取 Front Matter]
  D --> E{是否为草稿?}
  E -->|是| F[结束]
  E -->|否| G[创建 Issue 评论]
  G --> H[订阅者收到 GitHub 通知]
</code></pre>
<h3>1. 用路径过滤减少无关执行</h3>
<p>工作流只关心文章目录，因此可以在触发器中限制路径：</p>
<pre><code class="hljs language-yaml"><span class="hljs-attr">on:</span>
  <span class="hljs-attr">push:</span>
    <span class="hljs-attr">branches:</span> [<span class="hljs-string">main</span>]
    <span class="hljs-attr">paths:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">'posts/**/*.md'</span>
</code></pre>
<p>这样修改样式、图片或站点配置不会产生文章提醒，也不会浪费 Actions 执行时间。</p>
<h3>2. 对比本次推送前后的提交</h3>
<p>一次 <code>push</code> 可能包含一个或多个 commit。只比较 <code>HEAD~1</code> 与 <code>HEAD</code> 会漏掉同一次推送中较早的提交，因此应使用 GitHub 事件给出的前后 SHA：</p>
<pre><code class="hljs language-bash">git diff --name-only --diff-filter=AM <span class="hljs-string">"<span class="hljs-variable">$BEFORE_SHA</span>"</span> <span class="hljs-string">"<span class="hljs-variable">$AFTER_SHA</span>"</span> -- <span class="hljs-string">'posts/*.md'</span>
</code></pre>
<p>其中：</p>
<ul>
<li><code>--name-only</code> 只输出文件路径。</li>
<li><code>--diff-filter=AM</code> 只保留新增（<code>A</code>）与修改（<code>M</code>）的文件。</li>
<li><code>-- 'posts/*.md'</code> 仅检查 <code>posts/</code> 根目录内的 Markdown 文件。这里使用的是 Git pathspec，与工作流触发器中的 Actions glob 不是同一种匹配规则。</li>
</ul>
<p>如果仓库是首次推送，前一个 SHA 会是全零值。此时改用 <code>git show</code> 获取当前提交中的文章文件即可。</p>
<h3>3. 从 Front Matter 读取文章信息</h3>
<p>D-blog 的文章在顶部使用 YAML 风格的 Front Matter：</p>
<pre><code class="hljs language-yaml"><span class="hljs-meta">---</span>
<span class="hljs-attr">id:</span> <span class="hljs-string">github-issue-blog-subscription</span>
<span class="hljs-attr">title:</span> <span class="hljs-string">用</span> <span class="hljs-string">GitHub</span> <span class="hljs-string">Issue</span> <span class="hljs-string">和</span> <span class="hljs-string">Actions</span> <span class="hljs-string">为静态博客实现新文章订阅</span>
<span class="hljs-attr">excerpt:</span> <span class="hljs-string">不依赖第三方推送平台，实现自动的新文章提醒。</span>
<span class="hljs-attr">date:</span> <span class="hljs-number">2026-07-22</span>
<span class="hljs-attr">draft:</span> <span class="hljs-literal">false</span>
<span class="hljs-meta">---
</span></code></pre>
<p>通知脚本需要的字段很少：</p>
<ul>
<li><code>id</code>：生成文章 URL，例如 <code>/post/github-issue-blog-subscription</code>。</li>
<li><code>title</code>：评论标题。</li>
<li><code>excerpt</code>：评论中的摘要。</li>
<li><code>date</code>：展示发布日期。</li>
<li><code>draft</code>：值为 <code>true</code> 时不通知。</li>
</ul>
<p>生产环境中可以使用完整 YAML 解析器。不过为了使工作流不增加额外依赖，简单的单行字段可通过正则和逐行分割读取。对于多行 YAML、复杂嵌套对象或引号转义较多的内容，则应使用可靠的 YAML 解析库。</p>
<h3>4. 使用 <code>GITHUB_TOKEN</code> 发布评论</h3>
<p><code>actions/github-script</code> 提供了已认证的 Octokit 客户端，可以直接调用 GitHub REST API：</p>
<pre><code class="hljs language-yaml"><span class="hljs-attr">permissions:</span>
  <span class="hljs-attr">issues:</span> <span class="hljs-string">write</span>
</code></pre>
<pre><code class="hljs language-js"><span class="hljs-keyword">await</span> github.<span class="hljs-property">rest</span>.<span class="hljs-property">issues</span>.<span class="hljs-title function_">createComment</span>({
  <span class="hljs-attr">owner</span>: context.<span class="hljs-property">repo</span>.<span class="hljs-property">owner</span>,
  <span class="hljs-attr">repo</span>: context.<span class="hljs-property">repo</span>.<span class="hljs-property">repo</span>,
  <span class="hljs-attr">issue_number</span>: <span class="hljs-title class_">Number</span>(process.<span class="hljs-property">env</span>.<span class="hljs-property">ISSUE_NUMBER</span>),
  body,
});
</code></pre>
<p>这里不需要个人访问令牌。GitHub 会自动为工作流注入 <code>secrets.GITHUB_TOKEN</code>。当前实现只需 <code>issues: write</code>，用于创建 Issue 评论。</p>
<h2>实现教程</h2>
<p>下面以 D-blog 的结构为例，从零配置一套可用的提醒工作流。</p>
<h3>第一步：创建订阅 Issue</h3>
<p>在仓库中创建一个公开 Issue，例如标题为“D-blog 更新订阅”。读者打开该 Issue 后点击 <strong>Subscribe</strong>，后续 Issue 评论就会进入其 GitHub 通知中心。</p>
<p>例如 D-blog 的订阅入口是：</p>
<pre><code class="hljs language-text">https://github.com/ououduck/D-blog/issues/6
</code></pre>
<p>Issue 编号是 URL 最后的数字，本例为 <code>6</code>。</p>
<h3>第二步：确定接收通知的 Issue</h3>
<p>D-blog 的工作流默认将提醒发送到 Issue <code>#6</code>，因此不需要额外创建仓库变量即可使用：</p>
<pre><code class="hljs language-yaml"><span class="hljs-attr">env:</span>
  <span class="hljs-attr">ISSUE_NUMBER:</span> <span class="hljs-string">${{</span> <span class="hljs-string">vars.BLOG_NOTIFY_ISSUE_NUMBER</span> <span class="hljs-string">||</span> <span class="hljs-string">'6'</span> <span class="hljs-string">}}</span>
</code></pre>
<p><code>BLOG_NOTIFY_ISSUE_NUMBER</code> 是可选的覆盖项。只有在未来需要更换订阅 Issue 时，才在仓库的 <code>Settings</code> -> <code>Secrets and variables</code> -> <code>Actions</code> -> <code>Variables</code> 中创建它：</p>
<pre><code class="hljs language-text">Name: BLOG_NOTIFY_ISSUE_NUMBER
Value: 12
</code></pre>
<p>配置后，通知会发送到 Issue <code>#12</code>；未配置时则始终发送到默认的 Issue <code>#6</code>。</p>
<h3>第三步：创建工作流文件</h3>
<p>在仓库新增 <code>.github/workflows/notify-post-update.yml</code>：</p>
<pre><code class="hljs language-yaml"><span class="hljs-attr">name:</span> <span class="hljs-string">Notify</span> <span class="hljs-string">Blog</span> <span class="hljs-string">Post</span> <span class="hljs-string">Update</span>

<span class="hljs-attr">on:</span>
  <span class="hljs-attr">push:</span>
    <span class="hljs-attr">branches:</span> [<span class="hljs-string">main</span>]
    <span class="hljs-attr">paths:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">'posts/**/*.md'</span>
  <span class="hljs-attr">workflow_dispatch:</span>
    <span class="hljs-attr">inputs:</span>
      <span class="hljs-attr">post_files:</span>
        <span class="hljs-attr">description:</span> <span class="hljs-string">'Markdown paths to notify, separated by new lines'</span>
        <span class="hljs-attr">required:</span> <span class="hljs-literal">true</span>
        <span class="hljs-attr">type:</span> <span class="hljs-string">string</span>

<span class="hljs-attr">jobs:</span>
  <span class="hljs-attr">notify:</span>
    <span class="hljs-attr">runs-on:</span> <span class="hljs-string">ubuntu-latest</span>
    <span class="hljs-attr">permissions:</span>
      <span class="hljs-attr">issues:</span> <span class="hljs-string">write</span>

    <span class="hljs-attr">steps:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-attr">uses:</span> <span class="hljs-string">actions/checkout@v5</span>
        <span class="hljs-attr">with:</span>
          <span class="hljs-attr">fetch-depth:</span> <span class="hljs-number">0</span>
</code></pre>
<p>这里的 <code>fetch-depth: 0</code> 会取得完整 Git 历史。虽然比浅克隆稍多一些数据，但能保证 <code>before</code> SHA 在同一次包含多个 commit 的 push 中可用于比较，逻辑更可靠。</p>
<h3>第四步：识别文章并发布通知</h3>
<p>推荐把多篇文章合并为一条 Issue 评论。一次批量发布三篇文章时，读者只会收到一条通知，评论中列出全部文章。</p>
<p>当前实现将变更检测、草稿过滤和评论发布放在同一个 <code>actions/github-script</code> 步骤中。这样只要匹配到非草稿文章，Action 就一定会调用评论 API；若权限或 API 调用失败，工作流会明确失败，不会再出现绿色成功但没有评论的情况。</p>
<p>下面是核心逻辑的简化版：</p>
<pre><code class="hljs language-js"><span class="hljs-keyword">const</span> childProcess = <span class="hljs-built_in">require</span>(<span class="hljs-string">'child_process'</span>);
<span class="hljs-keyword">const</span> fs = <span class="hljs-built_in">require</span>(<span class="hljs-string">'fs'</span>);

<span class="hljs-keyword">const</span> files = childProcess
  .<span class="hljs-title function_">execFileSync</span>(<span class="hljs-string">'git'</span>, [<span class="hljs-string">'diff'</span>, <span class="hljs-string">'--name-only'</span>, <span class="hljs-string">'--diff-filter=AM'</span>, beforeSha, afterSha, <span class="hljs-string">'--'</span>, <span class="hljs-string">'posts/*.md'</span>], { <span class="hljs-attr">encoding</span>: <span class="hljs-string">'utf8'</span> })
  .<span class="hljs-title function_">split</span>(<span class="hljs-regexp">/\r?\n/</span>)
  .<span class="hljs-title function_">filter</span>(<span class="hljs-title class_">Boolean</span>);

<span class="hljs-keyword">const</span> posts = files.<span class="hljs-title function_">flatMap</span>(<span class="hljs-function">(<span class="hljs-params">file</span>) =></span> {
  <span class="hljs-keyword">const</span> content = fs.<span class="hljs-title function_">readFileSync</span>(file, <span class="hljs-string">'utf8'</span>);
  <span class="hljs-keyword">const</span> frontmatter = <span class="hljs-title function_">getFrontmatter</span>(content);

  <span class="hljs-keyword">if</span> (frontmatter.<span class="hljs-property">draft</span> === <span class="hljs-string">'true'</span> || !frontmatter.<span class="hljs-property">id</span>) <span class="hljs-keyword">return</span> [];

  <span class="hljs-keyword">return</span> [{
    <span class="hljs-attr">title</span>: frontmatter.<span class="hljs-property">title</span> || frontmatter.<span class="hljs-property">id</span>,
    <span class="hljs-attr">excerpt</span>: frontmatter.<span class="hljs-property">excerpt</span>,
    <span class="hljs-attr">date</span>: frontmatter.<span class="hljs-property">date</span>,
    <span class="hljs-attr">url</span>: <span class="hljs-string">`https://www.pldduck.com/post/<span class="hljs-subst">${<span class="hljs-built_in">encodeURIComponent</span>(frontmatter.id)}</span>`</span>,
  }];
});

<span class="hljs-keyword">let</span> body = <span class="hljs-string">'## D-blog has new article updates\n\n'</span>;
<span class="hljs-keyword">for</span> (<span class="hljs-keyword">const</span> post <span class="hljs-keyword">of</span> posts) {
  body += <span class="hljs-string">`### [<span class="hljs-subst">${post.title}</span>](<span class="hljs-subst">${post.url}</span>)\n\n`</span>;
  <span class="hljs-keyword">if</span> (post.<span class="hljs-property">excerpt</span>) body += <span class="hljs-string">`<span class="hljs-subst">${post.excerpt}</span>\n\n`</span>;
  <span class="hljs-keyword">if</span> (post.<span class="hljs-property">date</span>) body += <span class="hljs-string">`Published: <span class="hljs-subst">${post.date}</span>\n\n`</span>;
}
</code></pre>
<p><code>encodeURIComponent</code> 不应省略。虽然 D-blog 的 <code>id</code> 通常是英文 slug，但对 URL 参数进行编码是稳妥的默认做法。</p>
<h3>第五步：授予工作流权限</h3>
<p>除了 YAML 中的 <code>permissions</code>，还需要在仓库设置中允许工作流令牌写入：</p>
<ol>
<li>打开 <code>Settings</code> -> <code>Actions</code> -> <code>General</code>。</li>
<li>找到 <code>Workflow permissions</code>。</li>
<li>选择 <code>Read and write permissions</code>。</li>
<li>保存设置。</li>
</ol>
<p>如果只保留只读权限，创建 Issue 评论会收到 <code>Resource not accessible by integration</code> 一类的错误。</p>
<h3>第六步：手动验证通知</h3>
<p>除了在推送文章时自动执行，工作流也支持在 GitHub Actions 页面手动运行。打开 <code>Notify Blog Post Update</code>，点击 <strong>Run workflow</strong>，选择 <code>main</code> 分支，并在 <code>Markdown paths to notify</code> 中填写一个文章路径，例如：</p>
<pre><code class="hljs language-text">posts/github-issue-blog-subscription.md
</code></pre>
<p>这会立即针对该文章创建一条 Issue 评论，是检查权限、订阅入口与通知设置的最快方式。</p>
<p>手动输入时每行填写一个相对于仓库根目录的路径，不要加反引号、引号或项目 URL。工作流会自动清理每行首尾空格，因此下面两种多篇文章写法都可用：</p>
<pre><code class="hljs language-text">posts/github-issue-blog-subscription.md
posts/cf-tunnel.md
</code></pre>
<h3>第七步：通过日志确认结果</h3>
<p>工作流的 <code>Post article notification</code> 步骤会输出关键诊断信息。正常执行时，日志会依次出现：</p>
<pre><code class="hljs language-text">Changed post files: posts/github-issue-blog-subscription.md
Notification posted: https://github.com/ououduck/D-blog/issues/6#issuecomment-...
</code></pre>
<p>第二行给出的 URL 就是新建评论的永久链接。出现以下提示时可按对应方式处理：</p>
<table>
<thead>
<tr>
<th>日志提示</th>
<th>含义与处理方式</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>Skipping draft</code></td>
<td>文章包含 <code>draft: true</code>，将其设为 <code>false</code> 或移除该字段后重新推送。</td>
</tr>
<tr>
<td><code>file does not exist in this workflow checkout</code></td>
<td>手动填写的路径不存在或分支选择错误，检查路径、大小写和运行分支。</td>
</tr>
<tr>
<td><code>Missing id front matter</code></td>
<td>工作流会用文件名作为临时 slug；建议补充 <code>id</code>，确保文章链接与站点路由一致。</td>
</tr>
<tr>
<td><code>No published posts to notify</code></td>
<td>本次没有识别到可发布文章。检查推送是否修改了 <code>posts/*.md</code>，或手动触发时是否填了文章路径。</td>
</tr>
<tr>
<td><code>Resource not accessible by integration</code></td>
<td><code>GITHUB_TOKEN</code> 没有 Issue 写权限，到仓库 Actions 设置中启用 <strong>Read and write permissions</strong>。</td>
</tr>
</tbody>
</table>
<h2>给读者的订阅入口</h2>
<p>自动通知做好后，最容易遗漏的是让读者知道它的存在。建议在以下位置放置相同链接：</p>
<ul>
<li>页脚：让全站访问者都能找到。</li>
<li>关于页：适合介绍博客订阅方式。</li>
<li>文章底部：读者刚读完内容时，订阅意愿通常最高。</li>
</ul>
<p>按钮链接应直接指向 Issue：</p>
<pre><code class="hljs language-html"><span class="hljs-tag">&#x3C;<span class="hljs-name">a</span>
  <span class="hljs-attr">href</span>=<span class="hljs-string">"https://github.com/ououduck/D-blog/issues/6"</span>
  <span class="hljs-attr">target</span>=<span class="hljs-string">"_blank"</span>
  <span class="hljs-attr">rel</span>=<span class="hljs-string">"noopener noreferrer"</span>
></span>
  前往订阅
<span class="hljs-tag">&#x3C;/<span class="hljs-name">a</span>></span>
</code></pre>
<p>读者只需登录 GitHub，打开该 Issue 并点击 <strong>Subscribe</strong>。取消订阅同样由 GitHub 原生提供，不需要网站存储用户信息。</p>
<h2>常见问题</h2>
<h3>只修改旧文章，也会发通知吗？</h3>
<p>会。当前方案的目标是“文章更新提醒”，新增与修改的 Markdown 都会被识别。如果只希望首次发布时提醒，需要额外比较文件在 <code>BEFORE_SHA</code> 中是否存在：不存在才视为新文章。</p>
<h3>草稿为什么没有提醒？</h3>
<p>这是预期行为。<code>draft: true</code> 的文章通常尚未公开，通知脚本会主动跳过它。将草稿改为 <code>draft: false</code> 后推送，便会成为可提醒的发布内容。</p>
<h3>为什么我没有收到邮件？</h3>
<p>先确认 Issue #6 中已经出现由 <code>github-actions[bot]</code> 创建的新评论，再确认已在目标 Issue 上点击 Subscribe。最后检查 GitHub 的 Notifications 设置：GitHub 可以只显示站内通知，也可以按仓库、参与情况或关注状态发送邮件。Issue 评论已创建但没有邮件时，通常是个人通知渠道配置，而不是博客工作流问题。</p>
<h3>我需要配置仓库变量吗？</h3>
<p>不需要。D-blog 默认使用 Issue <code>#6</code>。只有需要迁移订阅入口时，才配置可选变量 <code>BLOG_NOTIFY_ISSUE_NUMBER</code>。变量值只应是数字，例如 <code>12</code>，不要填写 <code>#12</code> 或完整 URL。</p>
<h3>为什么工作流成功却没有评论？</h3>
<p>先查看 <code>Post article notification</code> 步骤的 <code>Changed post files</code> 输出，确认它识别到正确的文件。然后检查文章不是 <code>draft: true</code>。当前工作流会在文件不存在、没有可发布文章或评论 API 没有权限时明确失败并显示具体原因，不会再静默跳过。</p>
<h2>安全与维护建议</h2>
<ul>
<li>不要将个人访问令牌写入工作流。优先使用 GitHub 自动提供的 <code>GITHUB_TOKEN</code>。</li>
<li>为默认订阅 Issue 提供回退值，并用可选仓库变量覆盖它，避免遗漏配置导致工作流静默跳过通知。</li>
<li>不要将用户邮箱或订阅名单提交到仓库。Issue 订阅由 GitHub 管理，博客不需要保存个人数据。</li>
<li>使用最小权限。当前只需要 <code>issues: write</code>；不要额外授予仓库写入或个人访问令牌权限。</li>
<li>为脚本保留草稿过滤与缺失 <code>id</code> 的告警，避免未完成文章或格式错误文章被推送出去。</li>
</ul>
<h2>总结</h2>
<p>GitHub Issue + GitHub Actions 的文章订阅方案并不复杂：用 <code>push</code> 事件捕获文章变更，读取 Markdown 元数据，调用 Issue 评论 API，再由 GitHub 完成通知分发。</p>
<p>它的价值不在于替代 RSS，而在于为 GitHub 用户提供一个零额外注册、零额外服务、可随时取消的订阅方式。对于源码与内容都在 GitHub 中维护的静态博客，这是一条足够轻量、可维护且透明的更新通知链路。</p></article>]]></content:encoded>
      <pubDate>Wed, 22 Jul 2026 00:00:00 GMT</pubDate>
      <atom:updated>2026-07-22T00:00:00.000Z</atom:updated>
      <category domain="category">教程</category>
      <category domain="tag">GitHub Actions</category>
      <category domain="tag">GitHub Issue</category>
      <category domain="tag">静态博客</category>
      <category domain="tag">自动化</category>
      <category domain="tag">RSS</category>
      <author>跑路的duck</author>
    </item>
    <item>
      <title><![CDATA[Deepseek V4众望所归]]></title>
      <link>https://www.pldduck.com/post/deepseek-v4</link>
      <guid isPermaLink="true">https://www.pldduck.com/post/deepseek-v4</guid>
      <description><![CDATA[时隔一年半 Deepseek V4 众望所归]]></description>
      <content:encoded><![CDATA[<article><h1>时隔一年半 deepseek v4 众望所归</h1>
<hr>
<h2>这次官网上线分为了两个版本 并且一如既往的开源</h2>
<h3>分别为 <strong>Pro</strong> 和 <strong>Flash</strong> 版本</h3>
<p><strong>Pro</strong> 是 <strong>1.6T</strong> 总参 <strong>49B</strong> 激活<br>
<strong>Flash</strong> 是 <strong>284B</strong> 总参 <strong>13B</strong> 激活<br>
<a href="https://img.pldduck.com/20260812115919451.png" title="DeepSeek介绍" target="_blank" rel="noopener noreferrer"><img src="https://img.pldduck.com/20260812115919451.png" alt="DeepSeek介绍"></a><br>
<strong>两档都支持1M上下文</strong><br>
<strong>相较于上一代 V3 仍有明显提升</strong></p>
<hr>
<h2>话不多说 先看看价格</h2>
<p><a href="https://img.pldduck.com/20260812115919452.png" title="DeepSeek-V4价格" target="_blank" rel="noopener noreferrer"><img src="https://img.pldduck.com/20260812115919452.png" alt="DeepSeek-V4价格"></a><br>
<strong>其实我觉得这个价格 特别是现在打折的话 还是挺便宜的</strong></p>
<hr>
<h2>重点：这次DeepSeek-V4开创了一种全新的注意力机制</h2>
<p><strong>DeepSeek-V4 开创了一种全新的注意力机制，在 token 维度进行压缩，结合 DSA 稀疏注意力（DeepSeek Sparse Attention），实现了全球领先的长上下文能力，并且相比于传统方法大幅降低了对计算和显存的需求</strong></p>
<ul>
<li>
<p>相比于上一代V3.2降低了3-10倍的算力需求<br>
<a href="https://img.pldduck.com/20260812115919453.png" title="DeepSeek-V4算力需求降低" target="_blank" rel="noopener noreferrer"><img src="https://img.pldduck.com/20260812115919453.png" alt="DeepSeek-V4算力需求降低"></a></p>
</li>
<li>
<p>V4-Pro 比 V3.2 降低 3.7 倍</p>
</li>
<li>
<p>V4-Flash 比 V3.2 降低 9.8 倍<br>
<strong>也就是说 在相同算力下 可以处理更多请求(硅基流动狂喜 bushi)</strong></p>
</li>
</ul>
<hr>
<h2>接下来看跑分(仅作参考 你懂的)</h2>
<p><a href="https://img.pldduck.com/20260812115919454.png" title="DeepSeek-V4跑分" target="_blank" rel="noopener noreferrer"><img src="https://img.pldduck.com/20260812115919454.png" alt="DeepSeek-V4跑分"></a></p>
<hr>
<h2>总结</h2>
<p><strong>时隔一年半 Deepseek V4 不负众望</strong><br>
<strong>也是拿出了自己的实力 V4在各个方面都有不小的提升 尤其是算力方面 我认为是非常不错的</strong></p></article>]]></content:encoded>
      <pubDate>Fri, 24 Apr 2026 00:00:00 GMT</pubDate>
      <atom:updated>2026-08-03T00:00:00.000Z</atom:updated>
      <category domain="category">随笔</category>
      <category domain="tag">AI</category>
      <category domain="tag">人工智能</category>
      <category domain="tag">DeepSeek-V4</category>
      <category domain="tag">DeepSeek</category>
      <category domain="tag">深度求索</category>
      <author>跑路的duck</author>
    </item>
    <item>
      <title><![CDATA[CentOS 宝塔面板无法安装 Swoole5 解决教程]]></title>
      <link>https://www.pldduck.com/post/centos-bt-install-swoole5</link>
      <guid isPermaLink="true">https://www.pldduck.com/post/centos-bt-install-swoole5</guid>
      <description><![CDATA[教你解决CentOS 宝塔面板无法安装 Swoole5 的问题]]></description>
      <content:encoded><![CDATA[<article><h1>CentOS 宝塔面板无法安装 Swoole5 解决教程</h1>
<h2>教程介绍</h2>
<h2>最近在给程序部署环境时，发现<strong>CentOS 系统下的宝塔面板始终无法安装 Swoole 5</strong>，无论怎么点击安装都会失败。
排查后发现并不是 Swoole 本身问题，而是系统缺少相关依赖库。于是整理了这篇教程，记录完整解决过程，方便遇到同样问题的朋友参考。</h2>
<h2>问题现象</h2>
<p>在宝塔面板中安装 Swoole5 时：</p>
<ul>
<li>安装失败</li>
<li>编译报错</li>
<li>提示缺少依赖</li>
</ul>
<p>通常是因为系统缺少<code>brotli-devel</code> 相关依赖。</p>
<hr>
<h2>解决步骤</h2>
<h3>第一步：更新系统：</h3>
<p>执行以下命令：</p>
<pre><code class="hljs language-bash">yum update
</code></pre>
<p><img src="https://img.pldduck.com/20260812115907072.png" alt="centos-bt-install-swoole5-step-01"></p>
<h3>⚠️注意</h3>
<p>如果执行 <code>yum update</code> 出现报错，例如：</p>
<ul>
<li>无法连接镜像源</li>
<li>DNS 解析失败</li>
<li>下载失败</li>
</ul>
<p>说明你的 CentOS 镜像源有问题，需要自行更换国内镜像源（如阿里云、腾讯云镜像源）</p>
<p><a href="https://cn.bing.com/search?q=centos%E6%8D%A2%E6%BA%90&#x26;setmkt=zh-CN&#x26;PC=EMMX01&#x26;form=LBT003&#x26;scope=web" target="_blank" rel="noopener noreferrer">如何更换CentOS镜像源</a></p>
<hr>
<h3>第二步：安装缺失依赖</h3>
<p>执行命令安装 <code>brotli-devel</code>：</p>
<pre><code class="hljs language-bash">yum install brotli-devel
</code></pre>
<p><img src="https://img.pldduck.com/20260812115907074.png" alt="centos-bt-install-swoole5-step-02"></p>
<p>安装完成后，可使用以下命令确认依赖是否已安装：</p>
<pre><code class="hljs language-bash">rpm -q brotli-devel
</code></pre>
<hr>
<h3>第三步：重新安装 Swoole5</h3>
<p>回到宝塔面板：</p>
<ol>
<li>进入【软件商店】；</li>
<li>找到对应 PHP 版本</li>
<li>安装 Swoole5</li>
</ol>
<p>此时应该可以正常编译并安装成功：
<img src="https://img.pldduck.com/20260812115907075.png" alt="centos-bt-install-swoole5-step-03"></p>
<hr>
<h2>原因分析</h2>
<h2>Swoole 5 在编译时依赖 <code>brotli</code> 压缩库，如果系统中没有安装 <code>brotli-devel</code>，就会导致编译失败。
CentOS 默认环境中通常不会自带该依赖，因此需要手动安装：</h2>
<h2>总结</h2>
<p>如果你在 <strong>CentOS + 宝塔面板</strong> 环境下安装 Swoole5 失败，可以按照以下顺序排查：</p>
<ol>
<li>执行 <code>yum update</code></li>
<li>安装依赖 <code>yum install brotli-devel</code></li>
<li>重新安装 Swoole5</li>
</ol>
<p>基本都可以解决问题</p></article>]]></content:encoded>
      <pubDate>Tue, 24 Feb 2026 00:00:00 GMT</pubDate>
      <atom:updated>2026-02-24T00:00:00.000Z</atom:updated>
      <category domain="category">教程</category>
      <category domain="tag">Swoole5 编译报错</category>
      <category domain="tag">CentOS 安装 Swoole5</category>
      <category domain="tag">宝塔面板 Swoole 安装失败</category>
      <category domain="tag">brotli-devel 安装</category>
      <category domain="tag">PHP Swoole 扩展安装</category>
      <author>跑路的duck</author>
    </item>
    <item>
      <title><![CDATA[元宝被微信封了？]]></title>
      <link>https://www.pldduck.com/post/weixin-vs-yuanbao</link>
      <guid isPermaLink="true">https://www.pldduck.com/post/weixin-vs-yuanbao</guid>
      <description><![CDATA[腾讯内斗还是别有用心？]]></description>
      <content:encoded><![CDATA[<article><h1>微信和元宝打架？是腾讯内斗还是有意而为？</h1>
<h2>前情提要</h2>
<p><strong>这两天 腾讯元宝推出了送10亿的活动</strong><br>
<strong>于是微信 QQ等各大平台全都被元宝的红包邀请广告刷屏</strong><br>
<img src="https://img.pldduck.com/20260812115943309.jpg" alt="weixin-vs-yuanbao-ad"></p>
<h2>主要事件</h2>
<p><strong>结果......微信出手了</strong><br>
<strong>直接反手给元宝封了</strong><br>
<img src="https://img.pldduck.com/20260812115943310.jpg" alt="weixin-vs-yuanbao-ban-01"><br>
<img src="https://img.pldduck.com/20260812115943311.jpg" alt="weixin-vs-yuanbao-ban-02"></p>
<p><strong>很快 元宝也在微博发出了回应</strong><br>
<img src="https://img.pldduck.com/20260812115943312.jpg" alt="weixin-vs-yuanbao-response"></p>
<h2>观点看法</h2>
<p><strong>有的人认为这是腾讯内斗</strong><br>
<strong>有的人认为不同部门各尽其责很正常</strong><br>
<strong>也有人认为就是博流量和热度</strong></p>
<p><strong>但我看来：</strong><br>
<strong>1.确实是为了博流量</strong><br>
<strong>2.不完全是博流量 也不单单是为了彰显微信的公平</strong><br>
<strong>3.我认为最根本 最真正的原因是为了接下了其他AI软件的活动做伏笔 因为很多AI软件(如千问)都宣布要开启送福利活动 腾讯内部决定故意封禁元宝 避免之后封其他AI软件的时候被说“双标”</strong></p>
<h2>总结</h2>
<p><strong>这次事件绝非内斗或普通引流 而是为腾讯的一步好棋</strong></p>
<h2>后补-2026.2.10</h2>
<p><strong>果然猜想是正确的</strong></p></article>]]></content:encoded>
      <pubDate>Wed, 04 Feb 2026 00:00:00 GMT</pubDate>
      <atom:updated>2026-02-04T00:00:00.000Z</atom:updated>
      <category domain="category">随笔</category>
      <category domain="tag">腾讯元宝</category>
      <category domain="tag">腾讯</category>
      <category domain="tag">微信</category>
      <author>跑路的duck</author>
    </item>
    <item>
      <title><![CDATA[Cloudflare Tunnel内网穿透]]></title>
      <link>https://www.pldduck.com/post/cf-tunnel</link>
      <guid isPermaLink="true">https://www.pldduck.com/post/cf-tunnel</guid>
      <description><![CDATA[教你使用Cloudflare Tunnel轻松实现内网穿透]]></description>
      <content:encoded><![CDATA[<article><h1>使用Cloudflare Tunnel轻松实现内网穿透</h1>
<h2>引言</h2>
<p>在日常开发和运维中，我们经常遇到需要从公网访问本地网络服务的需求。传统的做法可能涉及复杂的配置或高昂的成本。然而，随着Cloudflare Tunnel的出现，这一切都变得简单而高效。</p>
<h2>Cloudflare Tunnel简介</h2>
<p>Cloudflare Tunnel通过建立一条安全、加密的隧道，将本地的服务暴露到公网，无需公网IP地址或端口转发。这不仅简化了内网穿透的过程，还提升了安全性。</p>
<h2>主要功能</h2>
<ul>
<li><strong>内网穿透</strong>：允许公网用户访问位于私有网络中的服务。</li>
<li><strong>端口转发</strong>：支持将非常规端口的请求转发至常规端口号（如80/443）。</li>
<li><strong>自动HTTPS</strong>：为您的服务自动配置SSL证书，确保数据传输的安全性。</li>
<li><strong>额外认证</strong>：为您的服务添加额外的安全层，提高访问控制。</li>
</ul>
<h2>工作原理</h2>
<p>Cloudflare Tunnel利用Cloudflare全球网络作为中介，通过运行在本地服务器上的<code>cloudflared</code>守护程序与Cloudflare云端进行通信，从而实现在公网访问本地服务的目的。</p>
<h2>开始使用Cloudflare Tunnel</h2>
<h3>前置条件</h3>
<ul>
<li>拥有一个域名，并将DNS解析托管于Cloudflare</li>
<li>内网中有一台能够运行<code>cloudflared</code>程序的服务器</li>
<li>一张国际双币信用卡（仅用于验证，不会产生费用）（其实没有也可以 有骚操作）</li>
</ul>
<h3>步骤1：注册并登录Cloudflare</h3>
<p>首先，前往<a href="https://one.dash.cloudflare.com/" target="_blank" rel="noopener noreferrer">Cloudflare Zero Trust</a>注册账号并登录。</p>
<h3>步骤2：创建Tunnel</h3>
<p><img src="https://img.pldduck.com/20260812115910248.jpg" alt="cf-tunnel-step-01">
先设置团队名称 随便写</p>
<p><img src="https://img.pldduck.com/20260812115910249.jpg" alt="cf-tunnel-step-02">
选择免费计划
然后的添加付款方式这一步很重要，有卡的直接绑定即可，反正免费，没卡的有一个骚操作，在这个页面直接关闭 重新进入<a href="https://one.dash.cloudflare.com/" target="_blank" rel="noopener noreferrer">Cloudflare Zero Trust</a>就可以跳过。</p>
<p><img src="https://img.pldduck.com/20260812115910250.jpg" alt="cf-tunnel-step-03">
最后直接在Access Tunnels中，创建一个Tunnel</p>
<p>这样创建Tunnel的步骤就完成了</p>
<h3>步骤3：安装并配置<code>cloudflared</code></h3>
<p><img src="https://img.pldduck.com/20260812115910251.jpg" alt="cf-tunnel-step-04">
根据官方指南，在您的本地服务器上安装<code>cloudflared</code>并配置Tunnel连接。以下是一个示例命令：</p>
<pre><code class="hljs language-bash">docker run --name cloudflared -d --restart unless-stop cloudflare/cloudflared:latest tunnel --no-autoupdate run --token YOUR_TOKEN_HERE
</code></pre>
<p>请记得替换<code>YOUR_TOKEN_HERE</code>为您自己的Token。</p>
<h3>步骤4：配置域名和转发规则</h3>
<p><img src="https://img.pldduck.com/20260812115910252.jpg" alt="cf-tunnel-step-05">
为你的域名配置一个子域名（Subdomain），Path 留空，URL 处填写内网服务的IP加端口号。注意 Type 处建议使用 HTTP，因为 Cloudflare 会自动为你提供 HTTPS，因此此处的转发目标可以是 HTTP 服务端口。</p>
<h3>现在，你就能通过刚刚设置的子域名直接在公网访问你的内网项目了</h3>
<h2>安全增强</h2>
<p>为了进一步提升安全性，您可以为服务添加基于Email、IP等多种方式的身份验证。</p>
<h2>结论</h2>
<p>Cloudflare Tunnel 夯完了 免费又好用 除了慢点</p></article>]]></content:encoded>
      <pubDate>Sun, 25 Jan 2026 00:00:00 GMT</pubDate>
      <atom:updated>2026-01-25T00:00:00.000Z</atom:updated>
      <category domain="category">教程</category>
      <category domain="tag">网络</category>
      <category domain="tag">Cloudflare</category>
      <category domain="tag">内网穿透</category>
      <author>跑路的duck</author>
    </item>
  </channel>
</rss>