<?xml version="1.0" encoding="UTF-8"?><rss version="2.0" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>SamHou&apos;s Blog</title><description>Sleep for a better life.</description><link>https://blog.samhou.moe/</link><language>zh-CN</language><item><title>从零开始的 Astro 博客设计</title><link>https://blog.samhou.moe/blog-to-astro/</link><guid isPermaLink="true">https://blog.samhou.moe/blog-to-astro/</guid><description>字少事儿大，我的博客进行了完全重写。一篇关于博客用 Astro 重写的文章，讨论了博客的视觉设计，对 Artalk 和 tracking 脚本进行断舍离，一些小巧思，友链动态监测，以及渐进增强的内容。最后，解释了不开源的原因。</description><pubDate>Wed, 30 Sep 2026 21:09:28 GMT</pubDate><content:encoded>&lt;p&gt;字少事儿大：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;我的博客进行了完全重写&lt;/strong&gt;。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;不是改改配置，不是插入点 html，不是应用一点 style，更不是稍微插入点 JS 脚本增强体验，而是完全从零开始的重写。&lt;/p&gt;
&lt;h2&gt;一切的起点&lt;/h2&gt;
&lt;p&gt;众所周知，我在之前，用的是现成的 SSG 方案：hexo + theme-butterfly-newbee（以防你不知道，它是 &lt;a href=&quot;https://blog.samhou.moe/hexo-fediverse/&quot;&gt;theme-butterfly 的个人 fork&lt;/a&gt;）。&lt;/p&gt;
&lt;p&gt;而让我想到「重写」的契机，则是学习前端 html + css + js 三件套（由于&lt;a href=&quot;https://blog.samhou.moe/aqua-surf-5/#liciamonitor&quot;&gt;想要写一个自己的探针&lt;/a&gt;，于是准备学一下前端页面）。既然都已经有 web 应用程序开发的必要条件，那么为何不来练练手呢？而且，现有的 Hexo 问题实在是太多了——隔一个月就十几个 npm 包漏洞，html 检查器居然会查出 2k 个警告，CSS 文件过大，甚至整个博客运行时会爆出一堆 JS ERR。&lt;/p&gt;
&lt;p&gt;当然，直接手搓 md 渲染 SSG 显然非常不现实，而且 100% 属于重复造轮子。于是想起了之前缺少 html/css/js 基础而放弃的 &lt;a href=&quot;https://astro.build/&quot;&gt;Astro&lt;/a&gt; 框架。它轻量、快速、组件编写简单，内置 markdown 渲染，可以继续使用现有的 vercel 平台，甚至可以按需要启用服务端动态渲染——对刚学会前端的我来说，是一个非常有意思的新玩意。&lt;/p&gt;
&lt;p&gt;于是，花了一个下午跟着官方的搭建 blog 教程走了一遍，稍微对这个框架有了一定的了解&lt;del&gt;也顺便补全了一些 JS 的妙妙语法&lt;/del&gt;。既然了解了 Astro 的基本用法，接下来就可以开始写一点自己的东西了……&lt;/p&gt;
&lt;h2&gt;视觉设计&lt;/h2&gt;
&lt;p&gt;不知道各位有没有看过一张梗图，大意是光鲜亮丽的前端外表后是胡乱堆砌的后端组件。是的——编写出阅读舒适的，美观的前端页面是非常重要的。其实之前学完 html/css 的时候，我已经设计过了&lt;a href=&quot;https://www.samhou.moe/&quot;&gt;主站&lt;/a&gt; 的视觉。它的配色和我的 obsidian 编辑器配色基本完全一致（是这个叫作 &lt;a href=&quot;https://github.com/insanum/obsidian_nord&quot;&gt;Obsidian Nord&lt;/a&gt; 的主题）。&lt;/p&gt;
&lt;p&gt;这种视觉风格非常舒适——既不是亮瞎眼的白纸黑字，也不是让我这种散光人士退避三舍的深黑背景。稍微加亮的背景，金色易读的标题，突出的、带有边框的引用块，容易发现的下划线链接，以及可读性高的 web 字体（和加大的字号），都是&lt;strong&gt;针对用眼优化&lt;/strong&gt;的。&lt;/p&gt;
&lt;p&gt;正在阅读这篇文章的读者，是不是也感觉读起来很舒服呢？当然，如果你有任何建议，或者觉得很丑，也可以留言给我。&lt;/p&gt;
&lt;h2&gt;断舍离&lt;/h2&gt;
&lt;p&gt;提到留言，就不得不提到我的「断舍离」了。&lt;/p&gt;
&lt;p&gt;是的，&lt;strong&gt;Artalk 评论区被我扬了&lt;/strong&gt;。不可否认，它为我带来了许多「想法的碰撞」，很多读者都给我的文章留下了属于自己的见解。但是也遇到过问题——有些滥用者利用评论区发广告导致我加强评论管理，有些朋友留下的邮箱采用了邮件无法到达的邮箱提供商，Artalk 需要大量 JS 执行经常出现网络问题……&lt;/p&gt;
&lt;p&gt;因此，在这次重写博客的时候，我没有再考虑 Artalk，而是只留下了 Hatsu（一个专为博客/ RSS 设计的 Fedi 软件）评论区。直接把博文链接贴近自己的 Fedi 软件，就可以看到对应的贴文啦。回复这个贴文，留言就会展示在文章的下方~&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;放心，各位在我这里留下的数据，也即 Artalk 的数据库，已经永久封存在我的机器上了，不用担心数据泄露问题，会好好珍藏的！&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;另一个断舍离是第三方分析脚本。之前为了对访问统计数据有更加深入的简介，博客同时接入了 cf 统计 / 自建 umami 实例 / Clarity Session 录像。它们引入了大量 JS 脚本，而且对各位读者的隐私也存在一定的问题（虽然他们自称隐私友好，但谁知道呢？）。所以这一次，我只留下了自建的 Umami，并打开了 Umami 的 Session 录像（替代 Clarity）。&lt;/p&gt;
&lt;p&gt;对于页面访问量统计的话，此前用的是 Umami 公开一个只读用户，Butterfly 主题内置调用 API 的方式。而这次我复用了个人主页上的 &lt;a href=&quot;https://github.com/journey-ad/Moe-Counter&quot;&gt;Moe-Counter&lt;/a&gt;，针对每个路径生成一个唯一的 ID 作为计数，更美观也更安全。&lt;/p&gt;
&lt;h2&gt;小巧思&lt;/h2&gt;
&lt;p&gt;在打磨新版博客设计的过程中，特意做了下面的一些{小组件|小巧思}，不知各位读者有没有发现呢？&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;博文的简介默认折叠「我就是手贱」，保持神秘感。&lt;/li&gt;
&lt;li&gt;TOC 的小标题、鼠标悬浮在链接上的平滑变色，是简单学了下过渡的结果&lt;/li&gt;
&lt;li&gt;鼠标放在链接图片上，图片会转动，来提示这是链接&lt;/li&gt;
&lt;li&gt;……还有更多，暂时不剧透了~&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;另外，博客也针对搜索引擎进行了优化，解决了多项 SEO 问题，引入了标题长度和 meta description 长度的单元测试检查。希望能提升一点搜索引擎排名……&lt;/p&gt;
&lt;h2&gt;服务端渲染&lt;/h2&gt;
&lt;p&gt;是的，astro 框架配合 Vercel 提供了服务端渲染。这就好玩了——你可以在服务端执行一些代码，实现按需运行的服务。&lt;/p&gt;
&lt;p&gt;基于此，我对友链页面进行了升级，你可以打开&lt;a href=&quot;https://blog.samhou.moe/links/&quot;&gt;友链&lt;/a&gt;尝试一下：应该可以看到每个友站的网络状态和状态码。注意，这些是在服务端进行测试的，和你当前所处的网络环境无关。&lt;/p&gt;
&lt;p&gt;如果你发现这个页面加载有些缓慢，那也是正常的，说明一段时间没人访问，而且缓存已经失效了，服务端需要重新测试友链的状态，才能把数据发回给你。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;可靠性问题&lt;/strong&gt;
这个友链测试只是一个简单的 fetch 网页内容，并计算 fetch 的延迟，受到 CDN、源站区域等多种网络因素的影响，甚至自动程序可能被拦截导致显示为网络错误。因此——仅供参考！！&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;渐进增强&lt;/h2&gt;
&lt;p&gt;一个网页最重要的是什么？除了视觉设计之外，「用户体验」也是非常重要的。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;渐进增强&lt;/strong&gt;是一种设计理念，旨在为尽可能多的用户提供基本的内容和功能，同时为能够运行所有必要代码的最现代化的浏览器用户提供最佳体验。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;这对网页提出了相当高的要求：&lt;/p&gt;
&lt;p&gt;如果用户没有开启 JS，那么还能看到基本内容吗？&lt;/p&gt;
&lt;p&gt;于是，我在编写整个网站的时候，尽量不引入任何 JS，所有的渲染都是 Astro 在构建阶段完成的。只有遇到无可避免的情况下，才使用 JS：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;博客的评论区，需要动态加载 Hatsu 上的评论内容，无法在没有 JS 的静态网站上正常运行&lt;/li&gt;
&lt;li&gt;友链监测在服务器端完成，但是加载动画的关闭用了几行 JS 实现&lt;/li&gt;
&lt;li&gt;Umami 数据分析和跟踪脚本需要 JS 才能执行，无法避免&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;因此，你现在可以看到一个非常干净的开发者控制台，网站的加载速度也非常快。&lt;/p&gt;
&lt;h2&gt;后记&lt;/h2&gt;
&lt;p&gt;对于新版的博客，我是比较满意的，不知各位读者怎么想呢？另外博客的 Atom 由于 Astro 不支持被移除了，不过 RSS 依然可用~&lt;/p&gt;
&lt;p&gt;作为亲手几乎从零开始写的，有{个人特色|代码堆屎}的东西，想了想还是不开放源代码给各位看了。毕竟，这个作品和看板娘 S 酱一样，作为「作者」，稍微有些私心，态度更像是「父母看孩子」一类，不希望别人拿走直接使用……&lt;/p&gt;
&lt;p&gt;&lt;del&gt;《水星冲浪日志 6》已经在写了，最近沉迷改网站所以拖更，各位读者应该能理解吧？（新建 markdown 文件）（逃）&lt;/del&gt;&lt;/p&gt;
</content:encoded></item><item><title>tailscale 进阶用法 一个服务绑一个域名并启用 SSL</title><link>https://blog.samhou.moe/tailscale-domain-guide/</link><guid isPermaLink="true">https://blog.samhou.moe/tailscale-domain-guide/</guid><description>本文介绍将自定义域名解析到 Tailscale IP，并用 Nginx 按 Host 反向代理多个内网 HTTP 服务，再通过 acme.sh 的 DNS 验证签发 SSL 证书，实现免端口访问的 HTTPS 内网服务。文章说明 DNS、代理和证书配置步骤，并提醒私网解析与服务范围的注意事项。</description><pubDate>Sat, 05 Sep 2026 14:10:20 GMT</pubDate><content:encoded>&lt;h2&gt;引入&lt;/h2&gt;
&lt;p&gt;众所周知，tailscale 这个工具可以把位于防火墙/ NAT 后方的机器进行组网，并且可以自动处理打洞问题尽量获得直连的效果。&lt;/p&gt;
&lt;p&gt;但是有一个非常大的问题，tailscale 官方不支持用你自己的域名！而且，即使使用官方的域名服务，启用 MagicDNS 时会造成系统 DNS 被抢夺，造成各种奇奇怪怪的网络问题……因此，我们通常都会这么做：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;sudo tailscale set --accept-dns=false
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;来禁用 DNS 抢夺。&lt;/p&gt;
&lt;p&gt;那么问题来了，我们难道真的束手无策吗？不能用上自己的域名，来访问内网 tailscale 的服务吗？&lt;/p&gt;
&lt;p&gt;当然是可以的！但是，我们需要一点小 trick……&lt;/p&gt;
&lt;p&gt;为了让你有个直观的印象，先来看看成品吧。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://img.samhou.top/1788585249929.webp&quot; alt=&quot;试试在你的浏览器里键入这个域名？&quot;&gt;&lt;/p&gt;
&lt;p&gt;如果你试着键入这个域名，是打不开的——因为这是一个运行在我的 tailnet 上的网站，只有安装了 tailscale 并加入我的 tailnet 才能访问这个网站。你可以看到，ssl 完全正常，也没有用自签名证书，并且域名后面什么端口都不用加。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;服务本身的要求&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;本文仅涉及「http 反向代理」的服务。ssh / rdp 这些需要用 Nginx 的 stream 进行代理，可自行探究，本文暂时不涉及&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;但是，在我们开始之前，必须先了解一下域名、DNS、反向代理、SSL 证书的知识。如果你已经知道这部分知识了，可以直接跳过下一章~&lt;/p&gt;
&lt;h2&gt;前置准备&lt;/h2&gt;
&lt;p&gt;放心，你不需要精通计算机网络，本文只是简略地介绍这些内容来辅助零基础的读者。比如，你不需要看下面这本书。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://img.samhou.top/1788581150957.webp&quot; alt=&quot;放心，你不需要这个&quot;&gt;&lt;/p&gt;
&lt;p&gt;让我们开始吧！&lt;/p&gt;
&lt;h3&gt;域名和 DNS&lt;/h3&gt;
&lt;p&gt;我们先来看公网的情况。&lt;/p&gt;
&lt;p&gt;你有没有想过，你键入一个域名之后，你的浏览器到底干了些什么？换句话说，它到底上哪里找网页递给你的？&lt;/p&gt;
&lt;p&gt;我们当然知道网页和 Web 程序位于一台服务器上，而每台服务器都有一个 IP 地址，浏览器对这个 IP 发出请求，服务器再把网页返回给你。&lt;/p&gt;
&lt;p&gt;而将「域名」解析为「IP 地址」的过程，就需要 DNS 服务的参与啦。当你给自己的域名设定好解析之后，全世界的 DNS 服务器就会通过逐层接力的方式，找到你设置的 IP 地址，并把它返回给请求你的网站的客户端。&lt;/p&gt;
&lt;h3&gt;反向代理&lt;/h3&gt;
&lt;p&gt;现在我们来看另外一个问题。你有台服务器，但是一台服务器上服务太多了，你想要一次开一堆网站怎么办？&lt;/p&gt;
&lt;p&gt;当然是可行的了！&lt;/p&gt;
&lt;p&gt;你既然都来到这里了，肯定知道服务可以绑定端口（tailscale ip + 端口，你肯定用过）。而给不同服务绑定不同端口，就可以实现在一个网站上开多个服务了。&lt;/p&gt;
&lt;p&gt;但是我们现在绑定了域名。如果每次都要让用户在域名后面加端口，既不美观也不方便。那么，能否通过「请求的域名」来找到对应的服务呢？比如说，请求 a.example.com 就返回 1145 端口上运行的服务，请求 b.example.com 就返回1919 端口上运行的服务，请求 samhou.moe 的时候，就返回 4433 端口上运行的服务，等等。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;小提示&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;不同二级域名也可以绑定同一个 ip。只要域名有任何字符不一样，绑定的网站就可以不同。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;让我们隆重介绍——「&lt;strong&gt;反向代理&lt;/strong&gt;」！&lt;/p&gt;
&lt;p&gt;当我们键入域名，浏览器通过 DNS 请求到服务器 ip 时，会自动在请求头中带一个 Host。你可以动手用 curl 实操试试：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;curl -v https://example.com
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;输出中肯定有类似这样的信息……&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;（省略）
&amp;gt; GET / HTTP/2
&amp;gt; Host: example.com
&amp;gt; User-Agent: curl/8.14.1
&amp;gt; Accept: */*
（省略）
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;curl 已经自动帮你把 Host 放到请求头里面了！&lt;/p&gt;
&lt;p&gt;这是一个非常、&lt;strong&gt;非常重要的&lt;/strong&gt;性质——&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;反向代理可以通过 Host 头，来判断你要访问的是服务器上的哪一个网站。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;反向代理通常运行在 80/443 端口上，根据 Host 头响应请求，将这些端口上的请求路由到对应设定的服务器。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;默认端口&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;80 443 是默认端口，因此输入域名时不需要指定它们。这就让域名变得美观了！&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Nginx 是一个老牌的 web 服务器，性能非常高，通过 config 文件的方式可以配置为反向代理。&lt;/p&gt;
&lt;p&gt;那问题来了，Nginx config 怎么配呢？&lt;/p&gt;
&lt;p&gt;先根据官方文档装好，然后打开 &lt;code&gt;/etc/nginx&lt;/code&gt;，看看默认的 &lt;code&gt;nginx.conf&lt;/code&gt; 怎么写的：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-nginx&quot;&gt;user  nginx;
worker_processes  auto;

error_log  /var/log/nginx/error.log notice;
pid        /run/nginx.pid;


events {
    worker_connections  1024;
}


http {
    include       /etc/nginx/mime.types;
    default_type  application/octet-stream;

    log_format  main  &apos;$remote_addr - $remote_user [$time_local] &quot;$request&quot; &apos;
                      &apos;$status $body_bytes_sent &quot;$http_referer&quot; &apos;
                      &apos;&quot;$http_user_agent&quot; &quot;$http_x_forwarded_for&quot;&apos;;

    access_log  /var/log/nginx/access.log  main;

    sendfile        on;
    #tcp_nopush     on;

    keepalive_timeout  65;

    #gzip  on;

    include /etc/nginx/conf.d/*.conf;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;望文生义一下，默认配置文件配置了user / log 文件 / pid 等东西，而 http 块内则有最重要的一行：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-nginx&quot;&gt;include /etc/nginx/conf.d/*.conf;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这告诉我们，真正的配置在 &lt;code&gt;conf.d&lt;/code&gt; 文件夹中。可以这么理解——该文件夹中的所有内容会「按原样复制」到这个配置文件中，生成最终的配置。&lt;/p&gt;
&lt;p&gt;而关于具体的配置，在下文 tailscale 内网配置中会涉及到，现在你只需要记住配置在这个文件夹里面写就可以了。&lt;/p&gt;
&lt;p&gt;现在嘛，我们已经扯了很久前置准备了，回到正题，tailscale 到底怎么绑定域名？&lt;/p&gt;
&lt;h2&gt;解析到 ts ip&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;动手做！&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;下面是实操，你可以跟着我动手做！&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;tailscale 在你连接上 tailnet 的时候，会自动给这台设备分配一个 tailscale ip。它是一个私有地址，在安装了 ts 的设备上，访问这个 ip 时 tailscale 会接管对这个 ip 的请求，然后把请求发送到对应的机器上。&lt;/p&gt;
&lt;p&gt;去你的域名后台增加一个解析，解析到你想要运行网站的那台设备的 tailscale ip：&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://img.samhou.top/1788585513076.webp&quot; alt=&quot;添加域名解析&quot;&gt;&lt;/p&gt;
&lt;p&gt;看到了吗？如果你用的是 cloudflare，会直接提示「保留 IP」，说明这个地址是保留的私网地址，小黄云代理会自动关闭。&lt;/p&gt;
&lt;p&gt;就这么简单！如果你的设备上已经在某个端口运行了服务，可以直接用域名 + 端口的方式访问这台设备上的对应服务。&lt;/p&gt;
&lt;p&gt;如果你依然打不开，可以检查一下自己的路由器是否会丢弃解析到私网地址的  DNS 请求。比如，openwrt 中的「网络 - DNS - 过滤器 - 重绑定保护」选项，如果打开了，会直接造成私网 DNS 解析为空的问题，需要关闭：&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://img.samhou.top/1788586353434.webp&quot; alt=&quot;关闭重绑定保护&quot;&gt;&lt;/p&gt;
&lt;p&gt;不用记下 ip 用域名已经是巨大的进步了。但是，问题是它没有 SSL 支持，虽然 tailscale 本身就已经有一层加密，但是某些程序/网页可能必须要求安全的 https 连接。而且，每次都要记下哪个程序对应哪个端口，是一件非常废脑子的事情。&lt;/p&gt;
&lt;h2&gt;反向代理&lt;/h2&gt;
&lt;p&gt;所以，该怎么解决上面两个问题？&lt;/p&gt;
&lt;p&gt;不知道你还记不记得前文提到的反向代理，如果在公网上可以这么干，为什么不直接应用到 tailscale 上呢！&lt;/p&gt;
&lt;p&gt;也就是说，访问链路是这样的：&lt;/p&gt;
&lt;p&gt;键入域名 - 请求域名 DNS 返回 ts ip - 请求 ts ip 上的 80/443 默认端口 - 目标机器上的 nginx 根据 host 头判断该交给哪个服务 - 对应服务收到你的请求，并返回内容。对，和前文提到的公网几乎就是一个道理，tailscale 只是影响了请求 ip 这一段，当你请求 ts ip 的时候代理你的请求，其余完全不变。&lt;/p&gt;
&lt;p&gt;这就简单了，我们刚刚提过反向代理的配置，现在来看看具体怎么写 config。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;化繁为简&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;本文受限于篇幅，无法讲清楚 Nginx 的所有配置。因此，只会丢出一些配置文件并指出重要的配置项。因为是 tailscale 内网比较安全，你可以抄作业。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;看看我这里的成品 &lt;code&gt;misskey.conf&lt;/code&gt;，它将 samhou.de 域名绑定到了 3000 端口的服务——&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-nginx&quot;&gt;upstream misskey {
    server 127.0.0.1:3000;
}

server {
    listen 80;
    listen [::]:80;
    server_name samhou.de;

    return 301 https://$host$request_uri;
}

server {
    listen 443 ssl;
    listen [::]:443 ssl;
    http2 on;
    server_name samhou.de;

    ssl_certificate     /etc/nginx/samhou.de.cert.pem;
    ssl_certificate_key /etc/nginx/samhou.de.key.pem;
    ssl_protocols TLSv1.2 TLSv1.3;
    ssl_session_cache shared:SSL:10m;
    ssl_session_timeout 1d;

    # Misskey default.yml maxFileSize: 250MB
    client_max_body_size 256m;

    location / {
        proxy_pass http://misskey;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection &quot;upgrade&quot;;
        proxy_read_timeout 90s;
        proxy_send_timeout 90s;
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;它做了以下事情：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;设定反向代理目标是 3000 端口的 misskey 服务&lt;/li&gt;
&lt;li&gt;80 端口绑定域名 &lt;code&gt;server_name samhou.de;&lt;/code&gt;（对，这里 server_name 就是前文所述匹配 Host 头的地方），然后 301 重定向到 443 端口（自动 http 到 https 跳转）&lt;/li&gt;
&lt;li&gt;[::] 表示 IPv6 也监听。&lt;/li&gt;
&lt;li&gt;443 端口 ssl 监听，设定证书 &lt;code&gt;ssl_certificate /etc/nginx/samhou.de.cert.pem;&lt;/code&gt; ，然后反向代理 &lt;code&gt;/&lt;/code&gt;（也就是域名根目录）到 misskey 服务 &lt;code&gt;proxy_pass http://misskey;&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;SSL&lt;/h2&gt;
&lt;p&gt;那问题来了，我们该配置什么证书呢？&lt;/p&gt;
&lt;p&gt;当然是和域名对应的有效证书啦。浏览器才不管你请求了哪个 ip，只要远程服务器返回的证书和域名匹配，那么就可以证明这台服务器的身份。&lt;/p&gt;
&lt;p&gt;对，获取证书的免费方法就是 acme。&lt;/p&gt;
&lt;p&gt;但是，由于是私网 ip，公网验证是做不到了，只能通过添加 dns 解析来验证。&lt;/p&gt;
&lt;p&gt;这部分大家应该都会，所以这里只放出几条命令，每个人的域名解析提供商都不同，可以参阅 acme 的文档来配置。&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;export CF_Token=&quot;token&quot;
export CF_Account_ID=&quot;ID&quot;
acme.sh --issue --dns dns_cf --server letsencrypt -d example.com -d &apos;*.example.com&apos;
acme.sh --install-cert -d example.com \
--key-file       /etc/nginx/example.com.key.pem  \
--fullchain-file /etc/nginx/example.com.cert.pem \
--reloadcmd     &quot;sudo service nginx reload&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;证书签发之后，重启 nginx，现在访问网站就可以看到已经升级为安全连接了。&lt;/p&gt;
&lt;h2&gt;多个网站&lt;/h2&gt;
&lt;p&gt;我们已经看了一个示例网站，那么多个网站也不在话下了，只要简单地再创建一个 &lt;code&gt;other_service.conf&lt;/code&gt; 反向代理配置文件，然后稍微复制改改就可以了（注意证书覆盖哦）：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-nginx&quot;&gt;upstream service {
    server 127.0.0.1:1145;
}
server {
    listen 443 ssl;
    listen [::]:443 ssl;
    http2 on;
    server_name a.example.com;

    ssl_certificate     /etc/nginx/example.com.cert.pem;
    ssl_certificate_key /etc/nginx/example.com.key.pem;
    ssl_protocols TLSv1.2 TLSv1.3;
    ssl_session_cache shared:SSL:10m;
    ssl_session_timeout 1d;

    location / {
        proxy_pass http://service;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection &quot;upgrade&quot;;
        proxy_read_timeout 90s;
        proxy_send_timeout 90s;
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;保存，重载 Nginx，你会发现访问 &lt;code&gt;a.example.com&lt;/code&gt; 和 &lt;code&gt;samhou.de&lt;/code&gt; 这两个网站，会分别出现 1145 和 3000 端口上运行的服务。&lt;/p&gt;
&lt;h2&gt;后记&lt;/h2&gt;
&lt;p&gt;在这篇文章里，我们把反向代理和 tailscale 结合起来用，让你可以把域名绑定到 ts 上的服务。其实这整个过程和公网并无太大区别，所以本作也可以说是「部署公网服务」的教程（笑）。&lt;/p&gt;
&lt;p&gt;&lt;del&gt;啰啰唆唆了一大堆，其实和 ts 关系不大嘛。&lt;/del&gt;&lt;/p&gt;
&lt;p&gt;总之，食用愉快~&lt;/p&gt;
</content:encoded></item><item><title>水星冲浪日志 5</title><link>https://blog.samhou.moe/aqua-surf-5/</link><guid isPermaLink="true">https://blog.samhou.moe/aqua-surf-5/</guid><description>《水星冲浪日志》的第五期，记录了博主装机的结果，开发中的探针 LiciaMonitor，NtfyDesktop 和 Avalonia 开发过程中的 Linux 体验，Windows 游戏本做串流机器的配置，初探 Pixel 手机的原因和过程，以及转向 Debian GNU/Linux 的前因后果</description><pubDate>Fri, 28 Aug 2026 10:08:56 GMT</pubDate><content:encoded>&lt;p&gt;&lt;del&gt;好热！好热啊！出门要融化了！！&lt;/del&gt;&lt;/p&gt;
&lt;p&gt;&lt;del&gt;不想开学。&lt;/del&gt;&lt;/p&gt;
&lt;p&gt;各位读者好，这里是沉迷代码的 SamHou，这次是真的久等了。&lt;/p&gt;
&lt;p&gt;上一篇《水星冲浪日志》发出去之后已经过去一个半月，开始写作本文的现在都快开学了。既然声称自己是月刊，我觉得还是更新一下比较好，&lt;del&gt;避免各位以为我失联&lt;/del&gt;。当然，素材已经足够，写一篇新的日志也不是那么困难的事情。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;提到素材，在这里插播一个{广告|自我推广}——我会在 &lt;a href=&quot;https://clanna.dev/@samhou&quot;&gt;Fediverse&lt;/a&gt; 上实时更新每天的动态，而这个日志的素材很多都来自那里，许多新鲜（但不成熟）的想法会发布「仅关注者可见」的贴文，如果等不及月刊可以直接在那里阅览。非常欢迎各位来 Fedi 和我交流讨论。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;回到正题，我记得上一期提到装机的事情，那时候还在等电脑组件到货，这次就从那里继续聊下去吧——&lt;/p&gt;
&lt;h2&gt;家里养鸡·续&lt;/h2&gt;
&lt;p&gt;在&lt;a href=&quot;https://blog.samhou.moe/aqua-surf-4/#%E5%AE%B6%E9%87%8C%E5%85%BB%E9%B8%A1&quot;&gt;前文&lt;/a&gt;已经提到过购买机器的配置，而装机过程嘛，想必来阅读这个博客的大家也没什么兴趣，所以这里就先放一张外观图片吧~&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://img.samhou.top/1787824277935.webp&quot; alt=&quot;机箱图片&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;del&gt;之所以外接了移动硬盘，是因为硬盘太贵了买不起，这个盘是作为备份用途的。&lt;/del&gt;&lt;/p&gt;
&lt;p&gt;不得不说，这个性能真的很不错，我的一堆 docker 服务跑得非常欢乐，而且看 jellyfin 转码的时候也可以用集成的显卡轻松应对。&lt;/p&gt;
&lt;p&gt;嗯对，除了硬盘。存储什么时候降价了我必须狠狠地买盘，现在根本存不下那么多&lt;del&gt;学习资料&lt;/del&gt;动画和照片。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://img.samhou.top/1787824476549.webp&quot; alt=&quot;来看看探针吧&quot;&gt;&lt;/p&gt;
&lt;p&gt;提到探针，就不得不提另外一件事了——我打算开发一个属于自己的探针。&lt;/p&gt;
&lt;h2&gt;LiciaMonitor&lt;/h2&gt;
&lt;p&gt;这节的小标题就是我正在开发的探针的名字了。&lt;/p&gt;
&lt;p&gt;你可以在&lt;a href=&quot;https://github.com/SamHou0/LiciaMonitor&quot;&gt;这里&lt;/a&gt;，视奸我的开发过程。注意这个项目现在完全不完整，可能需要很久才能开发完毕。&lt;/p&gt;
&lt;p&gt;那么，好奇的读者肯定要问了，为什么我要开发这个探针呢？事情的起因，其实是我的这个 &lt;a href=&quot;https://www.nodeseek.com/post-848945-1&quot;&gt;Nodeseek 帖子&lt;/a&gt;。&lt;a href=&quot;https://github.com/komari-monitor/komari&quot;&gt;komari-monitor&lt;/a&gt; 的「数据存储」进行了一波重构，最显著的就是「自动数据聚合」功能，这个功能导致数据颗粒度变粗了很多（数据库不再存储原始数据），而在上面的帖子里，你也可以看到 komari 的作者解释是因为性能原因才做出这个决定。但是作为一个对数据准确性有很高要求的人，我希望一个探针可以：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;存储 agent 上报的所有数据&lt;/li&gt;
&lt;li&gt;在不影响数据准确性和全面性的前提下，尽量提升性能&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;而 komari 的丢包算法显然无法做到这一点——数据聚合后，你甚至无法知道到底是哪个精确时间点发生了丢包，这是我无法容忍的。&lt;/p&gt;
&lt;p&gt;并且，经过 fedi 上的朋友的提醒，才发现 komari 这些探针在用关系型数据库存时序数据，这在大量数据场景下会产生一定的性能损耗。&lt;/p&gt;
&lt;p&gt;于是，既然不满意现成的解决方案，直接手搓不就好了吗？&lt;/p&gt;
&lt;p&gt;——当我得出这个结论时，我立刻意识到这绝对是一个需要大量学习才能完成的项目。这是因为在此之前我只有 C# 和 .NET &lt;strong&gt;桌面应用程序&lt;/strong&gt;开发的经验。这点 C# 和 XAML 完全不足以支撑我完成这个应用程序。&lt;/p&gt;
&lt;p&gt;经过与 ChatGPT 等多种 AI 工具的激烈讨论后，我得出结论，我需要：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;学习 GO 语言，来取得一种编写能够在小容量设备和 NAT 机器的极限资源情况下运行的数据收集 Agent&lt;/li&gt;
&lt;li&gt;学习前端，制作一个 web 管理面板，和 ASP.NET 进行交互&lt;/li&gt;
&lt;li&gt;学习 ASP .NET Core 和 EF Core，以用 C# 接受这些上传的数据，并对后端数据库进行操作&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;截止到本文写作的时候，第一个目标已经达成了。现在，我正在阅读《Head First HTML/CSS》来学习前端网页编写技术，尝试写出一些基础的界面。&lt;/p&gt;
&lt;p&gt;总之，这个项目还需要更多的学习，还能顺便学习前端技术栈，或许过一段时间各位读者就会发现这个网站突然大变样了也说不定。&lt;/p&gt;
&lt;p&gt;声明：本文拖更就是因为沉迷学习和编写代码。&lt;del&gt;绝对不是因为看番和玩 Gal 入迷了。&lt;/del&gt;&lt;/p&gt;
&lt;h2&gt;请通知我吧！&lt;/h2&gt;
&lt;p&gt;上一节提到了我会用 C# 和 XAML 开发桌面应用程序。对于摆脱了 Windows 的一个 .NET 开发者来说，Avalonia 是我们不得不品味的一环，可以把 WPF 技术几乎按照原样搬过来，让应用程序可以跨平台运行。不得不说，Rider + Avalonia 的开发套件已经相当成熟了，写起来非常爽。&lt;/p&gt;
&lt;p&gt;使用 Avalonia 的机会很快就来了。我发现 &lt;a href=&quot;https://ntfy.sh/&quot;&gt;ntfy&lt;/a&gt; 的自托管服务器，在套 Cloudflare 的情况下，无法支持官方 ntfy 命令行 client 的 long pulling 方式的数据流，而必须使用 websocket。一个想法在我的脑中形成——既然如此，那就直接做一个「ntfy desktop」吧！做成桌面应用，用 websocket 连接远程服务器，再用 &lt;code&gt;notify-send&lt;/code&gt; 向 Linux 桌面环境发送通知。&lt;/p&gt;
&lt;p&gt;就这样，&lt;a href=&quot;https://github.com/SamHou0/NtfyDesktop.NET&quot;&gt;NtfyDesktop.NET&lt;/a&gt; 诞生了。它是一个 Ntfy 桌面程序，允许你连接到远程服务器，收取远程 topic 的消息。&lt;/p&gt;
&lt;p&gt;现在，终于可以愉快又方便地在桌面端收取 Ntfy 的消息了！&lt;/p&gt;
&lt;p&gt;Avalonia 开发确实是一个令人愉悦的过程。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;除了 Rider 的插件&lt;/strong&gt;。具体来说，在开发过程中，我发现我的消息经常会发送两次，而且在彻底退出关闭程序后，消息发送依然会继续。我百思不得其解地诊断了好几遍多线程和 Dispose 逻辑，甚至让 AI 做了会诊，却仍旧无法得出其根源。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;我刚才也提到了是 Rider Avalonia 的插件导致的问题，结合「发两次消息」，在继续往下翻之前，各位读者可以猜猜，到底插件干了什么呢？&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;直到我发现 Avalonia 插件的设计器中列表和按钮竟然是可以点击的，谜题的答案才浮出水面：设计器并不是按照代码生成页面的预览，&lt;strong&gt;而是后台运行了一整个窗口实例&lt;/strong&gt;！而由于我们使用了 MVVM，View Model 的实例在设计器中被后台生成，而 VM 中的初始化代码会从磁盘自动加载保存的 Ntfy 服务器配置——这就导致，在启动应用程序调试时，实际上启动了&lt;strong&gt;两个实例&lt;/strong&gt;，一个是设计器的，一个是正在调试的实际运行的程序。&lt;/p&gt;
&lt;p&gt;这也揭示了上文谜题的真正原因。发送两次消息是因为设计器 + 调试中程序总共有两份；彻底停止调试后，设计器中的程序并不会停止而是永久存在（直到代码修改，但是修改后设计器会立即重载，这样的话如果代码没有语法错误&lt;strong&gt;依旧会继续运行&lt;/strong&gt;）。&lt;/p&gt;
&lt;p&gt;找到根源的时候有点气笑了，我真的连夜卸载这个插件……&lt;/p&gt;
&lt;p&gt;&lt;del&gt;喂喂喂设计器是后台运行一个实例这件事你得告诉我一下吧，整成这样 Debug 几天才发现啊！！！&lt;/del&gt;&lt;/p&gt;
&lt;h2&gt;大战网络基础设施&lt;/h2&gt;
&lt;p&gt;NAS 有了，跑 Agent 的后备家里云有了，还差什么？&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Windows 主机&lt;/strong&gt;！虽说我已彻底迁移到 GNU/Linux，并且不玩任何 PVP 需要反作弊的游戏，而且大部分游戏都可以在 GNU/Linux 上用 Wine 或者直接在 Steam 中玩，但是如果有一台可以 24h 开机的 Windows 开发机器，&lt;strong&gt;不是很酷吗&lt;/strong&gt;？于是我选定了闲置游戏本，准备送到后备家里云那边（对，后备家里云和我的台式机并不是在同一个公寓）——&lt;/p&gt;
&lt;p&gt;好处有这些：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;可以将 Office 文档传送过去编辑，最终确认格式（用于处理那些只能交 word 的课，以及各种 PPT 展示）。&lt;/li&gt;
&lt;li&gt;可以远程游戏，风扇不会吵。&lt;/li&gt;
&lt;li&gt;可以用嵌入式和移动设备进行串流，允许它们访问桌面应用程序和游戏&lt;/li&gt;
&lt;li&gt;可以将负载迁移到电费更便宜的住处，以降低能源开销（{不知为何|你自己清楚}我家电费已经最高阶梯了，好心疼钱……）&lt;/li&gt;
&lt;li&gt;可以在任何地方继续开发 windows 应用程序，或继续执行操作&lt;/li&gt;
&lt;li&gt;可以在缺少独立显卡的设备上，委托远程运行 AI 训练/推理任务&lt;/li&gt;
&lt;li&gt;和后备家里云放在一起，可以利用 WOL 内网唤醒，避免单点故障关机需要人肉运维&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;然后，不出意外地出意外了。&lt;/p&gt;
&lt;p&gt;家里有两台游戏本，家里人正在使用其中一台。其中一台网口由于之前某人（真的不是我）暴力打开后盖的原因，接口的线断了，插入 2.5G 网线交换机，只能协商到 100M；另一台网口看起来是好的，但是今天插拔几次之后就再也没有识别了，硬件上不识别，网口的灯甚至在拔出网线后还是亮的。&lt;/p&gt;
&lt;p&gt;&lt;del&gt;我觉得真可以拉黑 hp 这家公司了，他们家电脑是我见过故障最多的，鬼知道为什么家里人特别爱买，全家游戏本全是 HP 的暗影精灵，而且他家打印机也是一坨大的，网络打印就没靠谱过&lt;/del&gt;&lt;/p&gt;
&lt;p&gt;行吧，先给后者挂上 USB 网卡，在家里用起来，先进行一波稳定性测试。&lt;/p&gt;
&lt;p&gt;不测不知道，一测吓一跳：就在网口“死亡”后的十几天，我发现网口的灯突然灭了。再次插入网线之后，一切都恢复了正常，2.5 G 的协商速率回来了！&lt;/p&gt;
&lt;p&gt;&lt;del&gt;玄学问题&lt;/del&gt;&lt;/p&gt;
&lt;p&gt;既然稳定性已经在内网得到了验证，于是装上 Tailscale 送到了后备家里云那边。测试发现一切正常，无论是 Sunshine + Moonlight 串流还是 RDP，都能够稳定地连接，而由于是同一个城市 P2P 直连，有一端是 NAT1，所以几乎完全没有延迟。&lt;/p&gt;
&lt;p&gt;写作本文时是安装完毕后的第二天，目前状态良好……？&lt;/p&gt;
&lt;p&gt;&lt;del&gt;是的，我把某些垃圾应用（比如绿油油和工作绿油油）装载在那台 24h 开机的机器上，彻底避免了需要掏出手机才能登入的问题&lt;/del&gt;&lt;/p&gt;
&lt;h2&gt;Pixel Experience&lt;/h2&gt;
&lt;p&gt;&lt;del&gt;字少事大：&lt;/del&gt; 我买了台 Pixel 体验安卓。&lt;/p&gt;
&lt;p&gt;不得不说，原生安卓在不安装垃圾拉史应用（嗯，垃圾应用有哪些，你都来看我博客了，应该懂得）的前提下，是非常流畅好用的。&lt;/p&gt;
&lt;p&gt;至于为什么要买呢？这就不得不从购买之前说起了。在此之前，我的两个手机是这样组织的——Apple 手机用于养蛊，安装垃圾&lt;em&gt;拉史应用&lt;/em&gt;，而我的一加手机则刷入酷安大佬的 LineageOS 用于使用开源 App 和非拉史应用。&lt;/p&gt;
&lt;p&gt;但是，Apple 产品让我红温了好几次，几个月前我甚至还写了篇《为什么 Apple 产品应该滚出我的生活》的小文章分享在绿油油的圆形分享区中。&lt;/p&gt;
&lt;p&gt;&lt;em&gt;由于比较有攻击性，原文放在本文最后了，欢迎各位来品鉴。&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;而且考虑到 Apple 没法用 root 去掉广告，而酷安大佬的 LineageOS 非官方可能存在某些安全风险，于是彻底扔掉了 Apple 手机 + 一加 Lineage 的方案，改成了一加默认 ROM + Pixel 默认 ROM 的组合方案。&lt;/p&gt;
&lt;p&gt;顺带一提，这标题不是 Pixel Experience 这个 ROM，而是 Pixel 的 Experience（笑）。&lt;/p&gt;
&lt;p&gt;&lt;del&gt;请原谅我这个莫名其妙的笑点&lt;/del&gt;&lt;/p&gt;
&lt;p&gt;至于「查找手机」功能嘛，这个简单——有个开源的 &lt;a href=&quot;https://gitlab.com/fmd-foss/fmd-server&quot;&gt;fmd&lt;/a&gt;，你可以自己部署然后在你的设备上安装并允许后台运行，就可以随时看到设备轨迹、锁定设备、抹除数据，甚至可以用摄像头进行拍摄。&lt;/p&gt;
&lt;p&gt;既然手机都折腾成这样了，电脑系统当然也少不了——&lt;/p&gt;
&lt;h2&gt;支持 Debian 谢谢喵&lt;/h2&gt;
&lt;p&gt;在之前的&lt;a href=&quot;https://blog.samhou.moe/aqua-surf-3/#Arch-Linux&quot;&gt;文章&lt;/a&gt;里，我曾经提到过我把系统重装成了 Arch Linux。但是深度使用几个月之后，我发现一件事：&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Arch 不适合我用，我更适合 Debian&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;为什么？&lt;/p&gt;
&lt;p&gt;自从用上了 Debian，我发现我折腾系统的时间变少了。&lt;/p&gt;
&lt;p&gt;我不再像在 Arch 上一样，查 Wiki 到凌晨只是为了让某个模块工作；也不会翻阅 114514 个贴文并祈祷有人遇到过一样的问题；更不会每天 &lt;code&gt;sudo pacman -Syu&lt;/code&gt; 滚动更新既折磨自己，又浪费流量。&lt;/p&gt;
&lt;p&gt;Debian 给我的感受是，&lt;em&gt;It just works&lt;/em&gt;. 几乎所有的应用程序都会提供 deb 的预编译包（或 appimage），而安装后立即就可以用（&lt;del&gt;而不是像 AUR 经常从源码编译之后才能运行&lt;/del&gt;）。当然从源码编译可能稍微能避免安全和某些问题，但是因此浪费的算力和时间对我来说还是开销太大了。&lt;/p&gt;
&lt;p&gt;我觉得 Debian 在可用性和可折腾性中取得了一种平衡——你的系统不会随随便便爆炸，让我能把该干的事情干完；系统虽说没有 Arch 那么轻量，但开箱即用的预装恰到好处；如果你想要自己折腾一些东西，系统也允许你这么做&lt;del&gt;而不是像 Windows 一样保护过度&lt;/del&gt;。&lt;/p&gt;
&lt;p&gt;此外，你甚至可以直接在大陆买到 Debian GNU/Linux 的安装介质——&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://img.samhou.top/1787881499051.webp&quot; alt=&quot;Debian 安装介质&quot;&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;其实之前还是尝试过一下 NixOS 的，但是发现学习成本有点高了，所以就放弃转投 Debian 的怀抱啦。毕竟，我的所有服务器几乎都是 Debian 系统！&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;后记&lt;/h2&gt;
&lt;p&gt;上海的夏天确实是太热了，完全不想出门啊。&lt;/p&gt;
&lt;p&gt;暑假确实是一个适合大量学习的时间段，而我也很幸运地遇到了《Head First Go》这本书，学会了基础 Go 开发。&lt;/p&gt;
&lt;p&gt;英语四级的成绩单也到手了，我个人其实并不是非常满意（&lt;del&gt;英语免修一学期没学了&lt;/del&gt;）；买了点六级的学习资料，准备年底碰瓷六级~&lt;/p&gt;
&lt;p&gt;说实话，当我过了高考这一关之后，突然发现学习「喜欢的东西」真的是非常快乐的一件事情。&lt;/p&gt;
&lt;p&gt;我的话，除了程序设计，还是非常喜欢英语和外语的——当你逐渐学会这些语言的时候，逐渐发现自己能够读懂更多的外语资料，是非常有意思的过程嘛。&lt;/p&gt;
&lt;p&gt;&lt;del&gt;哦对了，水课给我滚出大学啊！&lt;/del&gt;&lt;/p&gt;
&lt;p&gt;&lt;del&gt;别忘了，《水星冲浪日志》被设计为轻松的栏目，因此你可以在水课上偷偷阅读。&lt;/del&gt;&lt;/p&gt;
&lt;p&gt;那么，下一期《水星冲浪日志》见~&lt;/p&gt;
&lt;h2&gt;附录 为什么 Apple 产品应该滚出我的生活&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;攻击性预警！&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;这个附录攻击性较强，包含对 Apple 这家公司的不友善的词句，请谨慎阅读！&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;为什么我觉得&amp;nbsp;Apple&amp;nbsp;设备应该滚出我的生活？&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;你的手机信号是喂狗了吗？byd 扫个共享单车半天打不开，发现整张卡就无信号。我请问了，我又不是在荒野求生，我还能处于无服务的区域吗¿我的解决方案居然是拿我的安卓给你开热点，我要你有啥用😅&lt;/li&gt;
&lt;li&gt;反人类操作。你那狗屎的返回逻辑，就不是给人用的吧？byd&amp;nbsp;左手能返回右手不行，系统还没有同一的全局返回，真的是离谱他妈给离谱开门，离谱到家了&lt;/li&gt;
&lt;li&gt;faceid，让我红温最多次的功能。我请问了，不加额外的指纹可以，但你这&amp;nbsp;faceid&amp;nbsp;纯纯一坨大的。戴口罩+头戴耳机根本就没几次能解锁，要把手机贴到眼球上才能勉强解开，口口声声说辅助功能做得好，你咋不考虑我们这些坐地铁要戴口罩保护自己，入耳耳机还不兼容耳朵的人类呢¿最关键你扫不开也就算了，你不让我输密码是何意味呢？非要等你&amp;nbsp;faceid&amp;nbsp;确认失败之后才能输入，我滑半天没法打开密码键盘&lt;/li&gt;
&lt;li&gt;你们设计师的脑子是单细胞的吗？同时插入两张卡，卡&amp;nbsp;2&amp;nbsp;作为流量，卡&amp;nbsp;1&amp;nbsp;通话，卡&amp;nbsp;1&amp;nbsp;收到电话卡&amp;nbsp;2&amp;nbsp;流量直接断开¿草履虫都比你们强，接个电话之后一段时间发不了微信，我真的会红温&lt;/li&gt;
&lt;li&gt;不识别我的手指。我是透明人还是咋滴，就你们家的设备不识别我的手指？？？好几次了，安卓从来没有这种问题，而且&amp;nbsp;Apple&amp;nbsp;的键盘一点也不流畅，相比安卓完全就是一坨巨大的屎山，开发者你们产品自己用吗？&lt;/li&gt;
&lt;/ul&gt;
</content:encoded></item><item><title>傲娇、后宫和党争——零之使魔动画点评</title><link>https://blog.samhou.moe/zero-tsukaima/</link><guid isPermaLink="true">https://blog.samhou.moe/zero-tsukaima/</guid><description>本文点评《零之使魔》动画，从各季度的观感出发，讨论露易丝等角色的傲娇属性、后宫与纯爱之间的关系、作品世界观和主线设计，以及福利桥段的处理。文章分为短评与深入分析，后半部分涉及剧情细节并含有剧透，适合看过动画或想了解作品特色的读者，也分享个人观看感受。</description><pubDate>Mon, 17 Aug 2026 12:09:31 GMT</pubDate><content:encoded>&lt;h2&gt;前言&lt;/h2&gt;
&lt;p&gt;偶然在 b 站刷到一个 Cosplay 舞蹈的文艺复兴视频，其中有一个角色不认识，于是在评论区的指点下找到了原作《零之使魔》（ゼロの使い魔）这部轻小说改动画。&lt;/p&gt;
&lt;p&gt;粉毛、傲娇、经典短裙黑丝的萌属性搭配，让我立即起了兴趣，结果一口气看完了整整四个季度，直至结局。在观看过程中和看完后，这部作品实在是让我又爱又恨，因此我稍微花了点时间写下了本文，&lt;strong&gt;有些东西实在是不吐不快&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;按照惯例，本文依旧分为「分季短评」和「分节长评」两个部分。短评的剧透基本上没有，是看完每一季度后直接书写的感受，会比较直接，即使没有观看完的读者也可以阅读；而至于后面的长评，则最好全部观看后再看——剧透比较严重。&lt;/p&gt;
&lt;h2&gt;短评&lt;/h2&gt;
&lt;p&gt;多季度作品将分每个季度进行评价：&lt;/p&gt;
&lt;p&gt;第一季度里，整个作品可以说是傲娇天花板，对 614 的刻画太戳 XP 了！整个故事还是比较有趣的，虽然是异世界但是放到现在也相当能打，小两口吵架很乐一点也不尬；恰到好处的福利我个人很喜欢。如果设定能更合理点就好了，虽然不是战斗番但是两个国家战争写成这样过家家我真的很难绷住哇&lt;/p&gt;
&lt;p&gt;而第二季则完全退步，从根本上不如上一季。恋爱喜剧不合时宜玩不明白，男主形象改太多了，小情侣吵架多到了令人反感的地步，几个配角要么查无此人要么整恶意的感情戏，而且主线也漏洞一堆。此外上一季的「现代科技」物品的世界观也没有很好的发挥，一个飞机从头用到尾，本来还指望有什么别的东西出现呢。只剩下感情线和钉宫的配音还能看看了，其他部分真的特别失望的一季。&lt;/p&gt;
&lt;p&gt;第三季依旧和第二季一样烂出了高度。最大的问题就是恋爱喜剧依旧乱写只停留在两个人反复拉扯上，而且这个男主又没有开后宫平等爱人的勇气，又没法做到纯爱天天小头控制大头太讨厌了，它最引以为傲的感情线烂完了。关于剧情和世界观的利用倒是稍微回升了一点，少了许多漏洞剧情，而且至少最后一集出现了现代的新东西，而且大飞机太酷啦，算是挽回了一点观感。&lt;/p&gt;
&lt;p&gt;第四季的话，至少……比上一季观感好。虽然它的感情线依旧很差，虽然男主又没法开后宫又小头控制大头，虽然剧情又有各种 BUG，但是本季的主线推动还算过得去，没有二三两季那么灾难。结局我个人也非常喜欢，感情终于平稳落地了啊，没有辜负我这一路赤石的坚持。&lt;/p&gt;
&lt;p&gt;&lt;em&gt;短评到此为止，接下来就要有剧透了，没有看完的读者千万不要继续前进了哟~&lt;/em&gt;&lt;/p&gt;
&lt;h2&gt;关于傲娇&lt;/h2&gt;
&lt;p&gt;露易兹（注：本文中针对女主采用该译名）作为整个作品的核心人物，一个非常重要的&lt;strong&gt;萌属性&lt;/strong&gt;就是傲娇。&lt;/p&gt;
&lt;p&gt;先来&lt;del&gt;说文解字&lt;/del&gt;拆解一下傲娇这个词语本身。傲娇傲娇，既傲又娇。这个词不是简单的「单独标签」，而是两个常见萌属性的集合。&lt;/p&gt;
&lt;p&gt;傲，也就是「傲气」，在 ACGN 中指的是角色无法坦率面对自己内心中的情感。在本作中，露易兹虽然喜欢男主，但是经常无法直率地表达出来，而是拿各种行为掩盖（如「爆炸」），并通过{作者的大手|刻意制造的矛盾}（如「亲吻到一半有别的角色突然闯入」）来增强这种「羞耻」、「不坦率」。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;del&gt;不，不要搞错了哟，写这篇点评才不是想给你们看呢，只是为了自己开心而已！&lt;/del&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;而娇，是「病娇」的缩写，也就是一种「独占欲」。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;del&gt;你刚才在看别的女孩子对吧……？从今以后，只需看着我一个人哟，你这条笨蛋狗狗！&lt;/del&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;实际上，傲娇这个词起源是日语 &lt;em&gt;ツンデレ&lt;/em&gt;：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Tsun（ツン）：源自日语拟声词“つんつん”，形容冷漠、尖锐的态度。Dere（デレ）：源自日语“でれでれ”，形容痴迷，迷恋的状态。
——摘录自「&lt;a href=&quot;https://zh.moegirl.org.cn/%E5%82%B2%E5%A8%87&quot;&gt;萌娘百科&lt;/a&gt;」&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;翻译为中文，就是「傲娇」啦。&lt;/p&gt;
&lt;p&gt;回到《零之使魔》。纵观整个四季度，傲娇这个属性简直就是贴在露易兹脸上的——无论是「才不是为了你」的不坦率态度，还是对男主&lt;del&gt;小头控制大头&lt;/del&gt;的花心行为的愤怒。&lt;/p&gt;
&lt;p&gt;这也就引出了本作第一个最大的败笔：傲娇的属性一直都在，但是作者很明显并不会写恋爱喜剧本身。恋爱喜剧是喜剧，但是喜剧怎么写是非常考验脚本的。整个四个季度中，有非常大的篇幅都在描写“欢乐的”「小两口吵架」，而为了合理化这些情节，动画的解决方式是什么呢？竟然是&lt;strong&gt;故意制造&lt;/strong&gt;让男主花心的「情境」！&lt;/p&gt;
&lt;p&gt;让我们来看一个例子：&lt;/p&gt;
&lt;p&gt;在第四季中，男主和露易兹之前吵架后男主去地下室睡觉，却突然发现暗门，内部空间居然通向公主的王宫。而公主则是对男主已经有了相当程度的好感，只是由于自己的身份注定无法与男主相爱——但是她还是由于私心向男主索吻，结果男主就亲上去了！而正好在这个时候，露易兹也发现了这里，目睹了亲亲的瞬间。&lt;/p&gt;
&lt;p&gt;——是的，这样「故意制造矛盾」的胃疼情节还不止一次。唯一正常一点的，就是第一季和第四季末尾了，至少观看流畅度是正常的，而不是被恶心的情节气哭。&lt;/p&gt;
&lt;p&gt;但是让我们再深入一点。在这些情节中，到底是什么让观众感觉到「恶心」呢？要理解这点，那就不得不提「后宫」、「党争」和「纯爱」了。&lt;/p&gt;
&lt;h2&gt;后宫？纯爱？&lt;/h2&gt;
&lt;p&gt;广义来说，我们把「一男主多女主且有全部感情线」的动画称为带有「后宫」属性的动画。&lt;/p&gt;
&lt;p&gt;如果按照这个定义来说，《零之使魔》确实是&lt;strong&gt;带有后宫属性&lt;/strong&gt;的动画。但是对于「后宫动画」来说，本作是算不上的。我们把「一男主多女主但是最后只选了正宫」的动画，称为「伪后宫」，本作也属于「伪后宫」动画。&lt;/p&gt;
&lt;p&gt;但如果只是「伪后宫」的话，不至于让观众那么难受——男主只是在做选择题罢了，最后得到哪个女孩子注定会让一些观众失望、一些观众高兴。但本作的话，无论观众喜欢哪个角色，都是愤怒的。&lt;/p&gt;
&lt;p&gt;为什么？这就要再提出「党争」这个要素了。党争，即多抢一，比如多个女主喜欢上同一个男主，但男主只能选一个。伪后宫和党争并不是冲突的，而是经常成对出现。这下我们就能指出《零之使魔》最大的问题了：&lt;strong&gt;伪后宫动画&lt;/strong&gt;中的党争是&lt;strong&gt;模糊的&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;什么意思呢？&lt;/p&gt;
&lt;p&gt;党争动画中的&lt;strong&gt;争斗&lt;/strong&gt;在本作中并不成立——很显然，作者知道露易兹是正宫，因此这个角色是有绝对优势的。这就导致其它角色靠近男主做出情侣行为时，来看露易兹的观众无疑是愤怒的。那么喜欢其它角色的观众呢？当然也是失望的——动画已经给出了「正宫是露易兹」的这种预期，其它角色一次又一次地接近男主，但必然会被露易兹赶走，在一次次的重复中消磨这些观众的信心。&lt;/p&gt;
&lt;p&gt;你可能会说，「啊我就是来看党争的，我不喜欢任何一个角色！」。那你可能也必然会失望了——当正宫已经由第一季的羁绊决定了之后，所有的党争都变成了儿戏。&lt;/p&gt;
&lt;p&gt;对于《零之使魔》的世界观和主线来说，做成党争动画绝对是不正确的，能让观众满足的是两条路：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;干脆做成真后宫&lt;/li&gt;
&lt;li&gt;&lt;del&gt;控制好男主的小头&lt;/del&gt;走纯爱路线&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;本作就是卡在这两条路线之间，刻画出来的男主既没有像「超百女友」那样平等地爱所有人的勇气，又&lt;del&gt;没法控制住自己的小头&lt;/del&gt;只爱露易兹一个人，导致了近乎灾难性的观感。&lt;/p&gt;
&lt;p&gt;说完问题，再来说说本作感情线的亮点：&lt;/p&gt;
&lt;p&gt;第一季的感情线属于是巅峰状态，男主那时候还没有那么渣男（后宫成员少 233333），对纯爱党来说较为友好；第二季 1v7w 的结尾属实让人眼前一亮；结尾和露易兹一起回日本的结局实在是让纯爱党留下了欣慰的眼泪（要是动画组早点领悟这点，也不至于观感这么差啊）。&lt;/p&gt;
&lt;h2&gt;世界观和主线设计&lt;/h2&gt;
&lt;p&gt;聊完感情线，再来说说本作的主线剧情和世界观。这块可以聊的不多，快速过一下（因为就是异世界嘛！）。&lt;/p&gt;
&lt;p&gt;说实话，第一季的时候，看到这个异世界魔法 + 现代武器穿越的世界观，我真的是稍微眼前一亮的。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;题外话，现在正在播的《黄泉的使者》和本作有许多相似之处哇，&lt;del&gt;世界观精神继承者？&lt;/del&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;我个人认为，第一季和第四季很好地发挥了这个世界观，但是中间两季实在是败笔。第二季一个飞机从头用到尾，第三季则只在最后用了个大炮。&lt;/p&gt;
&lt;p&gt;而对于主线的话，是经典的异世界套路，但是 BUG 实在是太多了我都没法列举出来。唯一眼前一亮的是老师的那部分，无论是火烧村子的「历史」，还是「赎罪」，还是制造大飞机后的「相互理解」，可以说是本作的巅峰剧情了。&lt;/p&gt;
&lt;p&gt;&lt;del&gt;你可以看爽，但是爽爽地看不太可能。&lt;/del&gt;&lt;/p&gt;
&lt;h2&gt;福利的恶意&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;本章节含有对「萌属性」和「XP」的讨论，可能不适合在公开场合阅览。&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;卖肉。&lt;/p&gt;
&lt;p&gt;福利。&lt;/p&gt;
&lt;p&gt;有性向动画经典的要素。&lt;/p&gt;
&lt;p&gt;我个人其实并不反感卖肉本身，毕竟这是表番，表番不卖肉的话就变成比较严肃的动画了。&lt;/p&gt;
&lt;p&gt;但卖肉伴随着的，本作我已经看到无数次对于「乳量」的讨论了——整个作品中充斥着一种对于「大就是好，好就是大」的宣扬，不仅男主是这样，连女主自己也因为这个而自卑。&lt;/p&gt;
&lt;p&gt;作为一个贫乳党，我个人对这些玩笑实在是非常讨厌——&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://img.samhou.top/1786941350530.webp&quot; alt=&quot;贫乳派的反攻&quot;&gt;&lt;/p&gt;
&lt;p&gt;贫乳有什么不好！？再说了，我看动画就是来看乳量的吗？玩一次梗可以，但是反复提这个，实在是让人难受。&lt;/p&gt;
&lt;p&gt;你可能会说：这是 2007 年左右的动画！老动画既有特有的趣味，也存在某些过时的媚宅元素，乳量梗只是其中一个罢了。&lt;/p&gt;
&lt;p&gt;但实际上，&lt;strong&gt;2026 年的 anemoi 依旧在玩乳量玩笑&lt;/strong&gt;，这属于 ACGN 的 XP 通病了。&lt;/p&gt;
&lt;p&gt;不得不说，&lt;del&gt;巨乳派能不能收收味&lt;/del&gt;。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://img.samhou.top/1786941666549.webp&quot; alt=&quot;Reaction&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;del&gt;看到贫乳派还有那么多支持者我就放心了。&lt;/del&gt;&lt;/p&gt;
&lt;h2&gt;后记&lt;/h2&gt;
&lt;p&gt;《零之使魔》，真的是又爱又恨的动画。可爱的露易兹，男主的决心，精心设计的世界观，在我看第一季的时候真的是眼前一亮。但后续几个季度恶意对我这个观众来说实在是太大了，缺陷实在太多，让我很难推荐所有人去看完整 4 个季度。如果你冲着钉宫的神配音和「钉宫四萌」去看的话，或许会少许有些失望了。&lt;/p&gt;
&lt;p&gt;&lt;del&gt;为什么会变成这样呢.webp&lt;/del&gt;&lt;/p&gt;
&lt;p&gt;听说轻小说版和动画版区别比较大，那就……先放入待看清单吧！&lt;/p&gt;
&lt;p&gt;&lt;del&gt;或许还能水一篇点评&lt;/del&gt;&lt;/p&gt;
</content:encoded></item><item><title>人生的意义是什么？——脏翅膀点评</title><link>https://blog.samhou.moe/aiyoku-eustia-review/</link><guid isPermaLink="true">https://blog.samhou.moe/aiyoku-eustia-review/</guid><description>本文点评视觉小说《秽翼的尤斯蒂娅》，从中世纪风格世界观、角色塑造和分线剧情谈起，讨论电车难题与人生意义等主题，并分享菲奥奈、爱莉丝、圣女、王女及真结局的个人观感。短评基本无剧透，后续长评含剧情剧透，也评述配音与音乐表现，并说明对结局的看法。</description><pubDate>Thu, 23 Jul 2026 16:07:13 GMT</pubDate><content:encoded>&lt;p&gt;久闻脏翅膀大名，终于在这个暑假捡起一口气推完，确实是一个很不错的模范型故事。&lt;/p&gt;
&lt;p&gt;照例，先来观前提示：&lt;strong&gt;短评无剧透已通关可跳过，后面的分线长评则是游玩过程中的记录重新撰写整合而来&lt;/strong&gt;。&lt;/p&gt;
&lt;h2&gt;短评&lt;/h2&gt;
&lt;p&gt;非常有意思的世界观和设定，构建了中世纪风格的独特史诗。本作对「电车难题」做了些许探讨，并在其中提出了「人生意义究竟是什么」的思考，对 gal 来说实属不易。从角色塑造上来说，王女&amp;gt;圣女&amp;gt;菲奥奈=艾莉丝&amp;gt;小动物，大家都是有血有肉的角色这点非常赞，但作为真女主的小动物却因为谜题的展开，和线性分支设计下的其它线路冲刷，在我心里留下的印象反而少，这也导致我对男主行为和结局处理持保留态度。音乐和游戏很配，男主有语音而且不是工具人好评；王女服设很好看。总之是一个值得一玩的浪漫故事。&lt;/p&gt;
&lt;p&gt;接下来是长评。&lt;/p&gt;
&lt;h2&gt;序章&lt;/h2&gt;
&lt;p&gt;作为一个看惯了王道故事，喜食甜作的的玩家来说，本作的世界观我一开始是拒绝的——属于那种腐败的中世纪风格。角色说话全是「高手过招」，一句话三个谎。&lt;/p&gt;
&lt;p&gt;但是支撑我看下去的是它的日常：没错，这就是我要看的聊天风味啊！有吐槽役的世界，加上精心设计的段子，这样的日常才是能让玩家玩下去的动力。&lt;/p&gt;
&lt;p&gt;说实话，我一开始对粉毛好感挺不错的，到被「黑羽」刀掉的那段真的吓了一跳，我甚至开始反思是不是哪里选错进 BE，结果你跟我说复活了？&lt;/p&gt;
&lt;p&gt;序章到这里差不多结束了，说实话玩到那时候粉毛在我心里的好感属于是有，但不完全有，我非常期待后续的刻画（当然期待落空了）。&lt;/p&gt;
&lt;h2&gt;菲奥奈&lt;/h2&gt;
&lt;p&gt;菲奥奈这段实际上是本作最有恋爱感的一个部分，玩到这里对金发女队长的好感真的爆棚。有正义感，还逐步成长变得圆滑……只可惜我那时并没有意识到这是分支线性流程，导致没有先进入她的线路。&lt;/p&gt;
&lt;p&gt;那个「黑羽」是哥哥我其实在前面早就猜出来了，中间插入了个副队长叛徒的剧情我还以为有什么更加深入的展开……&lt;/p&gt;
&lt;p&gt;俗话说「Gal 下限看女主，上限看男主」，到这里我觉得本作的男主塑造属实有点厉害。&lt;del&gt;谁再说想穿越到 gal 世界，就让他穿越到脏翅膀里，又会暗杀话术能力还强，多边形战士了属于是&lt;/del&gt;&lt;/p&gt;
&lt;p&gt;总而言之是个不错的故事，无论是紧张感，还是一步步探索真相，还是{热血的战斗|激烈的立绘碰撞}，很能让人看下去。&lt;/p&gt;
&lt;h2&gt;爱莉丝&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;「如果不管是谁，都有生存的意义的话」&lt;br&gt;「那么，我一定不算是个人类了吧」&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;说实话，爱莉丝这个角色一开始属于是那种，彻底搞不懂的蜜汁角色，很多行为完全就摸不着头脑。而且这个男主犹豫半天不说自己到底为啥赎买，搞得我实在心急。但从结果上来看，她的塑造是非常成功的，初看是个病娇，再看是自称「人偶」的，完全是创伤型的角色，从小就被贵族养在房间里不允许出来，只能机械地执行命令「扮演」。正是这种塑造中展开的故事，让男主和爱莉丝之间的感情才如此珍贵——男主在掩盖内心「对意义的找寻」之后遇到了爱莉丝，而这样的人偶又触动了男主的内心，让他潜意识里把「自由地找到生命意义」寄托在了爱莉丝身上，但男主却从未注意过自己的内心；而到这章最后，爱莉丝的所作所为又让男主领悟到了自己的真正想法。  &lt;/p&gt;
&lt;p&gt;经典的「互相救赎」样板不是吗？很厉害的是，这样板的展开却并不是围绕「爱莉丝」这个角色本身的，而是在两个帮派冲突的背景下，这无疑是非常高明的，不仅能让戳中 XP 的玩家感受到角色的魅力，还能让对角色无感的玩家同样欣赏到好故事。&lt;/p&gt;
&lt;p&gt;这种半「文学少女」味道，半毒舌的病娇角色真的太美味了，先过她的线路也不错~&lt;/p&gt;
&lt;h2&gt;圣女&lt;/h2&gt;
&lt;p&gt;圣女这章属于是刻板印象拉满了，无论是从早到晚的「祈祷」，还是这个教会和信仰的设计，很有「中世纪风味」的一章。其实本章节的日常一转「慢热型」，前期完全不如前几章有味道，而且有个很大缺点是缺少吐槽役，导致看得不是很起劲。&lt;/p&gt;
&lt;p&gt;但是，不得不说，后期发力还是可以的——无论是揭示圣女祈祷的无力，还是随从切断翅膀，还是“小动物”突然发力，再到圣女被处刑，人物剧情依旧是有血有肉的，看得我是非常爽，让一开始对圣女没感觉的我也来感觉了。圣女和随从这两人的羁绊，说实话是本章的亮点，处刑两人互相委托救赎的那个地方我真的掉小珍珠了。说实话，&lt;del&gt;被 key 社刀傻的&lt;/del&gt;我甚至以为真有人要被发盒饭了，特别是拉菲，她说吃不下饭那里真的有种触发关键词的感觉……不过结果是好的就行。&lt;/p&gt;
&lt;p&gt;所以为什么酒馆店长被发盒饭了啊？？完全是恶意啊！  &lt;/p&gt;
&lt;p&gt;至于双人各自的恋爱线路嘛，我觉得是很浪漫的，颇有些私奔的味道了，从牢狱里面逃出来到下层，为贵族工作……说实话这个结局或许不也挺好么？&lt;/p&gt;
&lt;h2&gt;王女&lt;/h2&gt;
&lt;p&gt;王女章节拿的是「大臣因为私情搞事情，大家合力政变构建国家」的剧本。关于王女这个角色，我第一眼（颜值上看）实在是非常喜欢。&lt;/p&gt;
&lt;p&gt;但{沃兹基硕德|我自己说的}曾经说过：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Galgame 是净化第一眼的邪念，传递角色真正魅力的地方。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;这个角色的成长是显而易见的——从被操作的放弃抵抗的「执政人偶」，到独立自主做出决策的真正国王，这样的角色是非常讨喜的。而在个人线路里，我真的感觉爱上了这个角色，让人有了「守护欲」。在另一边，这段又是模板剧本创新化的一个例证：无论是暗藏其中的主线，还是亲情和复杂的人物感情，还是「用自己眼睛和耳朵去探查真相」的主题，还是最后接过王冠时看到的那个花环的巧思（泪点低，又掉小珍珠了），一环套一环，构建起了这个发生在上层的史诗。  &lt;/p&gt;
&lt;p&gt;但当然，这段里面也有一处我个人很讨厌的地方，就是骑士团长的死。如果进了王女线，那么骑士团长还活着；但是推进主线，就极限一换一刀了这个角色？这也太不妥当了吧，这不相当于把角色的死推脱到玩家身上嘛！一般来说，如果是「剧情杀」，那么双线应该全刀；而如果是「因为做了错误的选择而死」，那么应该进入 BE 让玩家重来。但是本作却是「为了达到结局，你得死掉才行」，这样的处理我只能和酒馆老板一样，当作制作组的恶意了。  &lt;/p&gt;
&lt;p&gt;总而言之，王女线路我个人非常喜欢，在我心中的好感度比之前几个角色都要高。&lt;/p&gt;
&lt;h2&gt;真结局&lt;/h2&gt;
&lt;p&gt;走到最后，这个结局……可以说足够我发呆一会了。如果说前面的线路是开胃小菜，那么这个真结局就是饺子醋里面的醋啊。 &lt;/p&gt;
&lt;p&gt;现在回头再看，本作在终章前期探讨的首先就是「电车难题」这个问题。「杀掉一部分人，救下所有人；还是为了一个人，让全人类走向灭亡？」故事里面的不同角色都做出了不同的选择，而男主一开始选择的便是理性。  &lt;/p&gt;
&lt;p&gt;很多班友都觉得男主在终章的表现完全不达标。但我到觉得，男主的行为才是一个正常人类应该有的反应——「无力感」。是的，本作讨论的绝对不止电车难题问题本身，更多的是「意义」的追寻。「人生的意义是什么？」人类是需要赋予意义才能活下去的生物，而我们仔细想想故事里的每个人，你就会发现所有人都在追寻自己的意义——为了牢狱人民殊死一搏的吉克，追寻天使的圣女，通过治疗找到意义的爱莉丝，为了和平和人民而考虑的王女，坚守使命的菲奥奈，以及忠诚的几个随从，你能去否定任何一个人的「正确性」吗？根本不能吧！在这种情况下，设身处地地想想，男主会迷茫是当然的啊。  &lt;/p&gt;
&lt;p&gt;说句实话，相比于男主的行为，这个结局我个人倒是不太好评价。~~原来你是魔法少女小圆？~~搞到最后还是要靠天使来拯救人类，那么电车难题呢？或许因为我比较理性，我个人甚至赞同的是鲁基乌斯（哥哥）的做法，把男主最后的觉悟当作一种自我的觉醒（而非我个人可理解的行为）。  &lt;/p&gt;
&lt;p&gt;结果呢？你只抛出了论题，却用机械降神式的结尾和「爱」「羁绊」这种东西来结束，你叫我怎么接受……对电车难题的讨论完全就没有做出很好地处理。不，其实冷静下来仔细想想才发现，男主实际上确实对电车难题做出了自己的解答，他确实不再迷茫做出了选择：不惜放弃世界也要拯救自己所爱的人，只要能和她在一起就好了。但纵使如此，结尾也非常强行地结束了——是的，这是个浪漫的故事，但也正是这份浪漫，完全让对电车难题的探讨失去了意义。&lt;/p&gt;
&lt;p&gt;然而，即便抛开理性，去把它当作一个单纯的「浪漫的 Gal 故事来看」，我却依然觉得有一些异样感。是的，男女主互相献身这种剧情，理应带来情感冲击，但为什么作为观众的我在结尾内心甚至没有一点触动，甚至没法理解男主呢？仔细想了想，我的结论和班友们很不一样：&lt;strong&gt;缇娅作为真女主的塑造不够&lt;/strong&gt;啊！只在序章和终章做了展开，更多的是在介绍世界观和迷题，其他几个角色也冲淡了她的故事，导致我根本无法真正爱上这个角色。我之前也说过了，要让玩家爱上一个角色，首先剧本家要爱上自己的角色才行；但本作唯一没有把这份爱意传达给我的就是真女主线路了。其他角色在其线路里是有明显的成长的，而缇娅呢？肯定不能说没有，但是远远比不上吧？或许有人说结尾的献身就是成长，不过嘛，我个人更喜欢慢热的那种逐步铺垫的感觉，把角色塑造成有血有肉真正的「人」。而 galgame 对女主的塑造缺陷可是致命的，没法让玩家爱上角色的话，再怎么堆砌高深的世界观也显得无力了。&lt;/p&gt;
&lt;p&gt;总结一下吧，正如各位给本作打上 134 个「意义」的 user tag，能把贯穿始终的「追寻活下去的意义」这个道理用如此宏大和独特的世界观说明白已经实属不易。这是 galgame，是「美少女游戏」——我们或许没必要深究，毕竟少女恋爱才是终极目的嘛，能看到已经这么深入的主题已经非常不容易了，完全不妨碍成为 8 分佳作。&lt;/p&gt;
&lt;h2&gt;杂项&lt;/h2&gt;
&lt;p&gt;关于日常的优秀前文已有涉及，不再赘述。配音的话，味道很正，没有什么发挥失误；而音乐的话，和场景搭配起来很合适。没错，写作本文的时候我还在听 OST，越品越有味道。&lt;/p&gt;
&lt;p&gt;对于分支结局的理解，本作是开放的——你可以认为只是城市坠落之前的美好想象，也可以认为男主和他所选择的女孩子一起用别的方式渡过了危机——当然，作为 HE 爱好者我倾向于后者啦。&lt;/p&gt;
&lt;p&gt;&lt;del&gt;所以都有彩蛋了为什么不能开后宫！小孩子才做选择，我全都要！&lt;/del&gt;&lt;/p&gt;
</content:encoded></item><item><title>水星冲浪日志 4</title><link>https://blog.samhou.moe/aqua-surf-4/</link><guid isPermaLink="true">https://blog.samhou.moe/aqua-surf-4/</guid><description>《水星冲浪日志》第四期聊到博客转型与标题、内容取舍，回顾站点升级、折腾 NAS 和家用服务器的经历，也记录搭建 Matrix 聊天服务、定制 Sharkey 的过程。文章从博客写作和个人表达谈到自托管服务，分享这段时间积累的技术实践、生活近况与个人感受。</description><pubDate>Thu, 09 Jul 2026 12:07:53 GMT</pubDate><content:encoded>&lt;p&gt;好久不见，这里是刚刚看到期末分数，心情大好的站长 SamHou。&lt;/p&gt;
&lt;p&gt;&lt;del&gt;其实也没有好久不见了，差不多就一个月，而且非杂谈也在更新嘛&lt;/del&gt;&lt;/p&gt;
&lt;p&gt;本来这篇应该准时一个月的时候写完并发出的，但期末月真的太痛苦了，又是论文又是考试（不过结果是好的就行）。当然，这段时间也积累了许多可以写的素材，除了学业以外，还是很有收获的。&lt;/p&gt;
&lt;h2&gt;神秘感&lt;/h2&gt;
&lt;p&gt;你可能注意到了，这次标题里类似「速览」一样的副标题被我删了。其实之所以做出这个决定，是因为我想让文章内容&lt;strong&gt;更加充实&lt;/strong&gt;。具体来说，标题太长会影响 SEO 的最佳实践，&lt;strong&gt;所以不可避免地需要略去一些内容来进行概括&lt;/strong&gt;。但有时候再怎么概述也不能覆盖文章的所有内容，而对文章和标题都有「完美」的要求的我是无法接受这种「缺憾」的。之前写文章遇到这个问题时，我的解决方案非常简单，直接把文章一拆为二。但是作为一个月刊（&lt;del&gt;存疑&lt;/del&gt;），这种拆分是致命的：这个月没有用上一些素材，&lt;strong&gt;只会堆积得越来越多，导致最后不得不舍弃一些&lt;/strong&gt;。所以——不如直接去掉副标题好了，这样文章想要多长就可以有多长，还能把脑力放在真正的内容上，而不是考虑「&lt;em&gt;这些内容应该怎么概括？&lt;/em&gt;」的问题。至于左侧目录么，就用这个章节的关键词代替嘛。&lt;/p&gt;
&lt;p&gt;此外，在标题和目录里留下一点&lt;strong&gt;神秘感&lt;/strong&gt;和&lt;strong&gt;趣味性&lt;/strong&gt;，在正文里揭示，不也更有趣么？&lt;del&gt;真的想看剧透内容的话，可以退回主页看摘要。&lt;/del&gt;&lt;/p&gt;
&lt;p&gt;扯远了，回到「水星冲浪」的正题吧。&lt;/p&gt;
&lt;h2&gt;Happy End 和 True End&lt;/h2&gt;
&lt;p&gt;记得在我&lt;a href=&quot;https://blog.samhou.moe/aqua-surf-3/#%E7%99%BD%E5%AB%96%E4%BD%BF%E6%88%91%E5%BF%AB%E4%B9%90&quot;&gt;第一次搭建好 misskey&lt;/a&gt; 正式进入 fedi 世界的时候，就已经把「设置」翻了个底朝天了。很明显，&lt;strong&gt;机器人账号的选项&lt;/strong&gt;也不例外。在 fedi 冲浪的时候，也经常遇到各种奇怪的 bot，比如「没关系 bot」（每天都会发「没关系，能 XXX 已经很厉害了」的句子的机器人），以及各种 AI 助手的机器人。&lt;/p&gt;
&lt;p&gt;顺理成章地，「什么时候我，或者说 &lt;a href=&quot;https://clanna.dev&quot;&gt;ClanNA 档案馆&lt;/a&gt;能够有属于自己的 AI 机器人呢？」这个想法在脑海里生根发芽。&lt;/p&gt;
&lt;p&gt;自己写对我现在的技术来说，可能挑战比较大，于是我的第一想法是找现成的方案。&lt;/p&gt;
&lt;p&gt;而提到 AI Fedi 机器人，我第一个想到的就是用 AstrBot。之前曾经玩过，可惜权限管理非常差，没办法精细化管理用户对话的权限。&lt;/p&gt;
&lt;p&gt;行，让我们看看其他「原生支持 fedi」的机器人吧。第二个找到的就是 memoh。卖点是 docker 容器化、比 openclaw 更稳定、支持 fedi 和多种渠道——完美戳中我的好球区。&lt;/p&gt;
&lt;p&gt;但实际上呢？我甚至一句话都没有和 Agent 说，就已经遇到无数个奇怪的 bug 了。这似乎就是个半成品，至少在我体验的时候是这样……&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;声明：我没有不尊重开发者的意思。我自己肯定是写不出这样的项目的，开发的大佬们都非常厉害，只是这个项目不适合我使用。至于具体 bug，写作本文时，已经隔得比较久了，我只是模糊地记得 issue 里面似乎都有。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;行，「原生支持」的 Agent 看完了，再来看看非原生支持的。&lt;/p&gt;
&lt;p&gt;在此也要提一嘴龙虾 (OpenClaw)。我第一个就把它排除了——小龙虾经常改配置干死自己，极度不稳定。&lt;/p&gt;
&lt;p&gt;最后尝试的是 hermes。自动进化、会干活、有风险审批、工具多样，是一个非常有竞争力的项目（是的，直到现在，我依旧在 VPS 上留着 hermes，给了 sudo 让它作为我的个人助理）。但是不支持 misskey 接入。&lt;/p&gt;
&lt;p&gt;「但是最懂 AI 的还是 AI，我们直接让它自己写一个插件接入 misskey 不就好了？」我一开始就是这个想法，但模型性能太差了，写出来的东西完全&lt;strong&gt;没法正常在 misskey 上对话&lt;/strong&gt;——即使解决了接入插件的这个问题，依然会出现上下文太长胡言乱语、安全拦截干扰过度、记忆插件需要适配的大问题。可以说，hermes 更像是一个&lt;strong&gt;能干活的全能助理&lt;/strong&gt;，而不是一个单纯的带有记忆功能的聊天机器人，但后者才是我所需要的东西。&lt;/p&gt;
&lt;p&gt;所以，最后还是决定自己动手。但前提是得提升技术能力，之后才能写，因此这部分就不了了之了。&lt;/p&gt;
&lt;p&gt;……你或许感觉到了，拿一整个部分写一个{没有结果|BE}的故事，并&lt;strong&gt;不是这个系列的风格&lt;/strong&gt;。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;我可是{结果|HE}爱好者！&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;是的，且看另一条同步进行的时间线：&lt;a href=&quot;https://github.com/SamHou0/Bangumiss&quot;&gt;bangumiss 项目&lt;/a&gt;。&lt;/p&gt;
&lt;p&gt;这是一个四月份开始，甚至在「ClanNA 档案馆」的前身「SamHou 的小窝」上就已经在运行的 Fedi 机器人。它是一个桥接机器人——可以把你在 Bangumi 上的追番、游戏等条目的更新，转换为帖子发出在 Fediverse（仅支持 Misskey 和它的分支）。&lt;/p&gt;
&lt;p&gt;起初运行时，经常出现重复发帖、发垃圾帖子等情况，因此这个机器人只是选择了「主页」的贴文权限。而在六月份考试周的时候，我又{有兴趣|想摸鱼}去把它完善了一下，学了点 SQL 做了 SQLite 数据库的增删改查，完全避免了重复发帖，并且只会在观看状态更新时发帖来减少打扰。稳定运行一些时间后，我便把它的贴文权限转换为了「公开」，现在的用户名是 &lt;a href=&quot;https://clanna.dev/@bangumiss&quot;&gt;@bangumiss@clanna.dev&lt;/a&gt;。&lt;/p&gt;
&lt;p&gt;也就是说，虽然 AI Bot 的计划泡汤了，但是最终做出来的 Bangumiss 依旧是一个完整可运行的 FediBot，这个{结果|TE}也不算{太坏|BE}？&lt;/p&gt;
&lt;h2&gt;Custom&lt;/h2&gt;
&lt;p&gt;提到 Fediverse，就不得不提我对 &lt;a href=&quot;https://clanna.dev/&quot;&gt;ClanNA 档案馆&lt;/a&gt;，确切来说是 &lt;a href=&quot;https://joinsharkey.org/&quot;&gt;Sharkey&lt;/a&gt; 的「自定义」了。&lt;/p&gt;
&lt;p&gt;你可能会说，原版 Misskey 的功能很多，Sharkey 则是更加完善的一个版本，功能确实已经足够多了，为什么我还要继续自定义呢？&lt;/p&gt;
&lt;p&gt;这是因为，有一个非常严重的功能缺失：翻译。目前来说，Sharkey 仅仅支持 DeepL Translation 和 LibreTranslate。前者对于中国用户几乎不可能——你需要一张&lt;strong&gt;国外卡&lt;/strong&gt;才能进行免费层级的验证，且存在调用限制。后者则几乎不可用——&lt;strong&gt;翻译准确率极其低&lt;/strong&gt;，更别说是混入 emoji 和 MFM 语法的贴文了。&lt;/p&gt;
&lt;p&gt;于是，我&lt;del&gt;鞭打了 AI 黑奴，帮我&lt;/del&gt;改了源码，做出来了 Azure  Translate 的适配，这对于大学生 Azure 学生计划来说是完全免费的。成品看起来像这样：&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://img.samhou.top/1783507873734.webp&quot; alt=&quot;Azure Tanslator&quot;&gt;&lt;/p&gt;
&lt;p&gt;现在，贴文的翻译准确率大幅提升了（单纯英语可能看不大出来，但是日语/复杂格式情况下，准确率非常高，大部分情况下都能够保留正确的格式）：&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://img.samhou.top/1783508025846.webp&quot; alt=&quot;Test Message&quot;&gt;&lt;/p&gt;
&lt;p&gt;此外，或许你不知道，misskey 有内置一个 Live2D（是看板娘小蓝的 live2d 模型），我也给改成自定义的空门苍的模型了（&lt;del&gt;博客看板娘 S 酱没有 live2d 模型所以没法用&lt;/del&gt;），具体可以看我的&lt;a href=&quot;https://clanna.dev/notes/any44fkxcizr00w9&quot;&gt;这个帖子&lt;/a&gt;中的内容。&lt;/p&gt;
&lt;p&gt;效果像这样：&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://img.samhou.top/1783508351082.webp&quot; alt=&quot;Sharkey 右侧面板内嵌 Live2D&quot;&gt;&lt;/p&gt;
&lt;p&gt;好了，关于 fedi 已经讲得足够多了。关于「自定义」这个话题，其实还有别的内容可以说，也就是这个博客网站本身的自定义。不过，这部分的内容已经在「&lt;a href=&quot;https://blog.samhou.moe/hexo-fediverse/&quot;&gt;Hexo 博客接入 Fediverse —— Hatsu + Vercel 踩坑记&lt;/a&gt;」这篇文章里面详细讲过了，这里就不再提了。&lt;/p&gt;
&lt;h2&gt;Talk is NOT &quot;cheap&quot;&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;注：为了避免不必要的麻烦，下文中除了 matrix 均不会提到具体的软件名字，请自行品味。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;之前，因为线上大部分都是现实中的朋友，而他们都只用神秘绿油油软件，所以「联系方式」这个问题，在本博客上，也就只有邮箱一种方式。&lt;/p&gt;
&lt;p&gt;后来，作为一个合格的 mjj，毫无避免的，灵车商家们都会用「{那个软件|懂的都懂}」。所以，除了几个现实中的技术好友，那个 app 完全变成了一个&lt;strong&gt;购物软件&lt;/strong&gt;，每天大量的消息刷屏。&lt;/p&gt;
&lt;p&gt;而在 fediverse，我认识了许多对消息安全性有要求的人，大家都会留下一个叫作「Matrix」的联系方式。&lt;/p&gt;
&lt;p&gt;——实际上，几年前我就已经知道 Matrix 了，那会还是在做 &lt;a href=&quot;https://github.com/tldr-pages/tldr&quot;&gt;tldr&lt;/a&gt; 的汉化的时候，看到有个 matrix 聊天室的链接，于是在 matrix org 的官方 homeserver 上面注册了一个账号，加入了聊天。那时候我还不太了解，只知道 matrix 是一个小众的聊天软件，是一种开放的通信协议，在开源社区里面十分流行。而那时候我很喜欢 thunderbird 这个邮箱客户端，里面正好有 matrix 的聊天支持，但是十分得破旧，于是 matrix 就给我留下了不太好的印象。&lt;/p&gt;
&lt;p&gt;而这次重新捡起 matrix，深入研究之后，我才发现 matrix 的设计确实很有味道。首先，它支持端到端加密，所有的通信消息，即使你的账户密码泄露了，只要你没有用现有设备或恢复密钥进行验证（传递密钥），那么你开启加密的消息是无法被读取的。其次，Matrix 官方定义了一个协议，你可以自由选择服务端和客户端（和 Fedi 很像嘛），对于我这种喜欢高度自由配置的用户来说非常舒服。最后，bridge 支持非常完善，你可以把大部分平台通过 bridge 接入进来双向聊天。&lt;/p&gt;
&lt;p&gt;&lt;del&gt;绿油油软件你要不要看看你在做点什么史，学学人家 matrix&lt;/del&gt;&lt;/p&gt;
&lt;p&gt;所以，火速拿我们的老朋友 docker 起一个 &lt;a href=&quot;https://github.com/matrix-construct/tuwunel&quot;&gt;tuwunel&lt;/a&gt;（一个服务端实现），创建账号……而对于客户端，在尝试了 114514 个实现之后，我选择了电脑端 &lt;a href=&quot;https://github.com/cinnyapp/cinny&quot;&gt;Cinny&lt;/a&gt;，手机端 &lt;a href=&quot;https://fluffychat.im/&quot;&gt;FluffyChat&lt;/a&gt; 的组合，这是因为它们的 Sticker 支持相对完善，可以随意发 sticker，而且通知也会正常工作。最近 fluffychat 还修复了它那个安卓上的巨无霸字体的 bug，现在阅读流畅度好多了。&lt;/p&gt;
&lt;p&gt;好了，现在博客有了新的联系方式，你完全可以通过 &lt;a href=&quot;https://matrix.to/#/@samhou:clanna.dev&quot;&gt;Matrix&lt;/a&gt; 来联系我，享受端到端加密带来的安全感。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;本来写到这里，安利完 matrix 就要换一个话题了，但是……在这个月里，我仔细想了想——「水星冲浪日志」&lt;strong&gt;不应该是这样的&lt;/strong&gt;。
单纯堆砌折腾过的技术、发现的新项目，写出来的文章就&lt;strong&gt;太无趣&lt;/strong&gt;了。所以，这一部分还没完！&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;当我远离那个“购物软件”，用了一段时间 matrix 后，我突然明白了「做减法」的道理——&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;作为一个聊天软件，首要的功能是聊天，而不是夺取注意力的「附加功能」。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;那个大名鼎鼎的 Linus Torvalds 曾经说过这么一句话：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Talk is cheap, show me the code.（屁话少说，放码过来）&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;如果我们不「雅」，而是用大白话翻译这句话，那么会变成：「说话没有价值，给我看你的代码」。在代码界是这样，但在聊天软件中正好相反——&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;Talk is NOT cheap.&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;话语反而是最有价值的。&lt;/p&gt;
&lt;p&gt;一个聊天软件，&lt;strong&gt;必须提供最好的聊天性能&lt;/strong&gt;，让我们联系到应该联系的人，同时&lt;strong&gt;减少其它信息的打扰&lt;/strong&gt;。很显然，「那个软件」，对于 mjj 来说，没有做到这一点；而绿油油的那个，以及企鹅，更没有做到那一点（公众号、订阅号、空间、钱包……）。&lt;/p&gt;
&lt;p&gt;当然，对于前者，这不是软件本身的问题，而是其「&lt;strong&gt;生态位&lt;/strong&gt;」的问题。仔细想想就知道了，那些 VPS 商家肯定不会用绿油油，因为他们大多位于海外；而 matrix 这种，太过小众；所以「那个软件」由于其知名度和“安全性”（存疑）便成为了商家和 mjj 的明智之选。但这种生态位对我们来说真的好吗？不见得，商家和用户的交流群无时无刻在发送消息，你肯定会去看，想要不错过任何的优惠机会。&lt;/p&gt;
&lt;p&gt;再来说后者绿油油。在当下，「服务号」、「小程序」和「公众号」确实给我们带来了许多便利性。但是和那个软件一样，我们也不得不反思一下它们对我们专注力的夺取——我已经无数次在想要联系别人、干正事的时候遇到「订阅号消息」的小红点，结果点开公众号刷得无法自拔了。这次不再是「生态位」问题了，大部分是&lt;strong&gt;商业公司为了盈利所带来的结果&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;再说回 matrix。当我从那个软件迁移到 matrix，然后做好双机隔离，改成定时收取绿油油的消息后，世界都变得安静了起来。我不再去看那些降智内容、乐子内容，也不再蹲守 VPS 促销，对 mjj 的热情也恢复了正常水平，不再天天沉迷刷 nodeseek 论坛了。&lt;/p&gt;
&lt;p&gt;当然，我也知道，如果不加控制，matrix 也会终究变成夺取注意力的那一类。因此，可以说它给了我一次&lt;strong&gt;重新来过的机会&lt;/strong&gt;——matrix 我只会加必要的人、必要的群，单纯当作「&lt;strong&gt;通信工具&lt;/strong&gt;」和「&lt;strong&gt;讨论工具&lt;/strong&gt;」来使用。&lt;/p&gt;
&lt;p&gt;当然，这并不是说不欢迎你来私信打扰我。我这里的重点是，要减少垃圾信息，而人与人之间的交流，那些思维的碰撞，&lt;strong&gt;无论如何都是最宝贵的资产&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;因此，让我用修改过后的句子结束这一部分：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Talk is NOT cheap, show me your thought.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;家里养鸡&lt;/h2&gt;
&lt;p&gt;事情要从很久之前说起。&lt;del&gt;你这是什么童话故事吗&lt;/del&gt;&lt;/p&gt;
&lt;p&gt;因为家里阶梯电费已经不断升到了更高的等级，所以之前组装的「&lt;a href=&quot;https://blog.samhou.moe/aqua-surf-1/&quot;&gt;核弹小主机&lt;/a&gt;」没办法直接放在家里了，移动到了同一个城市的另外一处，那里电费阶梯比较低，可以节约 RMB。虽然遇到「主板电池没电」导致 BIOS 重置问题，炸过一次机器，但是总体上来说还是非常稳定的。&lt;/p&gt;
&lt;p&gt;于是，当我受够了在 arch 上开 Windows 虚拟机，吃掉大量系统资源还卡时，我做了一个大胆的决定。直接把吃灰多年的 8g 内存的笔记本也扔到核弹小主机那边，装好 windows 当作远程桌面使用。当然，整个过程没出任何岔子，组网就是喜闻乐见的 tailscale，无人值守状态下，禁用睡眠 rdp 也能正常连接。&lt;/p&gt;
&lt;p&gt;还没完，为了进一步节省这边的电费，我还废弃了之前的「闲置游戏本当家里云用」的方案。由于游戏本太强悍了，基本上 CPU 和 GPU 都是闲置的，性能非常过剩；而且我也实在受不了通过外接 USB 硬盘盒来连接 SATA 硬盘这种对硬盘不稳定的方式。&lt;/p&gt;
&lt;p&gt;那我的替换方案是什么呢？&lt;/p&gt;
&lt;p&gt;当然是……闲置 qnap TS-551 NAS！这台 NAS 在我受不了它的垃圾系统，而且系统爆炸无法再启动懒得折腾之后，就一直在吃灰。它用的是 J3355 CPU，内存升级到了 10G (2+8)，或许对于我的负载来说是个&lt;strong&gt;更好的低功耗选择&lt;/strong&gt;。而且它是 x86，网上有个装 fnOS 的教程，说明完全可以插入 hdmi 显示器&lt;strong&gt;当作一个正常的小主机来使用&lt;/strong&gt;。fnOS 这种我肯定是不敢用的，我选择了 openmediavault，基于 debian 的 NAS 套件，感觉非常适合我这种折腾用户来使用。一条清晰的路径都摆在那里了，那就开搞吧！&lt;/p&gt;
&lt;p&gt;你知道的，&lt;strong&gt;一般这个时候就要出事了&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;这台设备是内置一个 4G 的 dom 的，这个 dom 包含一定的系统固件，可以用来安装和启动 SATA 接口上安装的系统。但很可惜，正在拿 u 盘装系统的我显然是不知道这一点的，而人类的好奇心又是无限的——&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;欸？这怎么有个 4G 的内置存储设备，这么强！？让我试试，如果把 debian 安装在这个上面会发生什么……大不了之后再装一次，这次就当实验了。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;dom 灰飞烟灭。&lt;/p&gt;
&lt;p&gt;喜闻乐见地，debian 装好了但没完全装好。现在，机器的 bios 没法识别任何一个硬盘设备，本来存在的一个 qnap 启动项也没了，新装的 debian 也没有显示。我不信邪，又在插在 SATA 接口上的 300G 盘子安装了一遍 debian，结果依旧无法启动。&lt;/p&gt;
&lt;p&gt;后来上网查询才知道，似乎是需要 dom 中的原厂系统，才能启动 sata 硬盘笼中的外部系统。折腾到心累的我虽然确实网上下载到了 dom 的镜像，但是用等效方法在 Ubuntu CD 中向 dom 写入镜像却依旧无法恢复 qnap 启动项。而严格按照官网的教程来，则需要一台物理 Windows——身边都是 arch，根本没有（只有远程桌面）！&lt;/p&gt;
&lt;p&gt;于是，灵机一动——既然 usb 上的系统能识别，那我直接把之前的那个游戏本上的 m2 nvme 盘拆下来，买个硬盘盒，&lt;strong&gt;用 usb 插上去&lt;/strong&gt;，不就好了？&lt;/p&gt;
&lt;p&gt;火速下单 m2 硬盘盒后，等了一天很快就拿到货了。拆下之前的游戏本上的硬盘后，接入硬盘盒，然后在上面安装 openmediavault 作为系统盘。一切都很顺利——外置 usb 开机、数据复制、tailscale、远程访问……一切都很好，我甚至做好了长期使用的准备。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://img.samhou.top/1783571625553.webp&quot; alt=&quot;NAS 照片&quot;&gt;&lt;/p&gt;
&lt;p&gt;没错，就在这时又出问题了。&lt;/p&gt;
&lt;p&gt;jellyfin 突然无法访问，检查日志后发现，服务器的 sqlite 在遇到一次 disk io error 后，数据库无法识别了。&lt;/p&gt;
&lt;p&gt;我这时候还没有意识到问题的严重性，瞄了一眼 SMART 信息也没有任何异常出现，于是就打算重启一下，看看是不是哪里遇到了临时性问题。结果重启之后发现问题更加严重，系统直接起不来了。还好不是远程机器，立马拿了运维小屏幕物理接入进去。一看彻底傻眼了——系统启动项没了，&lt;strong&gt;外置 usb 的硬盘盒中的 UEFI 启动项根本就没有被 BIOS 识别出来&lt;/strong&gt;！&lt;/p&gt;
&lt;p&gt;其实这个问题在安装系统的时候曾经出现过一次，我当时就怀疑是 USB 接触问题，重新插拔外接 usb 系统盘即可恢复正常。但这次是在真正运行系统时出现问题，性质就不一样了：说明外置 usb 对系统运行的&lt;strong&gt;稳定性存在重大影响&lt;/strong&gt;，而 NAS 最重要的就是稳定性。&lt;/p&gt;
&lt;p&gt;不过事已至此，重新插拔硬盘吧。重启后，果然识别出来了系统，一切都恢复了正常，除了 jellyfin——数据库报废了，所有配置和观看记录都要重来，扫描到一半的存储库也要重新扫描。（是的，&lt;strong&gt;我没有备份&lt;/strong&gt;！！真的要哭了。各位，切记要好好做备份。）&lt;/p&gt;
&lt;p&gt;既然如此之不稳定，看来 TS-551 这条路是堵死了。而之前组装「核弹小主机」已经积累了一些经验，是时候自己组装一台 NAS 了。于是火速和一位专门研究硬件的朋友 &lt;a href=&quot;https://github.com/GreatHuang2007&quot;&gt;@GreatHuang&lt;/a&gt; 在 Agent 的协助下出了这份配置单：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;cpu i3 12100 850r二手
内存 七彩虹战斧 ddr4 3200 8G*2 550r
电源 利民 TR-GX650 金牌全模组 260r
主板 华硕 PRIME B760M-AJ D4 550r 淘宝拆机
散热 Thermalright AX120 R SE LGA1700 版本 65r
机箱 半岛铁盒f20 14盘位+风扇 215r
电源线 绿联电饭煲线 14r
硬盘、硅脂、其余外接线材：自己有，不用买。等降价再补充
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;写作本文时，正在处于等快递的过程中。所以——&lt;/p&gt;
&lt;p&gt;「欲知后事如何，且听下回分解~」&lt;/p&gt;
&lt;h2&gt;什么是博客&lt;/h2&gt;
&lt;p&gt;什么是博客？&lt;/p&gt;
&lt;p&gt;如果你问之前的我，我或许会说是「由文章组成的地方，记录个人对某些东西的理解和解释，是个人输出的大杂烩」。&lt;/p&gt;
&lt;p&gt;但重新审视自己的网站之后，我发现有些东西，似乎不适合博客这个地方；而有些东西，是博客该有的，但这个网站却缺失。&lt;/p&gt;
&lt;p&gt;比如，单纯介绍编程语言的语法，真的是博客应该写的东西吗？特别是那些「从零开始」的教程，真的适合博客这个形式吗？现在，我给出的答案是「不是」。这些文章可以说是笔记，而笔记强调的是一个&lt;strong&gt;知识体系&lt;/strong&gt;，是有组织性的。因此，我做出了一个&lt;a href=&quot;https://clanna.dev/notes/aodsivqeqcn5011i&quot;&gt;大胆的决定&lt;/a&gt;，把这些单纯讲编程语言语法的文章，移动到了&lt;a href=&quot;https://code.samhou.moe/&quot;&gt;单独的网站&lt;/a&gt;（命名为「中文教程式编码笔记」），并更新了导航栏，可以从博客跳转到那里去，也可以跳转回来。&lt;/p&gt;
&lt;p&gt;在&lt;a href=&quot;https://blog.samhou.moe/aqua-surf-3/#%E5%86%99%E4%BD%9C%E9%A3%8E%E6%A0%BC&quot;&gt;上一篇文章&lt;/a&gt;里面，我曾经提到写作风格从几年前到现在的转变。&lt;/p&gt;
&lt;p&gt;仔细审视之后，发现这个博客似乎缺少「人文气息」。什么意思呢？网站上全部都是技术内容，却缺少「艺术」「思考」一类的东西。为了解决这个问题，首先就引入了「&lt;a href=&quot;https://blog.samhou.moe/categories/anime-and-game-reviews/&quot;&gt;ACGN 点评&lt;/a&gt;」这个栏目作为 Bangumi 日志的镜像，让这个博客的内容更加充实。而你或许也已经注意到了，这篇「水星冲浪日志」不再仅仅堆砌技术折腾过程，也稍微融入了一些反思与思考。虽然这样会让文章变长，但我相信，来到这篇文章的每一个访客，&lt;strong&gt;都是喜欢长内容的人&lt;/strong&gt;——如果要看快餐内容，那么这个用户早就被某短视频网站夺走了。&lt;/p&gt;
&lt;p&gt;&lt;del&gt;理科生在这里大谈「人文气息」是不是搞错了什么&lt;/del&gt;&lt;/p&gt;
&lt;p&gt;总之，如果你要让我对博客进行重新定义的话，我或许会说：「&lt;strong&gt;博客是博主人生经历的结晶，它是一个分享折腾过程，表达自己的想法的地方，而不是一本堆砌内容的教科书&lt;/strong&gt;」，这也是对这段时间博客转型升级的总结。&lt;/p&gt;
&lt;h2&gt;后记&lt;/h2&gt;
&lt;p&gt;写完才发现，这篇文章已经相当长了。不过，我自己觉得这种内容充实的文章还不错，长文章更能满足「表达欲」，也更能把我想表达的东西说清楚（这点和我的编程语言语法讲解的文章很像嘛）。总之，下个月再见了！最近在学习 Rust，或许编程笔记会做些许更新~&lt;/p&gt;
&lt;p&gt;继续「&lt;a href=&quot;https://blog.samhou.moe/aqua-surf-1/#%E6%9D%82%E8%B0%88%E6%A0%8F%E7%9B%AE%E3%80%8A%E6%B0%B4%E6%98%9F%E5%86%B2%E6%B5%AA%E6%97%A5%E5%BF%97%E3%80%8B&quot;&gt;水星冲浪&lt;/a&gt;」吧！&lt;/p&gt;
</content:encoded></item><item><title>总感觉有什么不对——Anemoi 全线点评</title><link>https://blog.samhou.moe/anemoi-review/</link><guid isPermaLink="true">https://blog.samhou.moe/anemoi-review/</guid><description>anemoi 全线路点评，剧透预警。真·没有人类了！线路观感：淡雪&gt;ae线&gt;六花&gt;朱比华=小咏&gt;arr 尾声&gt;爱乃。日常和段子有些令人不适和无聊；结局很难评价，线路有惊喜也有惊吓，甚至还有摆烂。对 Key 来说及格边缘，总感觉有什么不太对……</description><pubDate>Tue, 09 Jun 2026 20:59:30 GMT</pubDate><content:encoded>&lt;p&gt;时隔很久又写了非常长的点评，因此也就作为博客发出来了~&lt;/p&gt;
&lt;p&gt;观前提示：本文是根据游玩顺序来的，每条线路完成后我都写了一部分，本文是重新整合到一起的结果。短评不剧透可跳过，如已通关可以直接拉到分线点评。&lt;/p&gt;
&lt;h2&gt;短评&lt;/h2&gt;
&lt;p&gt;短评：真·没有人类了！线路观感：淡雪&amp;gt;ae线&amp;gt;六花&amp;gt;朱比华=小咏&amp;gt;arr 尾声&amp;gt;爱乃。日常和段子有些令人不适和无聊；结局很难评价，线路有惊喜也有惊吓，甚至还有摆烂。对 Key 来说及格边缘，总感觉有什么不太对……&lt;/p&gt;
&lt;p&gt;下面是分线点评正文：&lt;/p&gt;
&lt;h2&gt;爱乃&lt;/h2&gt;
&lt;p&gt;第一个玩的是爱乃路线，总感觉 key 手下留情了，或者也可以说是摆烂了？&lt;/p&gt;
&lt;p&gt;整条路线既视感非常强，基本就是夏日口袋的䌷（思念体）+九岛鸥，不过写得很完整。前半也就卖萌日常+做飞机，后半突然套鸥的公式起来了，最后来了点奇幻，给了个还算可以的结局。&lt;/p&gt;
&lt;p&gt;不过我总感觉这条线路问题比较大，感觉 key 就套公式起来，看完缺少那种满足感，没有以前玩 key 的那种震撼。&lt;/p&gt;
&lt;p&gt;有一个很重要的疑问是，最后醒来之后有没有记忆，看最终一段我倾向于是有的，那既然有的男主你悲伤啥，而且看最后许愿+坐飞机过来，我猜测爱乃腿也好了吧？那男主更没有必要难受了，人都醒了嘛；但是按照 key 的惯例（比如 clannad 的风子），醒来之后是没有记忆的，如果是这么理解的话，是不是可以理解为 NTR？？&lt;/p&gt;
&lt;p&gt;爱乃是第一眼最喜欢的角色，到最后却让我感觉「就这？」。&lt;/p&gt;
&lt;h2&gt;淡雪&lt;/h2&gt;
&lt;p&gt;下一个推的，是淡雪，这条线路属实给了我一点惊喜，Key 社这次是真的玩大了，我也确实看爽了。&lt;/p&gt;
&lt;p&gt;先说结论，淡雪路线和上一个爱乃路线的剧情完全就不是一个质量级别的。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;「淡雪是爱着所有人，又被所有人爱着的存在」&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;这条线的第一周目其实前面的暗示足够多了，你完全就能猜个大概出来，我甚至幻视前作的紬线，第一次淡雪消失放 ED 的时候我真的觉得有点可惜，感觉 key 这么写水平有些倒退，无非就是加了点奇幻科幻的要素而已。&lt;/p&gt;
&lt;p&gt;但是你知道我看到尾声那里突然把存档扣掉的二选一选项有多震惊吗？！我甚至以为我在玩 Island，瞬间感觉 Key 社这次真的有东西。&lt;/p&gt;
&lt;p&gt;突然跳到另一个世界，当我以为它能给我点转机的时候，这剧情又给我来上一刀——你跟我说不告白，直接做一辈子朋友搞寿命论？不过不瞒你说，我确实挺感动的，觉得这个结局也还不错，毕竟大家都获得了幸福
——的这个时候，突然又给我蹦出两个选项跳转世界，然后连选快十几个「告白」才能推进剧情，再一次震惊到我，后面居然还有剧情！&lt;/p&gt;
&lt;p&gt;结果又吃了一遍一周目类似的刀子，这次情感到位我又掉了一堆小珍珠……当真 ED 出来我彻底放弃思考决定接受 BE（准备给编剧寄刀片）的时候，尾声出来了，意想不到地把人救回来了，这下感动的眼泪是真忍不住。我对这种穿越时空和时间线的故事没有一点抵抗力，Key 社这次真的没有让我失望，这条路线真的可以封神。&lt;/p&gt;
&lt;h2&gt;小咏&lt;/h2&gt;
&lt;p&gt;再下一个是小咏。不把人写成 BE 我就会死.jpg&lt;/p&gt;
&lt;p&gt;可以看得出来，这条线路是花了点心思的（相比于爱乃线来说）；但是对于淡雪线我只能说完全比不上。&lt;/p&gt;
&lt;p&gt;先说优点，对于本线大部分公路片的展开，我真的看得非常爽，真有点星之终途的味道了，很喜欢这种设定，也看得出来 anemoi 不想局限在一个小镇上，「旅行」贯穿在角色线中。&lt;/p&gt;
&lt;p&gt;好的说完了，来说这条线路的问题。&lt;/p&gt;
&lt;p&gt;我非常不喜欢这个 BE 和结尾处理，因为 key 的思念体看多了审美非常疲劳，看到人变成鸟的时候总会忍不住想「怎么又是这样」「猜都猜到了」。而在这条线路中我最感动的还是小咏爷爷的信件，但这段有新意的剧情并没有成为全线情感高潮，却在后续小咏消失的时候放催泪音乐试图用男女主的感情来「打动」玩家，属实是画蛇添足。依我看，这条线的恋爱感就比不上「家人」「误解」这些主题，你直接写成在树洞前一起消失，或者编个合理理由写个 HE 共同经营邮局加深一下感情，都要比现在这个结尾好上不少。&lt;/p&gt;
&lt;p&gt;写成这样，退钱！&lt;/p&gt;
&lt;h2&gt;六花&lt;/h2&gt;
&lt;p&gt;再下一个是六花。这条线路仅次于淡雪，是第二好的观感。&lt;/p&gt;
&lt;p&gt;这条线路属实非常聪明，在日常里面把其他角色完美地融入了进来，日常也并不无聊。在六花本身之外，这条线路把朱比华的人设也做了进来，猛提了一波好感，夸一下。&lt;/p&gt;
&lt;p&gt;作为「旅行」这个主题非常重的一条线路，从序章开始做的铺垫，一环扣一环，同时逐渐揭示世界观，观感真的非常的舒服，前面推的线路、序章的内容……全部都逐渐拼凑在一起了&lt;/p&gt;
&lt;p&gt;而作为「泣系」作品呢？很显然，这条线路是非常合格的。无论是前期展示的兄妹情，还是后期惯例的离别与消失，都是「恰到好处」的，从来没有像前面推的小咏和爱乃这两条线路，让我感觉「无法接受」「逻辑不通」。虽然最终也不是 HE 而是 BE，却让我这个 HE 爱好者感觉到，好像 BE 才是本来的样子，实在是厉害。&lt;/p&gt;
&lt;h2&gt;朱比华/真结局&lt;/h2&gt;
&lt;p&gt;最后是朱比华和真结局。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;「好想成为人类啊……」&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;真·已经没有人类了！&lt;/p&gt;
&lt;p&gt;朱比华线路在真结局之前完全就是个小甜点级别的故事，我还以为 Key 大发善心，心想这次不会刀太狠，结果看完 E A 两线和尾声之后，我心都凉透了。这里引用一下一个 bgmer 的评价：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;以后有人说我想成为 GALGAME 的男主，就请他穿越成为 anemoi 的男主&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;单论情节和角色塑造，这一作完全两人就超越了 SP，非常得高明，朱比华线路的恋爱感、时间无限拉长之后两人的羁绊、以及父女两人的互相付出，确实是有真东西在的，我个人非常喜欢。&lt;/p&gt;
&lt;p&gt;但是，本作的结局问题也很明显：Key 社的风格应该是让我看完心里暖暖的，但这一部的结局却不是这样，我只看到了恶意——男主一辈子的时间都在等待，结果就死之前见了一面？我本来以为会写成两人死后化作风相遇，结果到头来男主失去了一切，这到底算什么……&lt;/p&gt;
&lt;h2&gt;其他&lt;/h2&gt;
&lt;p&gt;聊完所有的线路，再来说说本作的其他问题：日常注水，段子有恶意。整个剧情里我看到不止一次对乳量的玩笑了，对贫乳派非常不适。&lt;/p&gt;
&lt;p&gt;&lt;del&gt;小游戏不评价，因为是 AI 汉化玩不了&lt;/del&gt;&lt;/p&gt;
&lt;p&gt;因此，通关之后我对本作的评价是：「线路褒贬不一，角色塑造满分，结局让人心凉。」&lt;/p&gt;
</content:encoded></item><item><title>Hexo 博客接入 Fediverse —— Hatsu + Vercel 踩坑记</title><link>https://blog.samhou.moe/hexo-fediverse/</link><guid isPermaLink="true">https://blog.samhou.moe/hexo-fediverse/</guid><description>本文记录将 Hexo 博客接入 Fediverse 的完整实践，使用 Hatsu 将 RSS 转为帖子，并处理账号发现、文章 URL 对应、Vercel 路由重定向与斜杠丢失问题；还介绍 ActivityPub 请求识别、边缘函数跳转以及将 Mastodon 评论集成到 Butterfly 主题。</description><pubDate>Fri, 05 Jun 2026 20:07:01 GMT</pubDate><content:encoded>&lt;p&gt;Fediverse 这个东西，我个人是真的非常喜欢。在&lt;a href=&quot;https://blog.samhou.moe/aqua-surf-3/&quot;&gt;之前的杂谈&lt;/a&gt;里面，曾经提到过我到底是怎么接触到这个去中心化社交媒体的。&lt;/p&gt;
&lt;p&gt;其实给博客接 Fedi 的想法已经由来已久了。几个月前，找到了 Hatsu 这个非常厉害的工具，于是接入了 Fediverse，每次博客更新的时候，Hatsu 都会自动把 Feed 里面的内容转换为 Fediverse 贴文。不过，在之前因为对&lt;a href=&quot;https://hatsu.cli.rs/intro.html&quot;&gt;文档&lt;/a&gt;的忽视和「能用就行」的想法，导致我只是部署了 Hatsu 后端，实现了博客 -&amp;gt; Fedi 的单向转换，有两个重要的问题没有解决：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Fedi 收到评论后，怎么把评论显示回来博客？&lt;/li&gt;
&lt;li&gt;读者知道一篇文章的 URL，怎么找到这个 URL 对应的 Fedi 帖子？由于去中心化的特点，在本地查看远程实例信息会出现帖子的遗漏，除非直接输入远程实例 URL 。但是 Hatsu 贴文的 URL 和博客文章的不是一个，它的格式是 &lt;code&gt;https://feed.clanna.dev/posts/${url}&lt;/code&gt; 后面的 URL 是文章的原始 URL。也就是说，直接查询博文的原始 URL 会直接失败&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;所以这篇文章就来聊聊到底怎么实现双向互通。从 Hatsu 部署开始，到彻底互通，来说说一路上的坑。&lt;/p&gt;
&lt;p&gt;在开始之前，先来说说我这套架构是什么：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;博客是部署在 Vercel 上的静态博客，接入的是 github 仓库，当我们推送更新时，vercel 拉取并执行 npm 命令，生成静态网站。&lt;/li&gt;
&lt;li&gt;Hatsu 部署在 VPS 上，作为 Fedi 后端。上面注册了一个机器人用户，对应的就是博客的 Feed。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;此外，如果你也想复刻这套方案，我强烈建议你先读一遍 &lt;a href=&quot;https://hatsu.cli.rs/intro.html&quot;&gt;Hatsu 官方文档&lt;/a&gt;，再来看跟着操作，否则……你懂得，&lt;del&gt;出问题了别找我&lt;/del&gt;。&lt;/p&gt;
&lt;h2&gt;Feed 准备&lt;/h2&gt;
&lt;p&gt;在一切开始之前，你得确保你的博客有 RSS，并且 RSS 可被自动发现。&lt;/p&gt;
&lt;p&gt;也就是，html head 里面要有 link 指向你的 feed，比如下面。&lt;/p&gt;
&lt;p&gt;注：大部分情况下都有，hexo rss 插件会帮你处理好这个。如果你的博客没有，请检查配置。&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-html&quot;&gt;&amp;lt;head&amp;gt;
    &amp;lt;link rel=&quot;alternate&quot; type=&quot;application/feed+json&quot; href=&quot;https://example.com/feed.json&quot; /&amp;gt;
    &amp;lt;link rel=&quot;alternate&quot; type=&quot;application/atom+xml&quot; href=&quot;https://example.com/atom.xml&quot; /&amp;gt;
    &amp;lt;link rel=&quot;alternate&quot; type=&quot;application/rss+xml&quot; href=&quot;https://example.com/rss.xml&quot; /&amp;gt;
&amp;lt;/head&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Hatsu 部署&lt;/h2&gt;
&lt;p&gt;准备好 RSS 之后，首先，咱们来讲讲 hatsu 的部署。&lt;/p&gt;
&lt;p&gt;和官方文档不同，&lt;strong&gt;准备工作后面再讲&lt;/strong&gt;，先讲部署，因为部署不是今天的重点。&lt;/p&gt;
&lt;p&gt;打开&lt;a href=&quot;https://github.com/importantimport/hatsu/blob/main/examples/docker-compose/docker-compose.yml&quot;&gt;配置示例&lt;/a&gt;，在你的 VPS 上创建一个文件夹来存储内容，然后创建 yml 的 compose。这个不多说，如果你不知道 docker，我建议你读一下之前的&lt;a href=&quot;https://blog.samhou.moe/bangumi-media-server-1/&quot;&gt;看番教程&lt;/a&gt;系列，详细介绍了如何用 docker。&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-yaml&quot;&gt;# 这是写作本文时的版本。请点击上面的「配置示例」查看最新版本，切勿照抄！！！
version: &quot;3&quot;

services:
  hatsu:
    container_name: hatsu
    image: ghcr.io/importantimport/hatsu:nightly
    restart: unless-stopped
    ports:
      - 3939:3939
    # env_file:
    #   - .env
    environment:
      - HATSU_DATABASE_URL=sqlite://hatsu.sqlite3
      - HATSU_DOMAIN=hatsu.example.com
      - HATSU_LISTEN_HOST=0.0.0.0
      - HATSU_PRIMARY_ACCOUNT=blog.example.com
    volumes:
      # - ./.env:/app/.env
      - ./hatsu.sqlite3:/app/hatsu.sqlite3
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;要改的不多，就两处：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;HATSU_DOMAIN=hatsu.example.com&lt;/code&gt; 改成你的域名，这是 hatsu 所在的域名，是一个独立的域用于 fedi 交换&lt;/li&gt;
&lt;li&gt;&lt;code&gt;HATSU_PRIMARY_ACCOUNT=blog.example.com&lt;/code&gt; 改成你的博客域名
此外，你还需要生成 access token。&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;echo &quot;\nHATSU_ACCESS_TOKEN = \&quot;$(cat /proc/sys/kernel/random/uuid)\&quot;&quot; &amp;gt;&amp;gt; .env
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;然后取消上面配置文件中的注释加载环境变量。&lt;/p&gt;
&lt;p&gt;现在启动 docker：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;docker compose up -d &amp;amp;&amp;amp; docker compose logs -f
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;不出意外的话&lt;del&gt;就出意外了&lt;/del&gt;容器就起来了。&lt;/p&gt;
&lt;p&gt;然后创建用户（记得改变量）：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;NAME=&quot;example.com&quot; curl -X POST &quot;http://localhost:$(echo $HATSU_LISTEN_PORT)/api/v0/admin/create-account?name=$(echo $NAME)&amp;amp;token=$(echo $HATSU_ACCESS_TOKEN)&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;然后，用你各种手段，无论是 nginx 还是 caddy 还是 cf 的 tunnel，把 3939 端口反代出去到你的域名。这个不多说了，我相信看这篇文章的不是来学这个的。&lt;/p&gt;
&lt;p&gt;现在后端 Fedi 部分已经部署完毕，接下来我们来看看前端吧~&lt;/p&gt;
&lt;h2&gt;重定向跳转&lt;/h2&gt;
&lt;p&gt;根据&lt;a href=&quot;https://hatsu.cli.rs/users/redirecting-with-platform-specific-config.html&quot;&gt;文档&lt;/a&gt;，设置自定义跳转内容。&lt;/p&gt;
&lt;h3&gt;让用户名可搜索&lt;/h3&gt;
&lt;p&gt;这个最简单，文档里都有现成的内容可以用，我给翻译到中文。&lt;/p&gt;
&lt;p&gt;在你的博客项目根目录下，创建 &lt;code&gt;vercel.json&lt;/code&gt;，填入下面内容。记得把 &lt;code&gt;hatsu.local&lt;/code&gt; 改成你的 hatsu 域名！&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-json&quot;&gt;{
  &quot;redirects&quot;: [
    {
      &quot;source&quot;: &quot;/.well-known/host-meta&quot;,
      &quot;destination&quot;: &quot;https://hatsu.local/.well-known/host-meta&quot;
    },
    {
      &quot;source&quot;: &quot;/.well-known/host-meta.json&quot;,
      &quot;destination&quot;: &quot;https://hatsu.local/.well-known/host-meta.json&quot;
    },
    {
      &quot;source&quot;: &quot;/.well-known/nodeinfo&quot;,
      &quot;destination&quot;: &quot;https://hatsu.local/.well-known/nodeinfo&quot;
    },
    {
      &quot;source&quot;: &quot;/.well-known/webfinger&quot;,
      &quot;destination&quot;: &quot;https://hatsu.local/.well-known/webfinger&quot;
    }
  ]
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;提交，等待 vercel 部署。&lt;/p&gt;
&lt;p&gt;现在在你的 fedi 软件上搜索账号 &lt;code&gt;@blog.samhou.moe@feed.clanna.dev&lt;/code&gt; 应该就能请求成功了（这个示例是我的博客的 hatsu 用户，如果你搜索的话应该可以看到博客的简介）。因为上面创建了重定向，也可以直接查询 &lt;code&gt;@catch-all@blog.samhou.moe&lt;/code&gt;，得到的账号是一样的~&lt;/p&gt;
&lt;h3&gt;AS2 Alternate&lt;/h3&gt;
&lt;p&gt;细心的读者肯定发现了，我给出来的 hatsu 原版文档里面，可不止有用户名重定向，还有个叫 AS2 的重定向。&lt;/p&gt;
&lt;p&gt;&lt;del&gt;是的我几个月前没看到，搭建了个残废的 hatsu……&lt;/del&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Redirects file only applies to &lt;code&gt;.well-known&lt;/code&gt;. for AS2 redirects, you need to use &lt;a href=&quot;https://hatsu.cli.rs/users/redirecting-with-static-files-and-markup.html#as2-alternate&quot;&gt;AS2 Alternate&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;点开，你会发现，你需要在你的博客文章的 head 中注入更多内容：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-html&quot;&gt;&amp;lt;link rel=&quot;alternate&quot; type=&quot;application/activity+json&quot; href=&quot;https://hatsu.local/posts/https://example.com/foo/bar&quot; /&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;hmmm，看起来这个就有点难了，要按照 url，给每个页面增加独立的 href。对于接入 fedi 这种小众的事情，hexo 不可能原版有，你的主题也不一定有。&lt;/p&gt;
&lt;p&gt;所以，我们来{魔改主题源码|鞭打LLM黑奴}吧！我的 hexo 主题用的是 &lt;a href=&quot;https://github.com/jerryc127/hexo-theme-butterfly&quot;&gt;hexo-theme-butterfly&lt;/a&gt;，于是 fork 一份到自己这里，然后用 subtree 集成到我的博客里面进行开发。&lt;/p&gt;
&lt;p&gt;首先我们找一找就不难发现，处理 head 的内容在 &lt;code&gt;themes/butterfly/layout/includes/head.pug&lt;/code&gt;。简单搜索一下就知道，这个语法是 jade，一种用于生成 html 内容的模板。&lt;/p&gt;
&lt;p&gt;那就好办了，直接加上 include：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-jade&quot;&gt;include ./head/fediverse.pug
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;然后，创建这个 &lt;code&gt;fediverse.pug&lt;/code&gt;：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-jade&quot;&gt;if theme.fediverse
    link(rel=&quot;alternate&quot;
    type=&quot;application/activity+json&quot;
    href=new URL(`/posts/${urlNoIndex(null,config.pretty_urls.trailing_index,config.pretty_urls.trailing_html)}`,
    theme.hatsu.instance).href)
    link(rel=&quot;alternate&quot;
    type=&quot;application/ld+json&quot;
    href=new URL(`/posts/${urlNoIndex(null,config.pretty_urls.trailing_index,config.pretty_urls.trailing_html)}`,
    theme.hatsu.instance).href)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;OK，然后配置文件稍微写一下：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-yaml&quot;&gt;# Hatsu
# https://hatsu.local/
hatsu:
    instance: https://feed.clanna.dev
fediverse: true
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;完成！再次 commit 之后推送。&lt;/p&gt;
&lt;p&gt;现在在 fediverse 上搜索框直接键入文章 URL……&lt;/p&gt;
&lt;p&gt;不出意外的话，就出意外了。&lt;/p&gt;
&lt;p&gt;如果你用的是 &lt;code&gt;mastodon&lt;/code&gt; &lt;code&gt;misskey&lt;/code&gt; 这类软件，确实可以。但是很可惜，我用的是 &lt;code&gt;sharkey&lt;/code&gt; 这个 &lt;code&gt;misskey&lt;/code&gt; 分支，并没有成功识别这个 link 标签，而是直接报错。&lt;/p&gt;
&lt;p&gt;细心的读者肯定也发现了，文档里面这么说：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Only Mastodon and Misskey (and their forks) is known to support auto-discovery, other software requires redirection to search correctly. &lt;a href=&quot;https://github.com/w3c/activitypub/issues/310&quot;&gt;w3c/activitypub#310&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;行，看来这条路是走通一半了，但没有完全走通。&lt;/p&gt;
&lt;h3&gt;根据请求头进行重定向&lt;/h3&gt;
&lt;p&gt;在 &lt;a href=&quot;https://social.akk.moe/@skyone&quot;&gt;@skyone&lt;/a&gt; 的&lt;a href=&quot;https://social.akk.moe/notes/an3s67kvhi7n00vv&quot;&gt;提醒&lt;/a&gt;下，我决定不止靠上面的 link 元素，而是从根源入手：&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;当收到来自 Fedi 软件的 activity pub 请求时，把请求交给 hatsu 处理&lt;/strong&gt;。
请求头 Accept 中会包含：&lt;code&gt;application/ld+json&lt;/code&gt; 或 &lt;code&gt;application/activity+json&lt;/code&gt;。&lt;/p&gt;
&lt;p&gt;简单翻翻 vercel 的控制台，这个 Routing Rules 引起了我的注意。于是我们可以直接简单写个规则，当收到来自 activity pub 客户端的请求时，自动将请求交给 hatsu 处理。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://img.samhou.top/1780658654945.webp&quot; alt=&quot;路由规则&quot;&gt;&lt;/p&gt;
&lt;p&gt;一切都很顺利……才怪勒！&lt;/p&gt;
&lt;p&gt;我的 Sharkey 还是报错。仔细研究才发现：&lt;/p&gt;
&lt;p&gt;由于使用的是 rewrite，所以 fedi 应用请求我的博客文章 &lt;code&gt;https://blog.samhou.moe/aqua-surf-3/&lt;/code&gt; 时，vercel 会代理请求，重写请求发送到 hatsu，此时 hatsu 返回一串 json。这个 json 是从 hatsu &lt;code&gt;https://feed.clanna.dev/posts/https://blog.samhou.moe/aqua-surf-3/&lt;/code&gt; 这个 URL &lt;strong&gt;返回的 JSON 内容&lt;/strong&gt;，发送回 vercel ，再发回给 fedi 应用。&lt;/p&gt;
&lt;p&gt;示例如下：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;{&quot;@context&quot;:&quot;https://www.w3.org/ns/activitystreams&quot;,&quot;id&quot;:&quot;https://feed.clanna.dev/posts/https://blog.samhou.moe/aqua-surf-3/&quot;,&quot;type&quot;:&quot;Note&quot;,&quot;published&quot;:&quot;2026-05-24T07:35:00Z&quot;,&quot;attributedTo&quot;:&quot;https://feed.clanna.dev/users/blog.samhou.moe&quot;,&quot;to&quot;:[&quot;https://www.w3.org/ns/activitystreams#Public&quot;],&quot;cc&quot;:[&quot;https://feed.clanna.dev/users/blog.samhou.moe/followers&quot;],&quot;content&quot;:&quot;&amp;lt;p&amp;gt;水星冲浪日志 3 —— Fediverse、Arch Linux 和写作风格&amp;lt;/p&amp;gt;\n&amp;lt;p&amp;gt;《水星冲浪日志》的第三期，记录了博主加入 Fediverse 社交媒体的经历，Arch Linux 安装、磁盘加密和桌面环境的折腾，以及回首写博客旅途，自己文风的变化。&amp;lt;/p&amp;gt;\n&amp;lt;p&amp;gt;&amp;lt;a href=\&quot;https://blog.samhou.moe/aqua-surf-3/\&quot;&amp;gt;https://blog.samhou.moe/aqua-surf-3/&amp;lt;/a&amp;gt;&amp;lt;/p&amp;gt;\n\n&amp;lt;a href=\&quot;https://feed.clanna.dev/t/%E6%9D%82%E8%B0%88\&quot; rel=\&quot;tag\&quot;&amp;gt;#&amp;lt;span&amp;gt;杂谈&amp;lt;/span&amp;gt;&amp;lt;/a&amp;gt; &amp;lt;a href=\&quot;https://feed.clanna.dev/t/fediverse\&quot; rel=\&quot;tag\&quot;&amp;gt;#&amp;lt;span&amp;gt;fediverse&amp;lt;/span&amp;gt;&amp;lt;/a&amp;gt; &amp;lt;a href=\&quot;https://feed.clanna.dev/t/arch\&quot; rel=\&quot;tag\&quot;&amp;gt;#&amp;lt;span&amp;gt;arch&amp;lt;/span&amp;gt;&amp;lt;/a&amp;gt; &amp;lt;a href=\&quot;https://feed.clanna.dev/t/linux\&quot; rel=\&quot;tag\&quot;&amp;gt;#&amp;lt;span&amp;gt;linux&amp;lt;/span&amp;gt;&amp;lt;/a&amp;gt; &amp;lt;a href=\&quot;https://feed.clanna.dev/t/writing\&quot; rel=\&quot;tag\&quot;&amp;gt;#&amp;lt;span&amp;gt;writing&amp;lt;/span&amp;gt;&amp;lt;/a&amp;gt; &amp;lt;a href=\&quot;https://feed.clanna.dev/t/misskey\&quot; rel=\&quot;tag\&quot;&amp;gt;#&amp;lt;span&amp;gt;misskey&amp;lt;/span&amp;gt;&amp;lt;/a&amp;gt; &amp;lt;a href=\&quot;https://feed.clanna.dev/t/sharkey\&quot; rel=\&quot;tag\&quot;&amp;gt;#&amp;lt;span&amp;gt;sharkey&amp;lt;/span&amp;gt;&amp;lt;/a&amp;gt;&quot;,&quot;contentMap&quot;:null,&quot;source&quot;:{&quot;content&quot;:&quot;水星冲浪日志 3 —— Fediverse、Arch Linux 和写作风格\n\n《水星冲浪日志》的第三期，记录了博主加入 Fediverse 社交媒体的经历，Arch Linux 安装、磁盘加密和桌面环境的折腾，以及回首写博客旅途，自己文风的变化。\n\nhttps://blog.samhou.moe/aqua-surf-3/\n\n#杂谈 #fediverse #arch #linux #writing #misskey #sharkey&quot;,&quot;mediaType&quot;:&quot;text/markdown&quot;},&quot;tag&quot;:[{&quot;type&quot;:&quot;Hashtag&quot;,&quot;href&quot;:&quot;https://feed.clanna.dev/t/%E6%9D%82%E8%B0%88&quot;,&quot;name&quot;:&quot;#杂谈&quot;},{&quot;type&quot;:&quot;Hashtag&quot;,&quot;href&quot;:&quot;https://feed.clanna.dev/t/fediverse&quot;,&quot;name&quot;:&quot;#fediverse&quot;},{&quot;type&quot;:&quot;Hashtag&quot;,&quot;href&quot;:&quot;https://feed.clanna.dev/t/arch&quot;,&quot;name&quot;:&quot;#arch&quot;},{&quot;type&quot;:&quot;Hashtag&quot;,&quot;href&quot;:&quot;https://feed.clanna.dev/t/linux&quot;,&quot;name&quot;:&quot;#linux&quot;},{&quot;type&quot;:&quot;Hashtag&quot;,&quot;href&quot;:&quot;https://feed.clanna.dev/t/writing&quot;,&quot;name&quot;:&quot;#writing&quot;},{&quot;type&quot;:&quot;Hashtag&quot;,&quot;href&quot;:&quot;https://feed.clanna.dev/t/misskey&quot;,&quot;name&quot;:&quot;#misskey&quot;},{&quot;type&quot;:&quot;Hashtag&quot;,&quot;href&quot;:&quot;https://feed.clanna.dev/t/sharkey&quot;,&quot;name&quot;:&quot;#sharkey&quot;}],&quot;url&quot;:{&quot;type&quot;:&quot;Link&quot;,&quot;rel&quot;:&quot;canonical&quot;,&quot;href&quot;:&quot;https://blog.samhou.moe/aqua-surf-3/&quot;}}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;响应头是这样的：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;HTTP/2 200
age: 0
cache-control: public, max-age=0, must-revalidate
content-type: application/activity+json; charset=utf-8
date: Fri, 05 Jun 2026 09:09:49 GMT
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;你有没有注意到一个根本性的异常？&lt;/p&gt;
&lt;p&gt;请求的 URL 是 &lt;code&gt;https://blog.samhou.moe/aqua-surf-3/&lt;/code&gt;，返回了 200，但是返回的 JSON 帖子却显示这个帖子的 URL 是 &lt;code&gt;https://feed.clanna.dev/posts/https://blog.samhou.moe/aqua-surf-3/&lt;/code&gt;。&lt;/p&gt;
&lt;p&gt;虽然 sharkey 报错信息没见着，但是直接把后面帖子 URL 贴进去是可以识别的。&lt;/p&gt;
&lt;p&gt;这说明什么？&lt;strong&gt;请求 URL 必须和贴文本身 URL 匹配&lt;/strong&gt;！不能用 rewrite 这种类似「反向代理」的方式来返回内容给 fedi 软件。&lt;/p&gt;
&lt;p&gt;不过这个也好解决，我们可以直接不用 rewrite 了，直接用 redirect 嘛。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://img.samhou.top/1780659379214.webp&quot; alt=&quot;改成 Redirect&quot;&gt;&lt;/p&gt;
&lt;p&gt;此时你看了一眼这篇博客右边的滚动条和左边的目录，发现事情并没有这么简单。&lt;/p&gt;
&lt;p&gt;完成这样的部署之后，Sharkey 依旧报错。&lt;/p&gt;
&lt;p&gt;是的，这次 hatsu 也在报错了。&lt;/p&gt;
&lt;p&gt;仔细一看一堆 404：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;uri: /posts/https:/blog.samhou.moe/aqua-surf-3/
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;不是我斜杠怎么就剩下一个了？赶紧 curl 看看：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;{ &quot;redirect&quot;: &quot;https://feed.clanna.dev/posts/https:/blog.samhou.moe/aqua-surf-3/&quot;, &quot;status&quot;: &quot;302&quot; }
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;不是，哥们？由于 vercel 的&lt;del&gt;妙妙&lt;/del&gt;处理，跳转的 url 直接干没了一个斜杠。&lt;/p&gt;
&lt;p&gt;求助了{狗屁通|ChatGPT}老师之后，才知道这次是遇到面板极限了。&lt;/p&gt;
&lt;p&gt;进行了一番深入的探讨，G 老师建议我写一个&lt;em&gt;边缘函数&lt;/em&gt;，结合 &lt;code&gt;vercel.json&lt;/code&gt; 完成两次跳转。&lt;/p&gt;
&lt;p&gt;也就是说，当检测到 fedi 软件请求时的请求路径：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;request /blog-post 重定向 -&amp;gt; /api/apub?url=xxx 302 重定向 -&amp;gt; https://feed.clanna.dev/posts/xxxx
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;先增加第一个重定向：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-json&quot;&gt;&quot;routes&quot;: [
{
    &quot;src&quot;: &quot;/(.*)&quot;,
    &quot;has&quot;: [
    {
        &quot;type&quot;: &quot;header&quot;,
        &quot;key&quot;: &quot;Accept&quot;,
        &quot;value&quot;: &quot;.*(application/activity\\+json|application/ld\\+json).*&quot;
    }
    ],
    &quot;dest&quot;: &quot;/api/apub?url=https://blog.samhou.moe/$1&quot;
}
]
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;很好！稍微写点边缘函数：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;export default async function handler(req, res) {
  const url = req.query.url

  res.redirect(
    302,
    `https://feed.clanna.dev/posts/${url}`
  )
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;完美。现在提交并推送等待 vercel 构建。&lt;/p&gt;
&lt;p&gt;试一试……完美！输入文章 URL，就可以直接跳转到目标帖子了！&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://img.samhou.top/1780662756042.gif&quot; alt=&quot;自动跳转&quot;&gt;&lt;/p&gt;
&lt;h2&gt;自定义评论系统&lt;/h2&gt;
&lt;p&gt;在 Hatsu 的文档里面，还提到了你可以把来自 fedi 的评论集成回你的网站中。&lt;/p&gt;
&lt;p&gt;在&lt;a href=&quot;https://hatsu.cli.rs/users/backfeed.html&quot;&gt;文档&lt;/a&gt;里面，提到可以用 kkna（作者写的轻量加载器）或者 Mastodon Comments 来实现。&lt;/p&gt;
&lt;p&gt;前者搞半天都失败，所以我就选择了后者。&lt;/p&gt;
&lt;p&gt;直接把文档和 butterfly 主题的源码塞给 AI，让它来写。&lt;/p&gt;
&lt;p&gt;在无数次 Vibe 出 Bug 之后，终于……&lt;/p&gt;
&lt;p&gt;完成了下面的大作：&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://img.samhou.top/1780663157422.webp&quot; alt=&quot;评论区示例&quot;&gt;&lt;/p&gt;
&lt;p&gt;是的，按一下右上角的切换按钮就可以找到原来的 Artalk 了，而默认展示来自 Fediverse 的评论！&lt;/p&gt;
&lt;p&gt;如果你也想用这个的话，我已经把修改版主题&lt;a href=&quot;https://github.com/SamHou0/hexo-theme-butterfly-newbee&quot;&gt;开源&lt;/a&gt;了，你可以也集成到你的 hexo 博客里面，也可以自己魔改。&lt;/p&gt;
&lt;p&gt;经过我和 AI 几天的改造，整个博客优化了几处小细节，增加了几个好用的新功能，还是挺不错的。&lt;/p&gt;
&lt;p&gt;那么这篇文章就到这里结束了，希望各位能有所收获~&lt;/p&gt;
</content:encoded></item><item><title>水星冲浪日志 3 —— Fediverse、Arch Linux 和写作风格</title><link>https://blog.samhou.moe/aqua-surf-3/</link><guid isPermaLink="true">https://blog.samhou.moe/aqua-surf-3/</guid><description>《水星冲浪日志》第三期记录博主加入 Fediverse 后对 Mastodon、Misskey 等联邦社交的体验，也回顾域名事故与迁移过程、安装 Arch Linux、磁盘加密和桌面环境配置。文章还谈到写博客以来文风的变化，串联网络服务、系统折腾与个人写作感受。</description><pubDate>Sun, 24 May 2026 15:06:19 GMT</pubDate><content:encoded>&lt;blockquote&gt;
&lt;p&gt;好忙！&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;写下这篇文章的时候，已经是 5/19 了。上一篇杂谈是……让我看看……3 月 5 号……&lt;/p&gt;
&lt;p&gt;&lt;del&gt;我真不是故意拖那么久的，&lt;a href=&quot;https://clanna.dev/notes/alz2ly4dbrat05h0&quot;&gt;最近时间真的不够啦&lt;/a&gt;……&lt;/del&gt;&lt;/p&gt;
&lt;p&gt;虽说《水星冲浪日志》这个栏目是不定期的杂谈，但是我曾经的想法是{每个月更新一篇|白日做梦}……&lt;/p&gt;
&lt;p&gt;闲话到这里，进入正题！&lt;/p&gt;
&lt;h2&gt;Fediverse&lt;/h2&gt;
&lt;p&gt;联邦宇宙。&lt;/p&gt;
&lt;h3&gt;相遇&lt;/h3&gt;
&lt;p&gt;第一次听到这个词，是几年前的一天，那时我正在上网冲浪，偶然碰到了 mastodon 这个东西，于是就去 &lt;a href=&quot;https://mastodon.social&quot;&gt;mastodon.social&lt;/a&gt; 注册了一个账户。&lt;/p&gt;
&lt;p&gt;可惜那时我还完全不知道 fedi 的好处，也不了解它的工作原理，以为是和普通社交媒体差不多的一个平台，于是仅仅随便关注了几个有意思的账号。可惜 mastodon 的前端完全喜欢不起来，每天连点开的兴趣也没有，那个账号也自然吃灰了。&lt;/p&gt;
&lt;h3&gt;「故事的起点」&lt;/h3&gt;
&lt;p&gt;再次回到联邦宇宙，是去年六月左右。&lt;/p&gt;
&lt;p&gt;那个时候，我刚刚考完高考，终于摆脱了语数英物化生的折磨，终于能够自由地探索网络、计算机这些东西了。&lt;/p&gt;
&lt;p&gt;我仍然记得，高考最后一天是个上午，考的是「英语听说」，就在那天下午，我购买了第一台真正可以说是「MJJ 故事的起点」的 VPS，香港 2c4g 的大机器。&lt;/p&gt;
&lt;p&gt;拿到机器之后，我做的第一件事就是把之前那台 512M 的微型小鸡上的服务迁移过来，还记得最重要的一个是这个博客的 Artalk 服务端。&lt;/p&gt;
&lt;p&gt;其次，就是 &lt;a href=&quot;https://gotosocial.org/&quot;&gt;Gotosocial&lt;/a&gt;。上一台 512M 即使对这种轻量服务端也是过于小，所以很快放弃了。不过那时我已经花了点时间，明白了什么是 activity pub 协议，什么是联合，联邦宇宙到底是怎么工作的。&lt;/p&gt;
&lt;p&gt;这次终于把它跑起来了，于是关注了几个有意思的账号。&lt;/p&gt;
&lt;p&gt;你以为我正式加入联邦宇宙了？&lt;/p&gt;
&lt;p&gt;事情并没有这么顺利。&lt;/p&gt;
&lt;p&gt;同样，GTS 是没有前端的，几个 Web 前端实现也完全不好看。我完全找不到任何继续使用的理由和动力，于是再次搁置了服务器，任由账号和实例发展，看着联合上的服务器越来越多，自己的实例却一潭死水，几个月不打开。&lt;/p&gt;
&lt;h3&gt;白嫖使我快乐&lt;/h3&gt;
&lt;p&gt;而真正的转折发生在今年一月。&lt;/p&gt;
&lt;p&gt;故事的契机是我发现薅羊毛得来的 digital ocean 200 美刀学生账号根本用不完，而那时由刚好入了 Nodeseek 这个论坛，执着于寻找各种各样的 self-host 服务来塞满小鸡……&lt;/p&gt;
&lt;p&gt;fediverse 才重回我的视野。&lt;/p&gt;
&lt;p&gt;没错，长毛象 Mastodon，至少需要 2c2g，这不是完美的消耗资源的服务吗！&lt;/p&gt;
&lt;p&gt;可当我看到 &lt;a href=&quot;https://docs.joinmastodon.org/admin/install/&quot;&gt;Install from source（源代码安装）&lt;/a&gt;的时候，我畏惧了——&lt;/p&gt;
&lt;p&gt;很显然，作为一个长期使用 docker 的狂热分子，我不希望一堆环境和以来污染我的机器，也不想在裸机上执行编译安装。&lt;/p&gt;
&lt;p&gt;是时候找一个新的 Fedi 软件了。&lt;/p&gt;
&lt;p&gt;没错，它就是 &lt;a href=&quot;https://misskey-hub.net/cn/&quot;&gt;Misskey&lt;/a&gt;。&lt;/p&gt;
&lt;p&gt;第一次见到它的 UI 时，我都惊呆了——一个 Fedi 软件，竟然能有如此丝滑的页面，这么多好用的功能，对比下来长毛象就像是个原始人。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;虽然但是，和它光鲜亮丽的外表比起来，它的&lt;a href=&quot;https://clanna.dev/notes/alndfis6brat05gb&quot;&gt;文档简直是灾难&lt;/a&gt;……不过那都是后话了，当时的我还沉浸在发现新大陆的喜悦之中，暂且先看着这个发现新玩具的站长吧~&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3&gt;Misskey 重度依赖&lt;/h3&gt;
&lt;p&gt;接下来，事情就顺理成章了。&lt;/p&gt;
&lt;p&gt;在 DO 上开个小鸡，docker compose 一下，启动服务器——&lt;/p&gt;
&lt;p&gt;于是产生了 490 个帖子。&lt;/p&gt;
&lt;p&gt;Misskey 的 UI 太丝滑了，加入了&lt;a href=&quot;https://relay.nya.one/&quot;&gt;喵家中继&lt;/a&gt;后，我的服务器联合上了不少高质量的中文实例。&lt;/p&gt;
&lt;p&gt;手机上也安装了 &lt;a href=&quot;https://github.com/poppingmoon/aria&quot;&gt;Aria&lt;/a&gt;，随时随地刷帖子和时间线。&lt;/p&gt;
&lt;p&gt;几个月的时间里，我几乎每天都会打开联邦宇宙，看着各式各样的人分享自己的生活和成果。&lt;/p&gt;
&lt;p&gt;而且，或许是比较小众、门槛有些高，这里的内容质量可比那些大平台高多了，不需要沙海淘金就能看到许多符合喜好的高质量内容，关注到许多高质量用户。对于令人反感的用户和帖子，也可以直接静音/拉黑，把它彻底「请」出你的视野。&lt;/p&gt;
&lt;p&gt;而且由于去中心化的特点，你是自由的——你可以随意迁移到另外一个实例，导出你的内容，自己建立服务器，拓展联邦宇宙的边界……&lt;/p&gt;
&lt;p&gt;我认为这就是&lt;strong&gt;社交媒体的未来&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;我也尝试邀请了几位现实中的朋友来到这里，但是大家对联邦宇宙的热情似乎并不是很高。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;题外话。
实例还有很多空位和负载能力，欢迎各位加入。如果各位想加入的话：
如果您有本站的友链，可以直接联系我；
否则，如果您有一个博客，可以先申请友链；
或者直接带着您在别处的账号（无论是其他 Fedi 实例的账号，还是各种论坛和社交媒体活跃的证明），联系我加入&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;当然，事情也不是一帆风顺的，就在最近，发生了一件我怎么也想不到的事情——&lt;/p&gt;
&lt;h3&gt;域名事故和紧急迁移&lt;/h3&gt;
&lt;p&gt;众所周知，我建立 misskey 实例的时候，用的是 de 域名。&lt;/p&gt;
&lt;p&gt;而非常悲伤的事情是，de 域名由于注册局要求，对于约 10% 的高危用户需要进行 kyc 认证，对于国内用户是无法通过的——中文证件对面德国人看不懂。&lt;/p&gt;
&lt;p&gt;那就很悲伤了，虽然我自己没有收到 kyc 请求，但是谁知道呢？&lt;/p&gt;
&lt;p&gt;于是开始了&lt;a href=&quot;https://clanna.dev/notes/am1sqwo8zs8r002i&quot;&gt;紧急迁移&lt;/a&gt;，购买了新的域名 clanna.dev。&lt;/p&gt;
&lt;p&gt;但这一次，我没有继续使用 misskey，而是采用了其 Fork 分支 &lt;a href=&quot;https://activitypub.software/TransFem-org/Sharkey&quot;&gt;Sharkey&lt;/a&gt;，因为它有「编辑」功能，并且支持导入帖子（Misskey 缺失的那部分）。&lt;/p&gt;
&lt;p&gt;建立好服务器，观察一段时间后，我便立马执行了关注中和关注者迁移。&lt;/p&gt;
&lt;p&gt;其实过程中也是有些小插曲的，记录在了&lt;a href=&quot;https://clanna.dev/notes/am2scfjwux2m001x&quot;&gt;这里&lt;/a&gt;，比如 root 用户迁移失败、点批准关注到手断掉之类的。&lt;/p&gt;
&lt;p&gt;也给实例上的所有朋友发了消息，提醒他们尽快迁移到新的服务器。&lt;/p&gt;
&lt;p&gt;截止到现在，几乎全部的用户都完成了迁移，但其实 de 域名还没有收到 kyc 请求，大概率是安全了。&lt;/p&gt;
&lt;p&gt;所以……旧的实例也先不关闭了，等我做好一个「墓碑页面」，等待最后几个朋友赶上末班车之后，再彻底关闭服务器吧！&lt;/p&gt;
&lt;p&gt;至于那个 de 域名嘛，明年过期不续费就好啦~&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://img.samhou.top/1779595981991.webp&quot; alt=&quot;之前的账号&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://img.samhou.top/1779596044981.webp&quot; alt=&quot;迁移后的账号&quot;&gt;&lt;/p&gt;
&lt;h2&gt;Arch Linux&lt;/h2&gt;
&lt;h3&gt;再见，Windows&lt;/h3&gt;
&lt;blockquote&gt;
&lt;p&gt;I use Arch, btw.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;在整 Fediverse 的同时，也在被巨硬做的 Windows 折磨：&lt;/p&gt;
&lt;p&gt;关键时刻重启的系统更新，令人绝望的命令行支持，极低的自定义能力，超高的耗电速度和负载，114514 个系统 BUG，每一个对我来说都逐渐变得不可接受。&lt;/p&gt;
&lt;p&gt;于是，在清理红色的 C 盘，使用 &lt;a href=&quot;https://learn.microsoft.com/en-us/sysinternals/downloads/disk2vhd&quot;&gt;Disk2vhd&lt;/a&gt; 将整个笔记本“备份”为 vhd 文件后，我做出了大胆的决定：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;「扬了整个笔记本的 Windows，切换为 Arch Linux。」&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;其实我之前也用过一段时间的 Ubuntu 发行版，可惜那个时候还是高中，没有多少精力来折腾，Ubuntu 的 1919810 个 BUG 对一个全程图形页面安装的用户来说，是不可接受的。结果又换回了 Windows。&lt;/p&gt;
&lt;p&gt;所以这一次，决定从零开始——&lt;/p&gt;
&lt;p&gt;「自己来{掌控|折腾}一切」。&lt;/p&gt;
&lt;h3&gt;从零开始……？&lt;/h3&gt;
&lt;p&gt;在 &lt;a href=&quot;https://www.ventoy.net/&quot;&gt;Ventoy&lt;/a&gt; 中拷贝了 Arch Linux ISO 后，关闭安全启动，重启到 U 盘，便进入了 Arch ISO 安装环境。&lt;/p&gt;
&lt;p&gt;一切都十分顺利……才怪勒！！&lt;/p&gt;
&lt;p&gt;对于硬盘分区方案我只是有所耳闻，但是出于对数据安全的重视，毫不犹豫地在笔记本上选择了加密整个硬盘。&lt;/p&gt;
&lt;p&gt;——但很可惜，当时的我对 LVM 和 LUKS 这些东西是没有任何概念的。&lt;/p&gt;
&lt;p&gt;于是只好求助狗屁通，让它给了我一些帮助入门的命令。&lt;/p&gt;
&lt;p&gt;结果显而易见，LLM 生成的幻觉非常严重，是有错误的，旧版和新版的命令混合在一起，安装后系统根本无法正常启动。&lt;/p&gt;
&lt;p&gt;行吧，还得我自己来。&lt;/p&gt;
&lt;p&gt;一番搜索之后，找到了 &lt;a href=&quot;https://wiki.archlinuxcn.org/wiki/Dm-crypt/%E5%8A%A0%E5%AF%86%E6%95%B4%E4%B8%AA%E7%B3%BB%E7%BB%9F&quot;&gt;Arch Linux 的 Wiki 文章&lt;/a&gt;。&lt;/p&gt;
&lt;p&gt;选择了「在 LUKS 上配置 LVM」后，重新生成 initramfs，启动……&lt;/p&gt;
&lt;p&gt;依旧黑屏。&lt;/p&gt;
&lt;p&gt;花了几个小时诊断问题，在时间将要到达凌晨零点的时候，我终于明白了问题所在。&lt;/p&gt;
&lt;p&gt;我是用的是 sd-encrypt，但在 grub 配置中，却用的是非 sd-encrypt 内核参数。&lt;/p&gt;
&lt;p&gt;气笑了。&lt;/p&gt;
&lt;p&gt;这个故事告诉我们，千万别在早上晚上装 Arch，因为你的{脑子不一定正常运转|早晚会出事}！&lt;/p&gt;
&lt;p&gt;当我看到熟悉的 tty 时，真的太激动了。&lt;/p&gt;
&lt;p&gt;首次 Arch 安装耗费了 3 个小时，没有用安装脚本，很多概念都是从零开始，但这仍然是一段有意义的旅途：&lt;/p&gt;
&lt;p&gt;Linux 到底是怎么启动的，对整个过程有了大致的了解。&lt;/p&gt;
&lt;p&gt;不过既然已经零点了，&lt;del&gt;事已至此，先睡觉吧&lt;/del&gt;。&lt;/p&gt;
&lt;h3&gt;联网和桌面环境&lt;/h3&gt;
&lt;p&gt;时间来到第二天早上。&lt;/p&gt;
&lt;p&gt;当我兴奋地输入解密密码开机，登入 tty 准备装桌面的时候，pacman 直接来了一大堆报错。&lt;/p&gt;
&lt;p&gt;网络错误？&lt;/p&gt;
&lt;p&gt;等等。好像在安装的时候有提示要装网络管理器来着。&lt;/p&gt;
&lt;p&gt;但我什么都没装。&lt;/p&gt;
&lt;p&gt;又被自己气笑之后，再次回到 ISO 安装环境，装上了 netctl。&lt;/p&gt;
&lt;p&gt;这次终于把 wifi 搞定了，pacman 能正常安装软件包了。&lt;/p&gt;
&lt;p&gt;事不宜迟，由于之前用的 Ubuntu 默认是 Gnome 桌面，于是选择了比较熟悉的 Gnome 作为桌面环境。&lt;/p&gt;
&lt;p&gt;但问题又来了，桌面环境的联网设置不可用。&lt;/p&gt;
&lt;p&gt;一番搜索之后，这时我才明白，Gnome 默认的联网模块使用的是 NetworkManager。&lt;/p&gt;
&lt;p&gt;行，打开终端卸载 netctl，装好 NetworkManager……&lt;/p&gt;
&lt;p&gt;联网正常了。&lt;/p&gt;
&lt;p&gt;一切都是如此丝滑，顺利得甚至让我以为昨天{安装|折腾}到零点只是一场梦。&lt;/p&gt;
&lt;h3&gt;越调越好用&lt;/h3&gt;
&lt;p&gt;经过一段时间的使用，以及把我的台式机也换成 Arch 之后，我又干了几件事情：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;抛弃了天天出 Bug 的 Gnome，换成了 KDE&lt;/li&gt;
&lt;li&gt;自定义了 Rime 的输入方案&lt;/li&gt;
&lt;li&gt;写了 Kitty 的 Config，自定义了终端的表现&lt;/li&gt;
&lt;li&gt;安装了各种各样的开发 IDE&lt;/li&gt;
&lt;li&gt;学了点 Vim&lt;/li&gt;
&lt;li&gt;用虚拟机跑 Win，来测试 Office 功能&lt;/li&gt;
&lt;li&gt;装了 Steam 发现几乎所有游戏都能在 Linux 上玩&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;就这样，这段 Arch 的旅途仍在不断延续。&lt;/p&gt;
&lt;p&gt;或许以后再也不会回到 Windows 了吧。&lt;/p&gt;
&lt;p&gt;作为 Arch 的用户，我表示：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Arch 真是太爽了！&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;写作风格&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;「在系统计划任务中，有一个“发生事件时”的触发器。」&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;上面这句话，是这个博客的&lt;a href=&quot;https://blog.samhou.moe/run-program-on-usb-insertion-with-windows-task-scheduler/&quot;&gt;第一篇文章&lt;/a&gt;的第一句话。&lt;/p&gt;
&lt;p&gt;2021-09-05，1722 天前，那时我还是个初中生，这个博客也是托管在 Github Pages 上的，用的域名还是 github.io。&lt;/p&gt;
&lt;p&gt;细心的读者肯定已经发现了，整篇文章非常短，文风也非常不一样。&lt;/p&gt;
&lt;p&gt;这个博客到现在已经快 5 年了，当我回首这段旅途的时候，我突然发现了自己文风的形成——&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;从不带感情的平铺直叙，到轻松愉快的「有梗」文章&lt;/li&gt;
&lt;li&gt;从「基本没有格式」，到「熟练使用 Markdown」，并自定义整个博客&lt;/li&gt;
&lt;li&gt;从中英不带空格，到中英、数字必断开的写作习惯&lt;/li&gt;
&lt;li&gt;从只讲方法，到讲清原理和先决条件再动手&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;或许各位老读者已经注意到了，这篇文章和之前的文章似乎也不太一样&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;引号不仅用“”，现在更偏爱「」&lt;/li&gt;
&lt;li&gt;增加了{注音标示|Ruby}这种「写作XX，读作OO」类似的格式&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;实际上，这两项也是有原因的，而且就是最近发生的事情。&lt;/p&gt;
&lt;p&gt;「」这个符号，是因为用了 Misskey，然后去给 Sharkey 做中文翻译的时候，发现 Misskey 默认用的是「」而不是“”，随着在翻译中不断的重复使用，就喜欢上了这个{可爱|看起来很酷}的括号。&lt;/p&gt;
&lt;p&gt;而后者就简单多了，我在&lt;a href=&quot;https://clanna.dev/notes/amlwiuqbhvb40066&quot;&gt;这个帖子里&lt;/a&gt;解释了原因，这里就不再赘述了。&lt;/p&gt;
&lt;p&gt;既然已经形成了文风，突然想起个名字——&lt;/p&gt;
&lt;p&gt;就把我的这种写作风格，叫作 SFB 吧！&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;「SamHou Flavored Blog」&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;后记&lt;/h2&gt;
&lt;p&gt;说起来这篇也写得相当长了呢，几乎是前两篇的长度总和了。&lt;/p&gt;
&lt;p&gt;那本期的{杂谈月刊|咕咕咕}就到这里了，各位读者下次再见！&lt;/p&gt;
&lt;p&gt;&lt;del&gt;写完的时候都是 5-24 了，到底拖延了多久啊这个人&lt;/del&gt;&lt;/p&gt;
&lt;p&gt;该继续{网上|水星}冲浪，积累素材了~&lt;/p&gt;
</content:encoded></item><item><title>DroidSpaces 在安卓上跑 Linux 发行版踩坑实录</title><link>https://blog.samhou.moe/droidspaces-linux/</link><guid isPermaLink="true">https://blog.samhou.moe/droidspaces-linux/</guid><description>文章记录在安卓设备上通过 DroidSpaces 运行 Linux 容器的实践：需解锁、内核支持与 root；成功启动无桌面系统，但桌面失败；重点排查并解决 Tailscale 导致的 DNS 解析问题，整体可作轻量服务器使用。</description><pubDate>Tue, 28 Apr 2026 19:05:02 GMT</pubDate><content:encoded>&lt;blockquote&gt;
&lt;p&gt;AI 生成披露：封面由 AI 生成。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;最近朋友 &lt;a href=&quot;https://blog.aronnaxm.top/&quot;&gt;@AronnaxM&lt;/a&gt; 给我发了个&lt;a href=&quot;https://www.coolapk.com/feed/70899541?s=NGIxYjhiNTExZmVlZjYwZzY5ZGEzZDBjega1612b2&quot;&gt;酷安帖子&lt;/a&gt;，令人眼前一亮——直接在安卓上用 &lt;a href=&quot;https://github.com/ravindu644/Droidspaces-OSS&quot;&gt;Droidspaces-OSS&lt;/a&gt; 跑起来一个完整的 Linux 发行版容器！？太有意思了，正好我手上有一块 MI PAD 6 Pro，正好是酷安大佬支持的机型，就立马上手操作了。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;放低期待&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;先说结论：能起来一个可用的服务器，但是无法启动桌面环境。大佬给的那个“教程”的类似物完全就是碎成渣了，根本无法复现。不过能跑一个无桌面环境的 Linux 已经很不错了~&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;先决条件&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;解开 BL 锁&lt;/li&gt;
&lt;li&gt;酷安大佬内核支持，或你有自行编译内核的能力&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;刷入系统 boot 分区&lt;/h2&gt;
&lt;p&gt;首先，你的内核不一定能够把 Droidspaces 跑起来，因为需要内核支持：&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://img.samhou.top/1777295253179.webp&quot; alt=&quot;内核检查&quot;&gt;&lt;/p&gt;
&lt;p&gt;下载&lt;a href=&quot;https://cdn.goldzxcbug.top/%E7%A7%BB%E5%8A%A8-Droidspaces-OSS&quot;&gt;大佬的 boot 镜像&lt;/a&gt;，或自行编译内核后，放到你的手机/板子里面。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;编译内核&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;由于我的 MI PAD 6 Pro 已被大佬的 GKI 测试通过，所以编译内核不在本文的讨论范围内。请参阅 &lt;a href=&quot;https://github.com/ravindu644/Droidspaces-OSS/blob/main/Documentation/Kernel-Configuration.md#kernel-config&quot;&gt;DroidSpaces 官方教程&lt;/a&gt;，完成内核编译，才能通过上述检测。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;由于 Droidspaces &lt;strong&gt;需要 root 支持&lt;/strong&gt;，上述下载或编译的 boot 必须进行修补，才能让 KernelSU 加载（是的，这和正常获取 root 并没有什么不同）。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;支持的 root&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;在&lt;a href=&quot;https://github.com/ravindu644/Droidspaces-OSS#rooting-requirements&quot;&gt;这里&lt;/a&gt;查看支持的 root 方案。选择 KernelSU 是最佳的。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;kernelsu 修补 boot，这个是 root 的基本操作了。把修补后的 boot 复制到电脑上，进入手机 fastboot 模式：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;adb reboot bootloader
fastboot flash boot xxxx.img
fastboot reboot
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;就这么两行老掉牙的命令，刷入 boot 开机。&lt;/p&gt;
&lt;p&gt;不出意外的话，你应该能正常进入系统。用 KernelSU 给 Droidspaces 赋予 root 权限：&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://img.samhou.top/1777371488840.webp&quot; alt=&quot;给 root&quot;&gt;&lt;/p&gt;
&lt;p&gt;现在打开 Droidspaces，过一下引导（安装模块等），然后右上角设置，选择“要求”（Droidspaces 运行要求）：&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://img.samhou.top/1777373131880.webp&quot; alt=&quot;运行要求检查&quot;&gt;&lt;/p&gt;
&lt;p&gt;你现在应该能看到所有选项都是绿色的勾：&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://img.samhou.top/1777295253179.webp&quot; alt=&quot;内核检查&quot;&gt;&lt;/p&gt;
&lt;p&gt;当你看到绿勾，说明可以跑 linux 发行版了！&lt;/p&gt;
&lt;h2&gt;启动你的第一个容器&lt;/h2&gt;
&lt;p&gt;按照原来项目的说法，要启动 Linux，你必须获取 rootfs 的镜像。这可以在 &lt;a href=&quot;https://images.linuxcontainers.org/&quot;&gt;Linux Containers&lt;/a&gt; 镜像列表中找到。&lt;/p&gt;
&lt;p&gt;下载对应你的机型的架构的镜像（通常是 arm64），然后复制到你的手机/平板上。比如，我想要这个 debian 13 的系统：&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://img.samhou.top/1777373380673.webp&quot; alt=&quot;点这个时间开始下载！&quot;&gt;&lt;/p&gt;
&lt;p&gt;要创建一个新的容器，切换到“容器”页面，点击右下角的加号，然后在弹出的文件选择中，选择你刚才获取的镜像。&lt;/p&gt;
&lt;p&gt;然后给容器起个名字，设定主机名：&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://img.samhou.top/1777373606736.webp&quot; alt=&quot;Debian 容器命名&quot;&gt;&lt;/p&gt;
&lt;p&gt;点击下一步，打开你想要的设置，比如挂载存储（强烈推荐！打开挂载，存储容器里面就能直接访问手机上的文件了）：&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://img.samhou.top/1777373727027.webp&quot; alt=&quot;配置设置&quot;&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;自行探索&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;开头提到过，桌面环境怎么都起不来。所以硬件访问和 Termux 没啥用。如果有谁成功了，欢迎联系我分享你的做法（发个邮件，或者在 fediverse 上发个私信都可以~）。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;开机自启动自己想开就开。安全与启动页面建议默认，&lt;del&gt;我对内核这块了解不多，所以没办法和各位分享它们的含义，能看懂的评论区见&lt;/del&gt;。&lt;/p&gt;
&lt;h2&gt;开机进入终端&lt;/h2&gt;
&lt;p&gt;点一下播放按钮，开机。现在，你的容器应该已经在运行了：&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://img.samhou.top/1777374103134.webp&quot; alt=&quot;运行中的容器&quot;&gt;&lt;/p&gt;
&lt;p&gt;接下来，我们连接到容器。选择面板，选中活跃容器底下你的容器名字，你应该能看到这个：&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://img.samhou.top/1777374231850.webp&quot; alt=&quot;容器操作&quot;&gt;&lt;/p&gt;
&lt;p&gt;这个界面应该一看就会了吧？点第二个打开，就能直接进入你新安装的系统了。点击管理，可以直接管理系统的 systemd（方便~）&lt;/p&gt;
&lt;p&gt;现在来看看“复制登录命令”。这个按钮允许你在外部终端（只要有宿主手机/平板 root 权限的 shell），直接通过复制的命令进入你的 Linux。&lt;/p&gt;
&lt;p&gt;比如，adb shell（记得在 KernelSU 里面给 shell root 权限）：&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://img.samhou.top/1777374431199.webp&quot; alt=&quot;直接用上 adb&quot;&gt;&lt;/p&gt;
&lt;p&gt;现在你就可以干你想干的事情了。（别尝试跑 docker 了，嵌套虚拟化很困难的）&lt;/p&gt;
&lt;h2&gt;网络问题？&lt;/h2&gt;
&lt;p&gt;现在就要涉及到这篇文章最有含金量的地方了：网络。&lt;/p&gt;
&lt;p&gt;&lt;del&gt;Debug 了几个小时&lt;/del&gt;&lt;/p&gt;
&lt;p&gt;不知道你是否记得，你在安装容器的时候，网络选项中选择了“主机”。这就让容器和宿主机拥有了完全相同的网络环境！&lt;/p&gt;
&lt;p&gt;然后我就发现：容器内 DNS 炸了。&lt;/p&gt;
&lt;p&gt;理论上来说这不该发生，因为容器和外部共享网络和魔法环境。&lt;/p&gt;
&lt;p&gt;经过一个小时的 debug，我突然想起来：&lt;/p&gt;
&lt;p&gt;我的宿主机，安装了 magisk-tailscaled！而 tailscale 有一个功能，&lt;a href=&quot;https://tailscale.com/docs/features/magicdns&quot;&gt;Magis DNS&lt;/a&gt;：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;If you add a new web server called &lt;code&gt;my-server&lt;/code&gt; to your network, you no longer need to use its Tailscale IP: using the name &lt;code&gt;my-server&lt;/code&gt; in your browser&apos;s address bar or on the command line will work.
MagicDNS does not require a &lt;a href=&quot;https://tailscale.com/docs/reference/dns-in-tailscale#tailscale-dns-settings&quot;&gt;DNS nameserver&lt;/a&gt; if running Tailscale v1.20 or later. Otherwise, your network must have &lt;strong&gt;at least one DNS nameserver set&lt;/strong&gt; in the &lt;a href=&quot;https://login.tailscale.com/admin/dns&quot;&gt;DNS&lt;/a&gt; page of the admin console. These nameservers will receive all DNS queries not handled by MagicDNS.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;也就是说，它会对系统的 DNS 进行干扰！这就导致了容器内“可以 Ping 通任何 IP，但是无法解析任何域名”的问题，这个问题在魔法+容器网络环境下彻底被暴露出来。&lt;/p&gt;
&lt;p&gt;要解决也很简单，在 adb 里面执行&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;su
tailscale set --accept-dns=false
tailscale down
tailscale up
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;直接解决问题！&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;坑人的 DNS&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;tailscale 这个默认开启的 DNS 已经坑了我好多次了。关掉保平安~&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;好了，这就是本文的全部内容了！很可惜的一点是，我无法复现 boot 镜像作者的桌面环境。或许过一段时间，作者会自己更新教程，那时候再说~&lt;/p&gt;
</content:encoded></item><item><title>从零开始配置 VPS —— 主机名、用户组和远程权限安全实践</title><link>https://blog.samhou.moe/safe-vps/</link><guid isPermaLink="true">https://blog.samhou.moe/safe-vps/</guid><description>本文详述了 VPS 从零开始的安全配置，包括更新系统、修改主机名、创建新用户并赋予 sudo 权限。重点介绍了 SSH 密钥登录、禁用 root 登录及密码登录，调整 SSH 端口。同时配置 UFW 防火墙以开放必要端口，并安装 Fail2ban 自动封禁恶意 IP，全面提升 VPS 安全性。</description><pubDate>Fri, 17 Apr 2026 19:04:03 GMT</pubDate><content:encoded>&lt;p&gt;你是不是一直有这样的问题：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;买了台 VPS，但是默认的镜像用的是 root 感觉一点也不安全&lt;/li&gt;
&lt;li&gt;天天有人扫 22 端口，不小心设置了弱密码，机器被爆破之后被利用，惨遭删机&lt;/li&gt;
&lt;li&gt;新买的 VPS 不知道做什么安全措施好&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;如果是，那么这篇文章就是为你准备的，我们将一步一步配置，提升你的 VPS 安全性。&lt;/p&gt;
&lt;p&gt;今天我重装了一台吃灰的 VPS，把整个提升安全性的措施记录在这里，建议收藏一下，以后每次买了新机都可以回来抄作业~&lt;/p&gt;
&lt;p&gt;让我们开始吧！&lt;/p&gt;
&lt;h2&gt;连接到 VPS&lt;/h2&gt;
&lt;p&gt;这个不多说，检查面板或者邮件，里面有 root 密码。如果没说端口号就是 22。实在连不上开个工单问问商家也行。&lt;/p&gt;
&lt;p&gt;确保你能连上，比如这是我的，一台 Debian 服务器：&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://img.samhou.top/1776416800430.webp&quot; alt=&quot;连接 SSH&quot;&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;dd 脚本&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;如果你要用脚本&lt;strong&gt;重装系统&lt;/strong&gt;，请现在执行，因为这会&lt;strong&gt;清理所有配置和数据&lt;/strong&gt;。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;系统更新&lt;/h2&gt;
&lt;p&gt;因为你刚部署机器，如果硬盘充足，你可以更新一次系统，这样可以显著减少安全隐患，而且万一不小心被更新搞坏了，还能重新点重装，成本不高：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;apt update  
apt upgrade
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;注意这是 debian 的 apt 包管理。你可能需要根据系统调整命令。&lt;/p&gt;
&lt;p&gt;然后重启，看看机器能不能启动，ssh 能否连上。如果很不幸系统死了，请重装，然后跳过系统更新这一步。&lt;/p&gt;
&lt;h2&gt;探针、测评&lt;/h2&gt;
&lt;p&gt;如果需要探针和测评，请现在执行。如果是关键的建站机器，不建议打开远程 ssh，防止面板被黑一锅端。&lt;/p&gt;
&lt;p&gt;此处较为简单，不再提供参考。探针一般有自己的一键安装脚本和配置，仅仅列出 NQ 标准测试的脚本：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;bash &amp;lt;(curl -sL https://run.NodeQuality.com)
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;修改机器名称&lt;/h2&gt;
&lt;p&gt;买来的机器一般是商家生成的&lt;em&gt;随机名称&lt;/em&gt;。改一下，改成更加&lt;strong&gt;有意义&lt;/strong&gt;的名称，比如我这台机器在香港，用来跑 copilot cli 做 vibe 的，就起名 &lt;code&gt;hk-copilot-dev-vps&lt;/code&gt;：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;hostnamectl set-hostname hk-copilot-dev-vps
&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Systemd&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;上面的命令仅仅适用于基于 systemd 的系统。如果你的系统执行该命令发生了错误，你需要以传统方式修改：
&lt;code&gt;nano /etc/hostname&lt;/code&gt; 打开文件写入新的名称，然后 &lt;code&gt;nano /etc/hosts&lt;/code&gt; 修改 127.0.1.1 后面的内容！&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;改名后重启让它生效：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;reboot
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;重启再连上 ssh，可以看到 bash 已经显示出了新的名字，这样就完成了！&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://img.samhou.top/1776416778704.webp&quot; alt=&quot;新的 VPS 主机名&quot;&gt;&lt;/p&gt;
&lt;h2&gt;改密、添加用户&lt;/h2&gt;
&lt;p&gt;先把 root 密码改成自己的。记得设定一个&lt;strong&gt;安全的密码&lt;/strong&gt;！你可以用各种密码管理器来生成一些。&lt;/p&gt;
&lt;p&gt;输入 &lt;code&gt;passwd&lt;/code&gt; 改密。手滑了也没关系，比如下面的图片不小心按了 enter 创建了空密码，系统会自动提示密码为空，再次输入就行了。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://img.samhou.top/1776417268965.webp&quot; alt=&quot;改 root 密码&quot;&gt;&lt;/p&gt;
&lt;p&gt;接下来我们创建一个&lt;strong&gt;用户&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;其实如果只有你一个人用，root 也不是不行。但是如果你经常手滑希望加上 sudo 确认一下，或者要用 Homebrew，创建一个新的用户仍然是必要的。&lt;/p&gt;
&lt;p&gt;用 adduser 增加一个用户：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;adduser samhou
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;输入用户密码。可以不用提供那堆信息，直接 enter 即可。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://img.samhou.top/1776417475870.webp&quot; alt=&quot;创建新的用户&quot;&gt;&lt;/p&gt;
&lt;h2&gt;sudo 和免密码&lt;/h2&gt;
&lt;p&gt;现在我的用户没有管理权限，因此我们需要赋予 sudo 的权限，允许用户暂时以 root 执行操作。&lt;/p&gt;
&lt;p&gt;为了防止商家给的镜像太过于精简，首先你得装好 sudo：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;apt install sudo
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;注意上面的命令适用于使用 apt 的系统。&lt;/p&gt;
&lt;p&gt;我的机器看起来已经有了。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://img.samhou.top/1776417583402.webp&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;在常见的 debian 系统中，sudo 是一个用户组，你只要把用户加入这个用户组，即可赋予 sudo 权限：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;usermod -aG sudo samhou
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;现在切换到新创建的用户：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;su samhou
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;你应该仍然处于 root 文件夹里面，这个文件夹只有默认管理员才能碰。尝试下面的命令：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;ls
sudo ls
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;如果第一个 ls 显示 &lt;code&gt;Permission denied&lt;/code&gt;，第二个要求你输入密码然后输出了文件夹内容，说明你配置正确了！&lt;/p&gt;
&lt;p&gt;（我这里文件夹没有任何内容，所以什么输出也没有）&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://img.samhou.top/1776418129653.webp&quot; alt=&quot;sudo 权限测试&quot;&gt;&lt;/p&gt;
&lt;p&gt;由于我们的 VPS 一般都是远程连接的，在 ssh 时已经证明身份了，所以可以关掉 sudo 的密码验证（对，这样的话龙虾、cli 这些东西也能随便玩管理员权限了，不用你再给密码），这需要编辑 sudoers。&lt;/p&gt;
&lt;p&gt;先回退 root 用户防止锁死自己，然后用 visudo 编辑：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;exit
visudo
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;如果你只想要自己免密，可以在最后加上一行：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;samhou ALL=(ALL) NOPASSWD:ALL
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;记得改成自己创建的用户名。&lt;/p&gt;
&lt;p&gt;如果你想让所有 sudo 都免密，比如你有多个用户，请找到这一行：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;%sudo ALL=(ALL:ALL) ALL
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;把它改成：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;%sudo ALL=(ALL:ALL) NOPASSWD:ALL
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&quot;https://img.samhou.top/1776418577675.webp&quot; alt=&quot;sudo 配置示例&quot;&gt;&lt;/p&gt;
&lt;p&gt;然后保存退出。再切换成目标用户尝试：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;su samhou
sudo ls
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;是不是不用密码了呢？如果没有要求输入密码，说明成功了！&lt;/p&gt;
&lt;h2&gt;SSH&lt;/h2&gt;
&lt;h3&gt;密钥登录&lt;/h3&gt;
&lt;p&gt;接下来，我们来配置 ssh 登录。首先，在&lt;strong&gt;本地&lt;/strong&gt;创建密钥：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;ssh-keygen -t ed25519
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;按照提示设置保存位置和密码。此处不再赘述。如果不提供保存位置，默认在你的用户文件夹下的 .ssh 文件夹中，文件以加密算法（这里是 ed25519）命名。密钥有一对，私钥和公钥，你必须保护好私钥，这是你连接到服务器时证明你身份的东西。而公钥需要安装到服务器上，让服务器知道你这个人。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://img.samhou.top/1776419093720.webp&quot; alt=&quot;密钥文件示例&quot;&gt;&lt;/p&gt;
&lt;p&gt;linux 可以直接上传公钥到服务器：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;ssh-copy-id samhou@IP
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;记得改成自己用户名和 IP，然后输入刚才创建的用户密码。&lt;/p&gt;
&lt;p&gt;如果你是 windows 没法用上面的命令，请跟我来操作，手动安装密钥。&lt;/p&gt;
&lt;p&gt;首先用你创建的&lt;strong&gt;用户凭据&lt;/strong&gt;登录（不要用 root！），然后在你的用户目录执行：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;mkdir .ssh
cd .ssh
nano authorized_keys
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;然后把你在上一步创建的公钥粘贴进去（公钥就是后缀带有 .pub 的文件内容）。&lt;/p&gt;
&lt;p&gt;保存。然后返回用户目录，改一下文件归属和权限（必须的，否则会被认为不安全而被拒绝）：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;cd ~
sudo chown -R samhou:samhou .ssh
sudo chmod 700 .ssh
sudo chmod 600 .ssh/authorized_keys
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这些命令应该没有任何输出。如果报错说明你肯定哪里出问题了。&lt;/p&gt;
&lt;p&gt;现在在本地用密钥登录试试。如果没有要求输入密码或要求输入密码解锁密钥，说明一切正常！&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;确保能登录&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;请确保你能用密钥登录，再继续执行后续操作！否则你会把自己锁在外面……如果你自己搜索解决方案后仍然无法解决问题，请寻求技术大佬的帮助。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3&gt;端口和登录控制&lt;/h3&gt;
&lt;p&gt;端口和登录认证控制都是由 &lt;code&gt;/etc/ssh/sshd_config&lt;/code&gt; 文件控制的。&lt;/p&gt;
&lt;p&gt;先打开这个文件（记得用 sudo 权限）&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;sudo nano /etc/ssh/sshd_config
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;接下来，我们来改一下端口。&lt;/p&gt;
&lt;p&gt;找到这个 Port 这一行，取消注释，然后修改端口，随便改一个 22 除外的，别让别人轻易猜到：&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://img.samhou.top/1776422908902.webp&quot; alt=&quot;11451 端口&quot;&gt;&lt;/p&gt;
&lt;p&gt;然后禁用 root 登录，找到 &lt;code&gt;PermitRootLogin&lt;/code&gt; 改成 no：&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://img.samhou.top/1776423023418.webp&quot; alt=&quot;拒绝 root 登录&quot;&gt;&lt;/p&gt;
&lt;p&gt;最后关闭密码登录（这样仅仅允许密钥登录），把 &lt;code&gt;PasswordAuthentication&lt;/code&gt; 改成 no，然后去除 &lt;code&gt;PermitEmptyPasswords no&lt;/code&gt; 的注释：&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://img.samhou.top/1776423108157.webp&quot; alt=&quot;关闭密码登录&quot;&gt;&lt;/p&gt;
&lt;p&gt;保存文件。然后重启 ssh 服务：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;sudo systemctl restart ssh
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;如果不行，试试另外一种：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;sudo systemctl restart sshd
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;现在用新的端口登录，尝试一下。如果你的主机提供商有非常高级的面板式防火墙，记得去打开你要的端口，防止连不上~&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;ssh samhou@IP -p 11451
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;-p&lt;/code&gt; 表示端口，&lt;strong&gt;不要在 IP 后面直接写冒号&lt;/strong&gt;，这是不对的！&lt;/p&gt;
&lt;p&gt;节省篇幅，此处不再展示相同的登录成功页面。&lt;/p&gt;
&lt;h2&gt;防火墙&lt;/h2&gt;
&lt;p&gt;如果你的主机商家比较菜，一般不会提供面板防火墙。这时候，你可以用 ufw 管理你的 iptables，快速配置防火墙。&lt;/p&gt;
&lt;p&gt;先装上：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;sudo apt install ufw
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;然后，允许你想要的端口，比如你建站想要的 web 服务端口，以及刚才我们改好的 11451 ssh 端口：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;sudo ufw allow 80
sudo ufw allow 443
sudo ufw allow 11451
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;确定你的 ssh 端口包含了之后&lt;/strong&gt;（别把自己锁外面了），你就可以激活防火墙了：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;sudo ufw enable
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;然后就是再次连接 ssh，看看能不能连上。如果你很不幸把自己拦截在外面了，可以用商家提供的 vnc 登录（不算 ssh 所以直接输入密码登入即可），关掉防火墙再连接。&lt;/p&gt;
&lt;h2&gt;fail2ban&lt;/h2&gt;
&lt;p&gt;虽然改完端口仅仅允许密钥登录，理论上已经非常安全了，但是我还是建议你加上 fail2ban 来提升安全性，它会自动封禁扫描你机器的 IP。&lt;/p&gt;
&lt;p&gt;先装好：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;sudo apt install fail2ban
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;然后把模板配置复制一份给自定义配置，打开新建的自定义配置文件：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;sudo cp /etc/fail2ban/jail.conf /etc/fail2ban/jail.local
sudo nano /etc/fail2ban/jail.local
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;忽略开头的注释，向下滚动，你首先会看到 &lt;code&gt;[DEFAULT]&lt;/code&gt; 这个标签，然后是一堆提示。继续向下滚动，根据注释，你可以配置你想要的 ban 策略，比如 10 分钟里面认证失败 5 次直接封禁 24 小时：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;bantime = 24h
findtime = 10m
maxretry = 5
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&quot;https://img.samhou.top/1776425361076.webp&quot; alt=&quot;配置策略&quot;&gt;&lt;/p&gt;
&lt;p&gt;继续向下滚动，找到 &lt;code&gt;[sshd]&lt;/code&gt;，添加 &lt;code&gt;enabled = true&lt;/code&gt;，然后改一下端口 port，改成目标端口：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;[sshd]
enabled = true
port = 11451
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;如果你的系统是基于 systemd 的（systemctl 命令可用），那么把 backend 改成下面这样：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;backend = systemd
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;如果你的系统不用 systemd，还是传统的行为，会把访问日志放在文件里，那么把 logpath 改成这样：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;logpath = /var/log/auth.log
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;请注意，不同系统上面的路径可能不一样。请自行查询你的系统使用的认证日志的路径，切忌直接复制。&lt;/p&gt;
&lt;p&gt;下面是我的系统使用的配置，debian 12 是基于 systemd 的系统：&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://img.samhou.top/1776425903666.webp&quot; alt=&quot;示例配置&quot;&gt;&lt;/p&gt;
&lt;p&gt;保存，重启 fail2ban，并检查状态：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;sudo systemctl restart fail2ban
sudo systemctl status fail2ban
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;如果显示绿的 active (running)，说明你的配置完全正确！如果出错了，不妨问问 AI 或者寻求技术大佬的帮助。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://img.samhou.top/1776425987293.webp&quot; alt=&quot;正在运行的 fail2ban&quot;&gt;&lt;/p&gt;
&lt;p&gt;过一段时间再来看，如果你的 VPS 被扫到了，下面的命令应该会显示被封禁的 IP：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;sudo fail2ban-client status
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;至此，你的 VPS 已经相当安全了，本文也就到此为止，你可以继续部署你想要的服务，然后睡个好觉了！&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;别忘了防火墙！&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;你在刚才操作的过程中可能用上了 ufw。如果需要开放端口，请执行 ufw allow 命令，别把自己和正常用户拦在外面了……&lt;/p&gt;
&lt;/blockquote&gt;
</content:encoded></item><item><title>奶奶都能看懂的 C++ —— 类、初始化、预处理和分离式编译</title><link>https://blog.samhou.moe/cpp-class-compile-guide/</link><guid isPermaLink="true">https://blog.samhou.moe/cpp-class-compile-guide/</guid><description>本文面向初学者讲解 C++ 类与自定义数据类型，介绍成员初始化、构造函数、默认构造和初始化列表，再将代码拆分为头文件与源文件。文章进一步说明预处理、分离式编译、重复定义与作用域运算符的作用，配合示例展示如何把类组织成可维护的多文件项目，适合入门练习。</description><pubDate>Sat, 21 Mar 2026 21:03:18 GMT</pubDate><content:encoded>&lt;p&gt;&lt;a href=&quot;https://blog.samhou.moe/cpp-function-pointer-type/&quot;&gt;上一节&lt;/a&gt;，我们讲了函数指针。现在，我们来仔细聊聊类这个东西。&lt;/p&gt;
&lt;p&gt;在 C++ 中，类是一种自定义的抽象数据结构，允许你把一组相关的数据存储到一起。什么叫&lt;strong&gt;抽象&lt;/strong&gt;数据结构呢？其实就是给一个东西建模，让单个数据&lt;em&gt;代表&lt;/em&gt;这个东西。&lt;/p&gt;
&lt;p&gt;有了大体的概念，让我们来看看类到底是怎么玩的。&lt;/p&gt;
&lt;h2&gt;自定义数据类型&lt;/h2&gt;
&lt;p&gt;先来一段简单的代码：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-cpp&quot;&gt;struct Person {
    string name;
    int age;
};
int main() {
    Person user;
    user.name = &quot;SamHou&quot;;
    user.age = 114514;
    cout &amp;lt;&amp;lt; user.name &amp;lt;&amp;lt; &apos; &apos; &amp;lt;&amp;lt; user.age &amp;lt;&amp;lt; endl;
    // SamHou 114514
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;我们在此使用了 struct 关键字，来定义属于自己的类。&lt;code&gt;Person&lt;/code&gt; 是这个类的&lt;strong&gt;名字&lt;/strong&gt;，包含在名字后面大括号中的，称为类的&lt;strong&gt;成员&lt;/strong&gt;。大括号外需要一个分号，表示这个类的结尾。&lt;/p&gt;
&lt;p&gt;现在来看 main 函数。&lt;/p&gt;
&lt;p&gt;首先，我们创建了一个 Person 类的&lt;strong&gt;实例&lt;/strong&gt;（也叫&lt;strong&gt;对象&lt;/strong&gt;），它的名字叫做 user。&lt;/p&gt;
&lt;p&gt;那什么叫创建实例呢？你可以这么理解：类是一种&lt;strong&gt;蓝图&lt;/strong&gt;，规定单个数据类型中，&lt;strong&gt;应该有&lt;/strong&gt;哪些成员。当我们创建实例的时候，就是根据这个蓝图建了一个“房子”（也就是存在于内存中的独立对象）。&lt;/p&gt;
&lt;p&gt;每个对象可以用自己的名字找到，也因此可以根据名字访问对象的成员。因此&lt;strong&gt;修改不同对象的成员&lt;/strong&gt;，并&lt;strong&gt;不会影响其它对象&lt;/strong&gt;（即使它们是由同一个蓝图建立的）。&lt;/p&gt;
&lt;p&gt;在之前的文章中，我们已经用了很久这个 &lt;code&gt;.&lt;/code&gt; 了。它表示的是，访问一个类的成员，称之为&lt;strong&gt;成员运算符&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;因此，上面的代码表示，我们把 name 成员改成了 &quot;SamHou&quot; 这个字符串，然后把 age 改成了 114514；然后用同样的方法，再输出。&lt;/p&gt;
&lt;p&gt;要注意的一点是，任何东西（包括类）都要遵循&lt;a href=&quot;https://blog.samhou.moe/cpp-return-scope-life/#%E4%BD%9C%E7%94%A8%E5%9F%9F%E5%92%8C%E7%94%9F%E5%91%BD%E5%91%A8%E6%9C%9F&quot;&gt;名字作用域和对象生命周期规则&lt;/a&gt;。如果你忘了，可以复习一下。&lt;/p&gt;
&lt;p&gt;基本用法很简单，不是吗？但我们得仔细再深入挖一下。&lt;/p&gt;
&lt;p&gt;比如，这时候你可能就有个疑问了——创建一个类的实例的时候，里面的成员会怎样初始化呢？换句话说，那个 user 的 name 和 age 默认是什么呢？&lt;/p&gt;
&lt;p&gt;这就要来讨论下初始化的问题了。&lt;/p&gt;
&lt;h2&gt;类成员的初始化&lt;/h2&gt;
&lt;h3&gt;默认初始化&lt;/h3&gt;
&lt;p&gt;如果你&lt;strong&gt;什么都不添加&lt;/strong&gt;，那么类会执行默认初始化。在这个过程中，&lt;strong&gt;每个成员&lt;/strong&gt;都会执行其&lt;em&gt;自身&lt;/em&gt;的&lt;strong&gt;默认初始化&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;我们回看上面的程序：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-cpp&quot;&gt;struct Person {
    string name;
    int age;
};
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;string 的默认初始化会创建空字符串。而 int 类型的默认初始化，则会变成一个不可预测的随机值（未定义的值）。&lt;/p&gt;
&lt;p&gt;也就是说——你或许根本不知道会发生什么。如果你的类里面有指针类型，那就更麻烦了，未定义的指针谁也不知道指向了什么，这种行为是非常危险的。&lt;/p&gt;
&lt;p&gt;因此，我们先来说说最简单的初始化——&lt;em&gt;类内初始值&lt;/em&gt;。&lt;/p&gt;
&lt;h3&gt;类内初始值&lt;/h3&gt;
&lt;p&gt;类内初始值，和给变量初始化的语法就是一样的：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-cpp&quot;&gt;struct Person {
    string name = &quot;Example name&quot;;
    int age = 114;
};
int main() {
    Person user;
    cout &amp;lt;&amp;lt; user.name &amp;lt;&amp;lt; &apos; &apos; &amp;lt;&amp;lt; user.age &amp;lt;&amp;lt; endl;
    // Example name 114
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;我们通过直接写 = 的方式，给每个类的成员赋了初始值。&lt;/p&gt;
&lt;p&gt;在上面的代码中，我们创建实例之后，并没有对这个实例做任何修改。因此，输出的就是默认值。&lt;/p&gt;
&lt;p&gt;但是，有时候一行也太局限了，有没有一种更加灵活的方式，允许我们进行更加自定义的操作呢？&lt;/p&gt;
&lt;p&gt;当然是有的——这叫做&lt;strong&gt;构造函数&lt;/strong&gt;。&lt;/p&gt;
&lt;h3&gt;构造函数&lt;/h3&gt;
&lt;p&gt;顾名思义，构造函数就是类根据蓝图，创建实例时，&lt;strong&gt;执行的初始化函数&lt;/strong&gt;。&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-cpp&quot;&gt;struct Person {
    string name = &quot;Example name&quot;;
    int age = 114;
    int passKey;
    Person(string n, int a, int password) {
        name = n;
        age = a;
        passKey = age + password;
    }
};
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;构造函数是一个和&lt;strong&gt;类同名&lt;/strong&gt;的&lt;strong&gt;无返回值&lt;/strong&gt;函数，接受自定义的参数，然后执行自定义操作。可以直接在构造函数内，通过名字访问类的成员，不需要使用成员运算符（构造函数的作用域里也没有这个实例的名字）。&lt;/p&gt;
&lt;p&gt;&lt;em&gt;实际上，这种访问类成员的行为和 this 指针有关。将在下一篇详细解读。&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;在上面的代码中，我们接受了 n a password 三个值，然后把 n a 赋值给成员，再根据 age 和 password 生成 passKey。（注意：&lt;strong&gt;这是一个构造函数示例。千万不要理解为任何密码生成方法&lt;/strong&gt;）&lt;/p&gt;
&lt;p&gt;定义好构造函数后，创建实例时怎么调用呢？来看看吧：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-cpp&quot;&gt;int main() {
    Person user(&quot;SamHou&quot;, 114514, 233);
    cout &amp;lt;&amp;lt; user.name &amp;lt;&amp;lt; &apos; &apos; &amp;lt;&amp;lt; user.age &amp;lt;&amp;lt; endl;
    // SamHou 114514
    cout &amp;lt;&amp;lt; user.passKey &amp;lt;&amp;lt; endl;
    // 114747
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Great! 直接在名字后面带上参数列表，即可调用构造函数！&lt;/p&gt;
&lt;p&gt;提示：&lt;strong&gt;实际上，初始化和在函数体内赋值属于不同的操作。在下一小节内，我们会详细阐述这个问题。先留个基本印象。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;现在让我们回到构造函数。我们不禁在想：有那么多参数通常不需要执行自定义操作，而是直接赋值给类的成员，有没有什么更加简便的方法，来执行初始化呢？&lt;/p&gt;
&lt;p&gt;C++ 设计者早就考虑到了。下面是一个等价的定义：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-cpp&quot;&gt;struct Person {
    string name = &quot;Example name&quot;;
    int age = 114;
    int passKey;
    Person(string n, int a, int password): name(n), age(a) {
        passKey = age + password;
    }
};
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;注意到了吗？我们直接在参数列表后，块之前用冒号加入了一个新的列表。这个列表通过初始化类成员的方式，为成员赋初始值，这叫作&lt;strong&gt;构造函数初始化列表&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;另外也要注意优先级的问题。&lt;/p&gt;
&lt;p&gt;永远记住，构造函数中的赋值操作优先级，高于构造函数初始化列表，最后才是类内初始值。为什么？你别急，看看下面就知道了。&lt;/p&gt;
&lt;h3&gt;合成的默认构造函数、初始化列表、重载&lt;/h3&gt;
&lt;p&gt;刚才我们讨论了自定义构造函数的情况。那么问题来了——如果构造函数不存在，那么会发生什么呢？&lt;/p&gt;
&lt;p&gt;嗯，你肯定已经知道了，我们上面也提到过：会首先根据类内初始值。如果类内初始值不存在，那么就会对每个成员，执行默认初始化。&lt;/p&gt;
&lt;p&gt;表象是这样的，但是探究一下本质。实际上，C++ 在没有构造函数的情况下，生成的是一个合成的&lt;strong&gt;默认构造函数&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;这个函数的行为你也很清楚了，上面的“表象”刚刚说过。&lt;/p&gt;
&lt;p&gt;正如其他函数一样，构造函数也可以重载，因此我们在自定义构造函数之外，用 default 可以把默认构造函数“找回来”：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-cpp&quot;&gt;struct myClass{
    myClass() = default; // 执行默认行为，也就是允许 myClass mc;
    myClass(int a,...)...
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;好的，现在回到自定义构造函数。刚才我们提到了优先级问题，现在来看看构造函数初始化列表到底怎么工作的：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;如果构造函数初始化列表存在，那么忽略&lt;em&gt;对应的&lt;/em&gt;&lt;strong&gt;类内初始值&lt;/strong&gt;，转而采用初始化列表对成员进行&lt;em&gt;初始化&lt;/em&gt;。&lt;/li&gt;
&lt;li&gt;执行完此类初始化操作&lt;strong&gt;后&lt;/strong&gt;，开始执行&lt;strong&gt;构造函数体&lt;/strong&gt;。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;哦，现在你知道问题所在了——&lt;strong&gt;构造函数初始化列表&lt;/strong&gt;和&lt;strong&gt;类内初始值&lt;/strong&gt;都属于&lt;strong&gt;初始化操作&lt;/strong&gt;。但是函数体内的呢？是赋值操作。既然有这么个“初始化 - 赋值”的运行顺序，自然就表现出这种优先级了（后执行的，永远会取代旧的初始化）。&lt;/p&gt;
&lt;p&gt;构造函数中的&lt;strong&gt;赋值&lt;/strong&gt;操作 &amp;gt; 构造函数&lt;strong&gt;初始化列表&lt;/strong&gt; &amp;gt; 类内&lt;strong&gt;初始值&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;聪明的读者一定发现了，&lt;em&gt;初始化&lt;/em&gt;和&lt;em&gt;赋值&lt;/em&gt;这两个概念我们非常熟悉对吧？我们在 &lt;a href=&quot;https://blog.samhou.moe/cpp-pointer-const-guide/&quot;&gt;const 限定符与指针&lt;/a&gt;这一节中，重点提过它们的区别，可以复习一下。&lt;/p&gt;
&lt;p&gt;之所以提 const，是因为一个非常重要的事情。&lt;/p&gt;
&lt;p&gt;如果是顶层 const，则一旦初始化就不能改变。因此，这一类成员&lt;strong&gt;仅仅可以通过构造函数初始化列表或类内初始值&lt;/strong&gt;进行初始化，不能在构造函数里面再赋新的值。&lt;/p&gt;
&lt;h2&gt;模块化开发&lt;/h2&gt;
&lt;h3&gt;头文件和源文件&lt;/h3&gt;
&lt;p&gt;在此之前，我们所创建的所有类，都是把函数体写在类内的。但实际上，C++ 更加推荐的一种方式，是把一个类的函数&lt;em&gt;声明&lt;/em&gt;放在&lt;strong&gt;头文件&lt;/strong&gt;里面，把函数&lt;em&gt;定义&lt;/em&gt;放在&lt;strong&gt;源文件&lt;/strong&gt;里面。&lt;/p&gt;
&lt;p&gt;我们在&lt;a href=&quot;https://blog.samhou.moe/cpp-func-arg-guide/#%E5%A3%B0%E6%98%8E%E5%92%8C%E5%AE%9A%E4%B9%89&quot;&gt;函数与参数传递&lt;/a&gt;这一节，曾经讲过声明和定义的区别，以及 &lt;code&gt;#include&lt;/code&gt; 头文件即可直接使用的好处。&lt;/p&gt;
&lt;p&gt;现在我们写的程序越来越长，于是不得不进行&lt;em&gt;模块化设计&lt;/em&gt;（把负责不同工作的代码放到&lt;strong&gt;不同文件&lt;/strong&gt;中），因此有必要说清楚以下概念的区别：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;头文件：用于&lt;strong&gt;声明&lt;/strong&gt;。类的所有成员声明应该放在这里，包括数据成员和函数成员&lt;/li&gt;
&lt;li&gt;源文件：用于&lt;strong&gt;定义&lt;/strong&gt;。必须实现声明中的所有函数成员，否则在调用时就会爆炸&lt;/li&gt;
&lt;li&gt;它们一般是独立的文件。&lt;/li&gt;
&lt;li&gt;C++ &lt;code&gt;#include&lt;/code&gt; 头文件后，即可直接使用&lt;/li&gt;
&lt;li&gt;如果不把声明放在头文件里面，非常容易导致重复定义（声明可以多次、但是&lt;strong&gt;定义只能一次&lt;/strong&gt;，下面会详细解释问题的根源）&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;来看例子，上面的程序如果进行模块化——&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-cpp&quot;&gt;// Person.h
#ifndef PERSON_INCLUDED
#define PERSON_INCLUDED
#include&amp;lt;string&amp;gt;
struct Person {
    std::string name = &quot;Example name&quot;;
    int age = 114;
    int passKey;

    Person(std::string n, int a, int password);
};
#endif // PERSON_INCLUDED
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-cpp&quot;&gt;// Person.cpp
#include &quot;Person.h&quot;

Person::Person(std::string n, int a, int password) // 先记住这个 :: 我们会详细解释这是什么
    : name(n), age(a) {
    passKey = age + password;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Main 函数不发生改变：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-cpp&quot;&gt;// main.cpp
#include &amp;lt;iostream&amp;gt;
#include &amp;lt;vector&amp;gt;
#include &quot;Person.h&quot;
using namespace std;
int main() {
    Person user(&quot;SamHou&quot;, 114514, 233);
    cout &amp;lt;&amp;lt; user.name &amp;lt;&amp;lt; &apos; &apos; &amp;lt;&amp;lt; user.age &amp;lt;&amp;lt; endl;
    cout &amp;lt;&amp;lt; user.passKey &amp;lt;&amp;lt; endl;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;看到了吗？我们只在主程序中 include 了头文件，声明就已经完成了。C++ 编译了定义，然后自动拼接起来——因此不需要 include 源文件。&lt;/p&gt;
&lt;p&gt;要注意的点是，数据成员和类内初始值应该放在头文件里面，构造函数的声明在头文件中不要包含初始化列表，而是要放在源文件定义中。&lt;/p&gt;
&lt;p&gt;要想深入了解编译的细节，我们要知道代码真正变成程序的过程中到底发生了什么。实际上，有三个重要阶段：预处理、分离编译、链接，让我们从代码出发，一步步讲清楚。&lt;/p&gt;
&lt;h3&gt;预处理和分离式编译&lt;/h3&gt;
&lt;p&gt;你可能有个大疑问，这个 &lt;code&gt;#include&lt;/code&gt; 到底是做什么的？为什么加上了，头文件里面的声明就能用了？&lt;/p&gt;
&lt;p&gt;这就要提到编译之前的&lt;strong&gt;预处理 (preprocessing)&lt;/strong&gt; 了。当你看到这个神奇的 # 号的时候，说明它是一个预处理命令，发生在实际的编译之前。&lt;/p&gt;
&lt;p&gt;而 &lt;code&gt;#include&lt;/code&gt; 这个预处理命令，就是把&lt;strong&gt;紧随其后&lt;/strong&gt;的头文件，直接&lt;strong&gt;复制&lt;/strong&gt;到这个源代码文件中。也就是说，编译只对处理过后的 .cpp 源代码生效，而头文件早已经被复制进去了。&lt;/p&gt;
&lt;p&gt;啥意思？你的代码在你执行编译之前，其实变成了这个样子：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-cpp&quot;&gt;// Person.cpp

// #include &quot;Person.h&quot; 现在不存在了！
// 内容被复制过来了！
#ifndef PERSON_INCLUDED
#define PERSON_INCLUDED
#include&amp;lt;string&amp;gt;
struct Person {
    std::string name = &quot;Example name&quot;;
    int age = 114;
    int passKey;

    Person(std::string n, int a, int password);
};
#endif // PERSON_INCLUDED
Person::Person(std::string n, int a, int password) // 先记住这个 :: 我们会详细解释这是什么
    : name(n), age(a) {
    passKey = age + password;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-cpp&quot;&gt;// main.cpp
// #include &amp;lt;iostream&amp;gt; 没错，这玩意也要复制
...
// #include &amp;lt;vector&amp;gt; 还有这个
...
// #include &quot;Person.h&quot;
// 内容被复制过来了！
#ifndef PERSON_INCLUDED
#define PERSON_INCLUDED
#include&amp;lt;string&amp;gt;
struct Person {
    std::string name = &quot;Example name&quot;;
    int age = 114;
    int passKey;

    Person(std::string n, int a, int password);
};
#endif // PERSON_INCLUDED
using namespace std;
int main() {
    Person user(&quot;SamHou&quot;, 114514, 233);
    cout &amp;lt;&amp;lt; user.name &amp;lt;&amp;lt; &apos; &apos; &amp;lt;&amp;lt; user.age &amp;lt;&amp;lt; endl;
    cout &amp;lt;&amp;lt; user.passKey &amp;lt;&amp;lt; endl;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;哦非常的厉害，&lt;code&gt;.h&lt;/code&gt; 直接消失了！现在我们假装自己是编译器，分别编译这两个文件（没错，这就叫作&lt;em&gt;分离式编译&lt;/em&gt;，每个文件分别编译然后链接起来变成程序）：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;编译 &lt;code&gt;Person.cpp&lt;/code&gt;&lt;ul&gt;
&lt;li&gt;声明 Person，包括其成员。&lt;/li&gt;
&lt;li&gt;用作用域运算符 &lt;code&gt;::&lt;/code&gt;（下方介绍），定义 Person 的成员&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;编译 &lt;code&gt;main.cpp&lt;/code&gt;&lt;ul&gt;
&lt;li&gt;声明 Person，包括其成员&lt;/li&gt;
&lt;li&gt;因为声明了，就可以直接使用，这在编译阶段没有任何问题&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;现在进行链接，把这些文件拼起来：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;找到了 Person 的声明。对应的构造函数确实只在 &lt;code&gt;Person.cpp&lt;/code&gt; 里面定义了一次，没问题！&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;了解了 &lt;code&gt;#include&lt;/code&gt; 和分离式编译，现在我们来解答上面的问题——到底为啥不能把定义写进头文件？&lt;/p&gt;
&lt;h3&gt;重复定义问题&lt;/h3&gt;
&lt;p&gt;来看看这个例子（只是个让你明白为啥不能这么写的小例子，别太关注具体的内容）：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-cpp&quot;&gt;// add.h
#ifndef ADD_INCLUDED
#define ADD_INCLUDED
int add(int a, int b)
{
    return a + b;
}
#endif // ADD_INCLUDED
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-cpp&quot;&gt;// main.cpp
#include &amp;lt;iostream&amp;gt;
#include &quot;add.h&quot;
using namespace std;
int main()
{
    cout &amp;lt;&amp;lt; add(1, 2) &amp;lt;&amp;lt; endl;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-cpp&quot;&gt;// myMath.cpp
#include &quot;add.h&quot;
int sum(int a, int b, int c)
{
    return add(a, b) + c;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;试着编译一下——&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;D:\code\Cpp\test\add.h|3|multiple definition of `add(int, int)&apos;; obj\Debug\main.o:D:/code/Cpp/test/add.h:3: first defined here|
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;果不其然，炸了。这个错误出现在链接阶段，因为经过预处理，编译阶段变成了这样：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-cpp&quot;&gt;// main.cpp
#include &amp;lt;iostream&amp;gt;
#ifndef ADD_INCLUDED
#define ADD_INCLUDED
int add(int a, int b)
{
    return a + b;
}
#endif // ADD_INCLUDED
using namespace std;
int main()
{
    cout &amp;lt;&amp;lt; add(1, 2) &amp;lt;&amp;lt; endl;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-cpp&quot;&gt;// myMath.cpp
#ifndef ADD_INCLUDED
#define ADD_INCLUDED
int add(int a, int b)
{
    return a + b;
}
#endif // ADD_INCLUDED
int sum(int a, int b, int c)
{
    return add(a, b) + c;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;没有任何问题，但是当链接到一起的时候，问题来了——&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;在两个源文件里面，都有 add 这个函数的定义！&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;C++ 就不允许多次定义，所以直接报错了。&lt;/p&gt;
&lt;p&gt;从上面的内容，我们可以得出下面的结论：&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;如果你把定义写在头文件里，一旦多个源文件引用了这个头文件，那么相当于重复定义，这是会直接报错的。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;因此，请确保你在头文件里面只写声明！&lt;/p&gt;
&lt;p&gt;一个小提示：你是不是在想：“为什么这里不拿上面的类来做例子呢”？这是因为如果直接在类内定义函数，那么这个函数就会默认变成 &lt;code&gt;inline&lt;/code&gt; 的。inline 的函数是一个&lt;em&gt;特例&lt;/em&gt;，它&lt;strong&gt;允许重复定义，不过这些定义必须全部相同&lt;/strong&gt;。&lt;/p&gt;
&lt;h3&gt;作用域运算符&lt;/h3&gt;
&lt;p&gt;等等，上面还有一个十分神奇的符号：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-cpp&quot;&gt;Person::Person(std::string n, int a, int password)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这是个啥？它叫作&lt;strong&gt;作用域运算符&lt;/strong&gt;。让我们回忆一下之前的&lt;a href=&quot;https://blog.samhou.moe/cpp-return-scope-life/?highlight=%E4%BD%9C%E7%94%A8#%E5%90%8D%E5%AD%97%E7%9A%84%E4%BD%9C%E7%94%A8%E5%9F%9F&quot;&gt;作用域&lt;/a&gt;一个小节中的内容……&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;让名字具有意义的地方，就叫做作用域（起作用的地方）。
在 C++ 中，名字的作用域，一般是一对花括号，也就是块（或者整个程序）。
一旦在作用域中声明（不是定义）一个名字，那么它就在该声明语句到作用域末尾有效。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt; 哦，这下清楚了——我们在类的外面写函数的定义，那么处于类的定义域之外，当然就找不到对应的名字了。&lt;/p&gt;
&lt;p&gt;作用域运算符，能够指定&lt;strong&gt;名字所在作用域&lt;/strong&gt;。Person 这个类后面的大括号组成了块，属于独立的作用域。因此 &lt;code&gt;Person::Person()&lt;/code&gt; 的含义，就是去 Person 这个名字对应的作用域里面，找一个名字为 Person 的函数。&lt;/p&gt;
&lt;p&gt;我们来画个图：&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://img.samhou.top/1774100618142.webp&quot; alt=&quot;作用域示例&quot;&gt;&lt;/p&gt;
&lt;p&gt;Person 这个类所在的有效区间是最大的，以红色框标识。因此，&lt;code&gt;Person::&lt;/code&gt; 可以找到 Person 这个类。黄色框标识 Person 类内的一个作用域，&lt;code&gt;Person::&lt;/code&gt; 把作用域限定在了这里面。然后，在黄色框中查找 &lt;code&gt;Person&lt;/code&gt; 这个名字，找到对应的构造函数 &lt;code&gt;Person()&lt;/code&gt;（蓝色框是它的有效区间，这里用不着，只是给你标出来而已）。&lt;/p&gt;
&lt;p&gt;也就是说，这个 :: 可以转换作用域，让目标名字被我们所找到，在类外定义类的成员函数时，是必须的！&lt;/p&gt;
&lt;p&gt;现在，你对类的工作方式和分离式编译已经有了一些基本了解了。下一节，我们将继续探索类中的一个重要概念，this 指针。然后进入面向对象的一个特征——封装，你将会学习对你的类进行访问控制。同样是通俗易懂的语言，下一篇再见！&lt;/p&gt;
</content:encoded></item><item><title>让你验证请求来自 CF —— Authenticated Origin Pulls</title><link>https://blog.samhou.moe/cf-authenticated-origin-pulls/</link><guid isPermaLink="true">https://blog.samhou.moe/cf-authenticated-origin-pulls/</guid><description>本文详细介绍了如何通过 &quot;Authenticated Origin Pulls&quot; 功能验证请求是否来自 Cloudflare 边缘节点，从而提升源站安全性。通过启用 mTLS 技术，使用 Cloudflare 提供的客户端证书配置 Nginx 实现验证，有效防止源站 IP 泄露与绕过攻击。</description><pubDate>Tue, 10 Mar 2026 22:04:13 GMT</pubDate><content:encoded>&lt;p&gt;套 Cloudflare，应该是各位站长的标准操作了吧。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;注意 SSL 配置&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;本文的前提是 Cloudflare 开启 SSL 严格/严格（完全）。否则无效。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;泄露 IP 和域名&lt;/h2&gt;
&lt;p&gt;当你建了个站，把你的域名解析到服务器，打开小黄云，你有没有想过——你怎么知道请求是不是 CF 传递过来的呢？&lt;/p&gt;
&lt;p&gt;众所周知，你的 Nginx 反向代理会根据请求的 Host 头判断目标服务，比如下面的这个 server 配置就来自我的某台服务器：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-nginx&quot;&gt;server {
&amp;nbsp; &amp;nbsp; listen 443 ssl;
&amp;nbsp; &amp;nbsp; listen [::]:443 ssl;
&amp;nbsp; &amp;nbsp; ... # ssl
&amp;nbsp; &amp;nbsp; server_name status.samhou.top; # Host
&amp;nbsp; &amp;nbsp; location / {
&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; proxy_pass http://uptime;
&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; ...
&amp;nbsp; &amp;nbsp; }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;也就是说，只要有人知道了你的服务域名+你的服务器 IP，就可以给你的源站发送请求，绕过 cloudflare 直接获取内容。&lt;/p&gt;
&lt;p&gt;这就麻烦了——你可能配了一堆 WAF，又是速率限制又是区域锁定，结果源站泄露，直接变得跟摆设一样。&lt;/p&gt;
&lt;p&gt;不信？你试试下面的 curl 命令：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;curl -k https://ip -H &quot;Host: example.com&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;替换成你的源站 IP 和你的域名试试执行。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://img.samhou.top/1773150190381.webp&quot; alt=&quot;html 全都出来了&quot;&gt;&lt;/p&gt;
&lt;p&gt;那怎么让源站验证你的请求确实来自 Cloudflare 的边缘节点呢？别急，Cloudflare 给你藏了一个非常隐秘的选项。&lt;/p&gt;
&lt;h2&gt;经过身份验证的源服务器拉取&lt;/h2&gt;
&lt;p&gt;先选中域名，然后导航到 SSL/TLS - 源服务器 - 经过身份验证的源服务器拉取，打开这个：&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://img.samhou.top/1773150463554.webp&quot; alt=&quot;经过验证的源服务器拉取&quot;&gt;&lt;/p&gt;
&lt;p&gt;这个选项会让 Cloudflare 向源服务器发送自己的证书。&lt;/p&gt;
&lt;p&gt;什么意思呢？&lt;/p&gt;
&lt;p&gt;大家都知道，源站有 SSL 证书来验证自己的身份（SSL 完全严格模式），那么客户端当然也可以有证书来验证自己的身份（这称之为 mTLS 技术）。在我们的情景下，源站是服务端，客户端是 Cloudflare 的边缘节点。&lt;/p&gt;
&lt;p&gt;打开这个选项后，边缘节点将向源站发送 Cloudflare 的证书。源站验证证书，确认来自 Cloudflare 之后，放行。否则，直接拒绝访问。&lt;/p&gt;
&lt;p&gt;我知道大家都用 Nginx，没错，它可以在源站充当身份认证的功能，只需两步——&lt;/p&gt;
&lt;p&gt;首先，下载 CF 的证书（官方文档里面给的地址）：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;wget https://developers.cloudflare.com/ssl/static/authenticated_origin_pull_ca.pem
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;然后，把它重命名为 crt 后缀：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;mv ./authenticated_origin_pull_ca.pem ./authenticated_origin_pull_ca.crt
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;然后，配置你的 Nginx，根据你的证书保存位置加上两行（此处以放在全局 http 块中为例子。你也可以放在 server 块，为单个域名启用）：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-nginx&quot;&gt;http {
    ...
&amp;nbsp; &amp;nbsp; ssl_client_certificate /etc/nginx/authenticated_origin_pull_ca.crt;
&amp;nbsp; &amp;nbsp; ssl_verify_client on;
&amp;nbsp; &amp;nbsp; ...
&amp;nbsp; &amp;nbsp; include /etc/nginx/conf.d/*.conf;

}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;现在重载 Nginx：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;sudo service nginx reload
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;好了，访问你的域名，应该仍然可以正常访问。再试一次，用刚才的 curl 命令绕过 CF 直接访问，你就会很欣喜地发现一个 400 错误，提示没有提供客户端证书：&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://img.samhou.top/1773151302799.webp&quot; alt=&quot;错误&quot;&gt;&lt;/p&gt;
&lt;p&gt;现在你的源站防护等级升高了一个档次。&lt;/p&gt;
&lt;p&gt;其实原理很简单：就是源站要求客户端提供证书，然后对证书进行了一次验证而已，和普通的 SSL 只是方向相反而已。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Cloudflare 内部威胁&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;由于使用了 Cloudflare 的证书，所以只能验证请求来自 CF 的边缘节点，但不能验证请求是你的账号所发出的。也就是说，如果别人通过某种方法让 CF 以你的 Host 回源你的服务器，就会绕过此限制。要解决这个问题，只能自定义上传证书，这不在本文的探讨范围内。&lt;/p&gt;
&lt;/blockquote&gt;
</content:encoded></item><item><title>水星冲浪日志 2 —— PT、换域名、约稿和建站机</title><link>https://blog.samhou.moe/aqua-surf-2/</link><guid isPermaLink="true">https://blog.samhou.moe/aqua-surf-2/</guid><description>《水星冲浪日志》第二期记录博主初次接触 PT 下载站、从 BT 下载经验中了解做种和站点规则的过程，也分享博客更换域名、看板娘形象设计与约稿，以及新购建站机器的经历。文章串联影音资源、博客运营和硬件折腾，呈现当时的个人计划、建站日常与后续打算。</description><pubDate>Thu, 05 Mar 2026 19:03:02 GMT</pubDate><content:encoded>&lt;p&gt;距离上一篇水星冲浪日志已经过去一个月多了。因此，第二篇日志新鲜出炉~在这一个月多一点的时间里，网上冲浪和技术又发生了许多可以写的内容，甚至让我有些难以抉择了。&lt;/p&gt;
&lt;p&gt;在开始之前，我们先来看看上一篇日志的评论区——收到了一条留言：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;关注一下你的装机，来自RSS订阅用户。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;嗯，这位读者可能要失望了，上一期组装好机器之后，作为做种机器了（见下方内容），&lt;strong&gt;目前暂时没有继续装机的打算&lt;/strong&gt;。下一次装机，预计是等到家里云硬盘出现问题的时候了——不过估计不会太久，等到硬盘和内存价格降下来之后，我希望能&lt;strong&gt;自己组一台 NAS&lt;/strong&gt;。毕竟，生命不息，折腾不止。&lt;/p&gt;
&lt;p&gt;留言读完，事不宜迟，让我们开始吧！&lt;/p&gt;
&lt;h2&gt;PT&lt;/h2&gt;
&lt;p&gt;作为一个追求极致的看番观众，我经常会需要下载老番和 BD 版本（至少也要 1080p 高清）的动漫资源。&lt;/p&gt;
&lt;p&gt;之前都是 bt 下载，然而发现大批吸血客户端吸血，此外还有一堆死种。然后在论坛里面知道了 PT 这个东西，正好有很多 vps 和两条联通宽带+大盘家里云，就开始玩 PT 了。&lt;/p&gt;
&lt;p&gt;PT 对你的分享率是有要求的，保种更是可以获得魔力值换各种各样的东西。&lt;/p&gt;
&lt;p&gt;于是趁着过年 PT 站开始注册，尝试入了几个 PT 站。&lt;/p&gt;
&lt;p&gt;摸索一段时间后，我发现 PT 的正确打法：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;先看规则和常见问题&lt;/li&gt;
&lt;li&gt;规则不允许盒子（vps 等国外大带宽公网设备）？那就用家里宽带下热门种子和免费种子&lt;/li&gt;
&lt;li&gt;先刷上传。刷完上传再保种，下你自己想要的保，这样满足下载和魔力要求&lt;/li&gt;
&lt;li&gt;可以认领，这样能获得很多魔力值&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;不过，毕竟我也是个 P 龄才一个月的新人，PT 站也都不允许公开宣传它们，所以此处就略过不多说啦（当然有大佬看到此文想发药，也可以联系我）。&lt;/p&gt;
&lt;p&gt;关于 PT 嘛，我个人是非常喜欢这种互利共赢的做法的——你把资源分发给别人，别人也给你，在 bt 的基础上，确保了所有人都在好好做贡献。显然，这是个比 peerbanhelper 反吸血更好的方法，毕竟所有数据都会被记录下来，你可以安心地做贡献，而不是害怕被吸血。&lt;/p&gt;
&lt;p&gt;再转向下一个话题之前，先来说说——&lt;/p&gt;
&lt;h3&gt;机器的归宿&lt;/h3&gt;
&lt;p&gt;上一期，我提到过自己装了一台机器。作为一台备用服务器，我把它设置在了异地——另一条宽带所在的地区。虽然盘非常小，但是够用了，只是用来保点 PT 种子，很不错。一个 PT 站要活下去，确保种子活着很重要，因此保种+认领是可以获得魔力值的，非常划算。&lt;/p&gt;
&lt;h2&gt;换域名&lt;/h2&gt;
&lt;p&gt;接下来就是博客的一件大事：&lt;/p&gt;
&lt;p&gt;top 这个后缀总感觉不太舒服，而且听论坛大佬说对搜索引擎收录有影响，所以我决定——换成 moe 后缀！&lt;/p&gt;
&lt;p&gt;moe 后缀其实早就注册了——小说站和一些项目用的就是 moe。现在只是需要把 top 迁移过来即可。&lt;/p&gt;
&lt;p&gt;首先创建 301 跳转，指示已经永久移动，这个简单，vercel 甚至可以一键自动完成这个操作。至于带域名的链接的话，可以批量VSCode 替换，非常方便。&lt;/p&gt;
&lt;p&gt;然后就是搜索引擎的迁移了。&lt;/p&gt;
&lt;p&gt;Search Console 简单，直接申请换域名，自动检查通过即可。&lt;/p&gt;
&lt;p&gt;Bing 没找到入口，于是给客服发了工单。客服说重定向了会自动建立索引，于是我就等了几天，还真是流量都回来了，看来这个过程确实很少需要人工介入。&lt;/p&gt;
&lt;p&gt;最后也是最难的一步，通知友链更改。当然由于自动 301 重定向，不改也没事，一个个通知会浪费自己和对方的时间，所以就有点摆烂了，只通知了友链下方评论区，以及“开往”、博友圈、虚拟备案这种可以自主更改的朋友和实体。&lt;/p&gt;
&lt;p&gt;如果有看到这篇文章的朋友，麻烦去更新一下友链信息哦，毕竟友链已经加了不少了。RSS 订阅也需要修改域名，只要把 top 改成 moe 即可。&lt;/p&gt;
&lt;p&gt;更改过程非常丝滑，而且由于只是要改那些需要搜索引擎收录的站点，所以评论系统和 umami 数据分析不需要修改。大大减少了工作量，好评。&lt;/p&gt;
&lt;h2&gt;约稿&lt;/h2&gt;
&lt;p&gt;接下来聊聊博客美术。&lt;/p&gt;
&lt;p&gt;记得上一篇，我们提到了&lt;a href=&quot;https://blog.samhou.moe/aqua-surf-1/#%E7%9C%8B%E6%9D%BF%E5%A8%98&quot;&gt;看板娘的形象&lt;/a&gt;。现在有点钱了，就找了一位老师约稿画了出来。作为一个理工男，想象力实在有限，我甚至害怕画师会因为我的设计太烂而拒绝。但是当我仔细构思过形象，然后把企划挂到平台上之后，很快就有老师来接稿了，于是就生成了这么个结果：&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://img.samhou.top/seimouhornia.webp&quot; alt=&quot;S 酱立绘（请尊重博主和画师的设计，勿复制/转载/喂给 AI）&quot;&gt;&lt;/p&gt;
&lt;p&gt;这个形象，以及修正后的设定，可在&lt;a href=&quot;https://blog.samhou.moe/character/&quot;&gt;设定集&lt;/a&gt;查看。&lt;/p&gt;
&lt;p&gt;看板娘的形象确定下来之后，接下来就有以下的设想：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;根据形象再约稿一个头像&lt;/li&gt;
&lt;li&gt;把看板娘应用到某些博客文章中去&lt;/li&gt;
&lt;li&gt;继续约稿完善博客的美术设计&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;嗯，这是个耗钱的工作。&lt;del&gt;所以等赚到钱了再说，摆烂！&lt;/del&gt;&lt;/p&gt;
&lt;h2&gt;建站机&lt;/h2&gt;
&lt;p&gt;最后，是上一篇 MJJ 的延续。&lt;/p&gt;
&lt;p&gt;是的。MJJ 上瘾了。我决定抛弃之前那台香港的 2c4g 杂牌 VPS（天天断网谁受的住啊），然后买一台正经的稳定建站机。&lt;/p&gt;
&lt;p&gt;于是找到了 netcup 这家便宜大碗的商家。经历了&lt;a href=&quot;https://samhou.de/notes/aj8dlxjie5wy00u9&quot;&gt;一点波折&lt;/a&gt;后，论坛上收了一个机器，4c8g 512G 高性能德国机器。&lt;/p&gt;
&lt;p&gt;然后，把服务迁移到了那里，现在整个博客的评论系统和 umami 数据分析就在这台机器上，大家可以体验一下套了 cf 之后的速度，应该还不错？&lt;/p&gt;
&lt;p&gt;这台机器我会长期续费了，MJJ 建站毕业机器，除非炸鸡，否则真的不买了。&lt;/p&gt;
&lt;h2&gt;结语&lt;/h2&gt;
&lt;p&gt;这就是第二期日志的全部内容了，因为是公开的杂谈栏目所以话题受限，只有这么多可以写的东西，请期待下一期~&lt;/p&gt;
</content:encoded></item><item><title>奶奶都能看懂的 C++ —— 函数指针、decltype、类型别名和尾置返回</title><link>https://blog.samhou.moe/cpp-function-pointer-type/</link><guid isPermaLink="true">https://blog.samhou.moe/cpp-function-pointer-type/</guid><description>本文深入讲解了C++中的函数指针、decltype、类型别名和尾置返回类型。通过通俗易懂的实例，详细拆解了函数指针的声明、赋值、调用及其在参数传递、函数返回等场景的应用，并探讨了如何利用现代C++特性简化复杂类型声明，使代码更清晰灵活。</description><pubDate>Wed, 04 Mar 2026 14:03:01 GMT</pubDate><content:encoded>&lt;p&gt;&lt;a href=&quot;https://blog.samhou.moe/cpp-overload-match/&quot;&gt;上一节&lt;/a&gt;我们讲了函数，这次来聊聊&lt;em&gt;函数指针&lt;/em&gt;。&lt;/p&gt;
&lt;p&gt;顾名思义，函数指针指的就是，&lt;strong&gt;指向函数的指针&lt;/strong&gt;。也就是，指针解引用之后是一个可调用的函数。&lt;/p&gt;
&lt;p&gt;先来看一段示例代码：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-cpp&quot;&gt;int addInt(int a, int b) {
    return a + b;
}
int main() {
    int (*pAddInt)(int, int);
    pAddInt = addInt;
    cout &amp;lt;&amp;lt; pAddInt(3, 4) &amp;lt;&amp;lt; endl; // 7
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;现在我们来仔细看看这段代码做了些什么。&lt;/p&gt;
&lt;h2&gt;声明函数指针&lt;/h2&gt;
&lt;p&gt;首先我们声明并定义了一个函数 &lt;code&gt;addInt&lt;/code&gt;，它接受两个 &lt;code&gt;int&lt;/code&gt; 类型的参数，然后返回它们的和。&lt;/p&gt;
&lt;p&gt;然后我们来看今天的重点，main 函数。&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-cpp&quot;&gt;int (*pAddInt)(int, int);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这就是我们所说的，&lt;strong&gt;函数指针的声明&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;我们来看看为什么它是函数指针：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;pAddInt&lt;/code&gt; 是这个变量的名称，我们从这里往外读&lt;/li&gt;
&lt;li&gt;首先是括号，里面有一个 &lt;code&gt;*&lt;/code&gt;，说明它是个指针&lt;/li&gt;
&lt;li&gt;右侧的括号中有两个 &lt;code&gt;int&lt;/code&gt; 类型，说明这是个参数列表&lt;/li&gt;
&lt;li&gt;左侧的 int 表示一个返回值类型&lt;/li&gt;
&lt;li&gt;因此，它是一个函数指针，可以指向一个函数，这个函数&lt;strong&gt;需要&lt;/strong&gt;接受 2 个 int 类型的参数，并返回一个 int&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;正如我们在介绍指针时所提到的，指针所指向的内容是有类型限定的，由指针的类型决定。函数指针也是如此，你看，上面的代码已经明确了允许指向的函数的要求了。&lt;/p&gt;
&lt;p&gt;其实，函数指针就是函数加个星号而已，声明和正常的指针没什么差异：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-cpp&quot;&gt;int addInt(int, int);
int (*pAddInt)(int, int);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;再提醒你一下，在 C++ 中，括号的优先级是比 &lt;code&gt;*&lt;/code&gt; 更高的，因此，声明函数指针的时候，必须给星号加上括号。比如，下面的声明是错误的，会声明一个&lt;strong&gt;返回值&lt;/strong&gt;为&lt;strong&gt;指向 &lt;code&gt;int&lt;/code&gt; 类型的指针&lt;/strong&gt;的&lt;strong&gt;函数&lt;/strong&gt;，而非&lt;em&gt;函数指针&lt;/em&gt;：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-cpp&quot;&gt;int *pAddInt(int, int);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;是不是看到了指向数组指针的影子？是的，由于括号问题，它们呈现出相似的视觉效果。&lt;/p&gt;
&lt;p&gt;好了，现在我们声明了一个函数指针，但问题是，这个指针现在没有指向任何东西，行为是未定义的。&lt;/p&gt;
&lt;h2&gt;赋值函数指针&lt;/h2&gt;
&lt;p&gt;我们来看下一行：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-cpp&quot;&gt;pAddInt = addInt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这一行代码将函数指针 &lt;code&gt;pAddInt&lt;/code&gt; 指向了一个实际的函数 &lt;code&gt;addInt&lt;/code&gt;。&lt;/p&gt;
&lt;p&gt;但是你或许注意到了一个问题——指针指向的是一个地址，但是为什么这里没有取地址呢？&lt;/p&gt;
&lt;p&gt;首先我们明确一点，函数确实有一个地址，指针也正是指向了这个内存地址。&lt;strong&gt;指针本身&lt;/strong&gt;存储了地址，是一个对象，但是&lt;strong&gt;函数&lt;/strong&gt;并不是对象（也不能赋值给变量）。&lt;/p&gt;
&lt;p&gt;然后我们再来看为什么没有取地址符号 &lt;code&gt;&amp;amp;&lt;/code&gt; 这个问题。&lt;/p&gt;
&lt;p&gt;其实你自己先试一下就会发现，加上这个符号也能通过编译，并正常运行：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-cpp&quot;&gt;pAddInt = &amp;amp;addInt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这种灵活来自于 C++ 的&lt;strong&gt;退化&lt;/strong&gt;性质，当一个表达式中存在函数名字的时候，这个函数会&lt;strong&gt;自动转换为函数指针&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;是不是很耳熟？没错，这和&lt;strong&gt;数组&lt;/strong&gt;是一个道理！还记得吗，我们在&lt;a href=&quot;https://blog.samhou.moe/cpp-array-with-pointer/&quot;&gt;数组与指针&lt;/a&gt;这一节中，曾经提到过，数组在表达式中也会自动转换为指针，指向的是第一个元素。&lt;/p&gt;
&lt;p&gt;函数也是类似，函数名字在表达式中自动转换为指向该函数的函数指针。因此，取地址符号是&lt;strong&gt;可以省略的&lt;/strong&gt;。&lt;/p&gt;
&lt;h2&gt;调用函数指针&lt;/h2&gt;
&lt;p&gt;既然我们已经有了一个函数指针，是时候来看看怎么用了。&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-cpp&quot;&gt;cout &amp;lt;&amp;lt; pAddInt(3, 4) &amp;lt;&amp;lt; endl;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;看到了吗？我们正在调用函数指针，正如调用普通的函数一样。&lt;/p&gt;
&lt;p&gt;我知道你要说什么。你肯定想这么干：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-cpp&quot;&gt;cout &amp;lt;&amp;lt; (*pAddInt)(3, 4) &amp;lt;&amp;lt; endl;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;你自己试试就会发现无论是否解引用，表现都是一致的。&lt;/p&gt;
&lt;p&gt;这是因为，编译器为你承担起了这一切——和自动退化相&lt;strong&gt;对称&lt;/strong&gt;，解引用也并不是必须的，我愿称之为一组&lt;strong&gt;对称法则&lt;/strong&gt;（提示，这不是官方说法，是我个人的总结）。&lt;/p&gt;
&lt;p&gt;实际上，函数指针&lt;strong&gt;可以作为返回值和参数&lt;/strong&gt;，就如通常的指针一样。但是，在继续讨论函数指针前，我们先来讲解几个好用的东西，然后再继续深入。&lt;/p&gt;
&lt;h2&gt;decltype&lt;/h2&gt;
&lt;p&gt;当我们涉及指针的时候，是不是发现声明语句越来越复杂了呢？一个变量的类型可能变得相当复杂：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-cpp&quot;&gt;int* addInt(int *a, int *b) {
    *a = *a + *b;
    return a;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;要创建指向上面函数的指针，我们必须使用这种类型声明语句：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-cpp&quot;&gt;int* (*pAddInt)(int*, int*);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;有没有什么简单的方法呢？当然，让我们隆重介绍 &lt;code&gt;decltype&lt;/code&gt;！&lt;/p&gt;
&lt;p&gt;看看这个：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-cpp&quot;&gt;decltype(addInt) *pAddInt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;decltype&lt;/code&gt; 可以把一个&lt;strong&gt;名字&lt;/strong&gt;对应的类型，直接&lt;strong&gt;偷过来&lt;/strong&gt;！我们这里偷来了 addInt 这个函数的类型（包括参数列表类型和返回值）。通常情况下，这样会声明一个新的函数，但由于我们加上了一个 &lt;code&gt;*&lt;/code&gt;，所以我们现在声明的是函数指针。&lt;/p&gt;
&lt;p&gt;一个非常重要的事情是，decltype &lt;strong&gt;不发生退化&lt;/strong&gt;，所以记得带上 &lt;code&gt;*&lt;/code&gt;。decltype 只是负责推算里面表达式的类型而已，不会帮你转换。&lt;/p&gt;
&lt;p&gt;因此，使用 decltype 可以极大简化代码编写流程，让我们免去书写指针的类型的麻烦。&lt;/p&gt;
&lt;p&gt;注意这个当然不局限于函数指针，而是对于任何指针都是有效的！&lt;/p&gt;
&lt;p&gt;比如，指向数组的指针：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-cpp&quot;&gt;int a[10];
decltype(a) *p;
int (*p2)[10];
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;第二、三行声明的指针，类型都是一样的，都是指向含有 10 个 int 的数组的指针哦。&lt;/p&gt;
&lt;h2&gt;类型别名&lt;/h2&gt;
&lt;p&gt;明白了 decltype 能够偷来类型，我们再来讲一个能够把类型起个别名的东西，&lt;em&gt;类型别名&lt;/em&gt;。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;类型别名&lt;/strong&gt;，从名字上来看，就是用另外一个名字，替代原来的类型名字。&lt;/p&gt;
&lt;h3&gt;typedef&lt;/h3&gt;
&lt;p&gt;传统的类型别名使用方法是 &lt;code&gt;typedef&lt;/code&gt;，比如：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-cpp&quot;&gt;typedef double d;
d num = 3.14;
cout &amp;lt;&amp;lt; num &amp;lt;&amp;lt; endl;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;我们给 double 起了个别名叫做 d。&lt;/p&gt;
&lt;p&gt;但这个太简单了，当类型变得非常复杂的时候，别名才会发挥出作用：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-cpp&quot;&gt;typedef double** d; // 指向指针的指针类型
typedef int arrType[10]; // 包含10个int的数组类型
typedef int (*arrType)[10]; // 一个指针类型，指向的是包含10个int的数组类型
typedef int *arrType[10]; // 包含10个指向int的指针的数组类型
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;你现在可能非常疑惑，为什么第一条和后面三条的语法&lt;strong&gt;看起来完全不一样&lt;/strong&gt;（你看看，第一句的语法好像是 &lt;code&gt;typedef 类型 新的名字&lt;/code&gt;，但后面的语法完全不是这样）？换句话说，typedef 到底是如何工作的？&lt;/p&gt;
&lt;p&gt;别被迷惑了，让我们先把 typedef 本身移除掉——&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-cpp&quot;&gt;double** d; // 指向指针的指针类型
int arrType[10]; // 包含10个int的数组类型
int (*arrType)[10]; // 一个指针类型，指向的是包含10个int的数组类型
int *arrType[10]; // 包含10个指向int的指针的数组类型
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;现在你可以看出些端倪了：给类型取别名，和&lt;strong&gt;创建变量&lt;/strong&gt;的本质没什么区别。&lt;/p&gt;
&lt;p&gt;奶奶都知道可以这么创建变量：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-cpp&quot;&gt;double** dp;
int arr[10];
int (*arr)[10];
int *arr[10];
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;嗯？是不是发现了什么呢？语法&lt;strong&gt;完全一致&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;也就是说，typedef 和创建变量的区别只有一个，就是前者&lt;strong&gt;创建的名字是一个可以直接使用的类型&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;比如：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-cpp&quot;&gt;d myd;
arrType myArr; // 直接当作类型使用！
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;好了，既然你已经知道如何创建类型别名，那么我们回到函数指针。同理，你可以这么创建函数指针类型：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-cpp&quot;&gt;typedef int (*funcPType)(int*, int*);
funcPType p1; // 这是一个函数指针
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这样可以节省大量时间。&lt;/p&gt;
&lt;h3&gt;using&lt;/h3&gt;
&lt;p&gt;在新版的 C++ 中，你可以用另一种方式创建类型别名：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-cpp&quot;&gt;using d = double;
d d1 = 3.14;
cout &amp;lt;&amp;lt; d1 &amp;lt;&amp;lt; endl; // 3.14
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这个 using 会把等号后面的类型，起别名，别名名字为等号前的内容。我们可以把上面的 typedef 全部转换为 using：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-cpp&quot;&gt;using d = double** ; // 指向指针的指针类型
using arrType = int[10]; // 包含10个int的数组类型
using arrType = int (*)[10]; // 一个指针类型，指向的是包含10个int的数组类型
using arrType = int*[10]; // 包含10个指向int的指针的数组类型
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;很简单吧？只是把名字前置了，相比 typedef，这种方式看起来更加方便。&lt;/p&gt;
&lt;p&gt;当然，也别忘了我们的主角函数指针：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-cpp&quot;&gt;using funcPType = int (*)(int*, int*);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;更强大的是，你可以把类型别名和 decltype 组合使用，避免写出复杂的类型：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-cpp&quot;&gt;using funcType = decltype(func); // 现在，你给函数起了别名
funcType funcP = *funcType; // 根据函数别名，创建对应的函数指针
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;传递函数指针&lt;/h2&gt;
&lt;p&gt;既然函数指针本身，是个指针，那么它就变成了一个对象，自然可以传递，就如其它指针一样。&lt;/p&gt;
&lt;h3&gt;作为参数传递&lt;/h3&gt;
&lt;p&gt;那这有什么用呢？其实，这样可以让同一个函数根据传入的函数指针，执行不同的操作。&lt;/p&gt;
&lt;p&gt;比如，下面的代码，将运算的函数指针传入，然后调用函数执行自定义操作：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-cpp&quot;&gt;int sum(int a, int b) {
    return a + b;
}
int diff(int a, int b) {
    return abs(a - b);
}
using funcPType = decltype(sum)*;
int mathOperation(int a, int b, funcPType funcP) {
    return funcP(a, b);
}
int main() {
    int a = 3, b = 4;
    cout &amp;lt;&amp;lt; mathOperation(a, b, sum) &amp;lt;&amp;lt; endl; // 7
    cout &amp;lt;&amp;lt; mathOperation(a, b, diff) &amp;lt;&amp;lt; endl; // 1
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;花点时间好好理解一下上面的代码。&lt;/p&gt;
&lt;p&gt;首先，写了两个执行算术操作的函数。&lt;/p&gt;
&lt;p&gt;然后，我们创建了一个叫做 funcPType 的类型别名，它代表的类型是一个函数指针，指向接受 2 个 int 参数的返回 int 的函数。&lt;/p&gt;
&lt;p&gt;之后，写了一个算术操作的函数，接受函数指针类型，在内部调用这个函数指针，执行相对应的操作。&lt;/p&gt;
&lt;p&gt;最后写了 main 函数，两次传入不同的函数名字。函数名自动退化为函数指针，作为参数传入。你可以看到，我们的 a b 都没有变化，但是结果却不同。这正是因为传入了不同的函数指针导致的。&lt;/p&gt;
&lt;p&gt;也就是说，&lt;strong&gt;函数指针可以把不同操作打包，交给其它部分操作&lt;/strong&gt;。也就是，&lt;strong&gt;把行为作为数据传递&lt;/strong&gt;。函数指针只记下了传入什么、返回什么，不关心实际执行了什么操作——也正因为如此，我们可以在需要函数指针的地方传入不同的函数，这极大地提升了我们程序的灵活性。&lt;/p&gt;
&lt;p&gt;另外我要提醒你一点，&lt;strong&gt;不要给函数名字后面加上括号&lt;/strong&gt;。这样会变成直接调用函数，传递的就是返回值了，会发生类型不匹配直接报错。我们要传递的，是会自动退化成函数指针的&lt;strong&gt;函数名字&lt;/strong&gt;。&lt;/p&gt;
&lt;h3&gt;作为返回值&lt;/h3&gt;
&lt;p&gt;不止参数。函数指针也可以作为&lt;strong&gt;返回值&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;来看看下面的示例代码（略去和上述相同的 sum 和 diff 函数）：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-cpp&quot;&gt;using funcPType = decltype(sum)*;
funcPType chooseOperation(char c) {
    if(c == &apos;+&apos;) return sum;
    if(c == &apos;-&apos;) return diff;
    return nullptr;
}
int main() {
    char c1 = &apos;+&apos;;
    char c2 = &apos;-&apos;;
    funcPType choice = chooseOperation(c1);
    cout &amp;lt;&amp;lt; choice(1, 3) &amp;lt;&amp;lt; endl; // 4
    choice = chooseOperation(c2);
    cout &amp;lt;&amp;lt; choice(1, 3) &amp;lt;&amp;lt; endl; // 2
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;仔细看看，我们的函数根据传入的参数不同，返回了不同的函数指针。返回的指针可以作为函数调用——在两条输出语句那里，我们用的是同一个函数指针名称，但是它们指向的是&lt;strong&gt;不同的函数&lt;/strong&gt;，也就导致了不同的执行结果。&lt;/p&gt;
&lt;p&gt;这是一个非常现实的例子，同样也是把&lt;strong&gt;行为作为数据传递&lt;/strong&gt;的体现。掌握了这种写法，你就可以大大提升你的程序的灵活度。&lt;/p&gt;
&lt;p&gt;此外，我们也可以不用类型别名，但是这会出现一些非常复杂的东西，我们在下面的尾置返回类型中，进行进一步的探讨。&lt;/p&gt;
&lt;h2&gt;尾置返回类型&lt;/h2&gt;
&lt;p&gt;关于函数指针我们说的够多了，现在我们再来补充一点 C++ 新版本的知识，&lt;strong&gt;尾置返回类型&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;顾名思义，尾置返回类型允许我们把函数返回值的类型放在末尾。来看看这个：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-cpp&quot;&gt;auto chooseOperation(char c) -&amp;gt; funcPType {
    if(c == &apos;+&apos;) return sum;
    if(c == &apos;-&apos;) return diff;
    return nullptr;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这是上面的代码的另一种写法。原本用于声明函数返回类型的首位被 auto 代替了，真正的返回类型写在最后。&lt;/p&gt;
&lt;p&gt;你可能在想，这不是多此一举吗？其实，只有当你不想用类型别名，但是却想返回函数指针（或任何复杂的类型，比如指向数组的指针）的时候，你会发现这大大被简化了：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-cpp&quot;&gt;int (*chooseOperation(char c))(int, int) // 一般情况
auto chooseOperation(char c) -&amp;gt; int(*)(int, int) // 尾置返回类型，这下看懂了
{
    if(c == &apos;+&apos;) return sum;
    if(c == &apos;-&apos;) return diff;
    return nullptr;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;也补充一下，如果不用尾置返回类型，第一行&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-cpp&quot;&gt;int (*chooseOperation(char c))(int, int)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;的含义是：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;从内向外阅读。首先 &lt;code&gt;chooseOperation(char c)&lt;/code&gt; 说明此处是个函数。&lt;/li&gt;
&lt;li&gt;其次 &lt;code&gt;(*...)&lt;/code&gt; 表示它的返回值是个指针。&lt;/li&gt;
&lt;li&gt;再次，&lt;code&gt;int(...)(int,int)&lt;/code&gt; 表示这个指针指向的是个函数，函数接受 2 个 int，返回一个 int。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;如果你有些混乱，不妨翻到文章开头，看看函数指针是如何声明的，对比看看：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-cpp&quot;&gt;int (*pAddInt)(int, int);
int (*chooseOperation(char c))(int, int)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;现在是不是发现了点规律？只是把变量名字换成函数名字+参数列表而已。&lt;/p&gt;
&lt;p&gt;但是，我完全不建议你这么写。请一定要使用尾置返回类型、类型别名和 decltype，让你的代码更加可读。&lt;/p&gt;
&lt;p&gt;关于函数指针和简化代码的方法，我们已经涉及了相当深入的内容了。下一节，我们将离开函数这一篇章，进入新的一部分——&lt;strong&gt;类&lt;/strong&gt;。&lt;/p&gt;
</content:encoded></item></channel></rss>