Logo

site iconEason Yang | 杨宇晨

长期从事后端研发和架构设计方面的工作,对技术和产品有一些自己的感悟和沉淀,这也是本博客所包含的主要内容。 
请复制 RSS 到你的阅读器,或快速订阅到 :

Inoreader Feedly Follow Feedbin Local Reader

Eason Yang | 杨宇晨 RSS 预览

2022年终总结

2023-01-02 00:47:14

这一年我经历了很多,看到听到的更多。工作生活都没什么大的变化,但心情总是很差。21 年没有做合格的年终总结,23 年不一定会有总结,但 2022 这魔幻的一年不能没有一份详实的年度回顾记录。

工作还顺利吗

《晚点 LatePost》在「中国十二大互联网公司年终盘点」一文中有这样一段话:

中国的互联网行业集体进入了一个新阶段 —— 不再寄望于无穷尽的增长,而是确保自己能在停止增长之后,通过提高管理和经营效率取得更高的利润。

这简直是对我 2022 工作体验的完美概括。过于关注局部的数据指标、外行领导内行盛行、向上管理几乎成了政治正确,即使没被裁员和降薪,我也要被迫体验到这些中高层老板从冲量到盈利过渡期对工作方向「艰难探索」所带来的副作用。不巧的是这些都是我所厌恶的东西,以至于时不时地我就会怀疑自己的工作到底能带来什么价值,全年可以说是毫无工作乐趣。而这令人沮丧的工作氛围也耗尽了我开拓副业的热情,甚至几度让我没了发言交流的欲望,如果你发现我几天都没在 Twitter 上瞎侃,那大概率我又因为工作的原因而怀疑人生了。

在工作内容方面,今年我的角色承担了更多的管理责任,一如《卓有成效的管理者》一书所言:

管理者的时间往往只属于别人,不属于自己。

无穷无尽的会议、代码及方案审核和各种花式汇报文档的编写,再加上前面所说的「艰难探索」带来的各种自上而下的奇葩指示中有不少还需要我来推动或落地,让我很少有时间能静下心来写代码。而又由于我对本组的项目实在过于熟悉,在架构和技术方案的设计上又很难遇到什么新的挑战,所以也谈不上什么技术成长。总体而言这一年我的时间和人基本是被事情推着走,所谓的管理大多也只是做了些顺水推舟的事情,好在有一些技术和业务层面的决策还算有少许价值,不然真可谓是在工作层面碌碌无为了一整年。

在主业之外,今年的内容和副业产出少得可怜。首先没能达成 21 年所设想的本年参与开源工作的目标,连小玩具都没动手做。其次博客也只写了三篇文章(把Notion变为个人网站再见Hexo——从Hexo迁移至Hugo旁路由的原理与配置一文通),虽然从搜索引擎排名情况来看文章质量应该还算可以,但三篇这个数量着实是太少了,而且基本都是我所熟悉的舒适区,可以概括为没有持续地用心写作。

最过分的是,Newsletter 被我鸽了一整年没更,鸽到平台 Revue 都倒闭了,实在是对不住 21 年以来订阅的这几十位读者了。不过下半年运营了下 Telegram Channel 并获得了一些关注,感谢各位的厚爱,这个应该不会再鸽了(吧)。Twitter 倒是说了不少话,涨了一点关注,但好像也没产出多少有价值的内容,惭愧惭愧。

生活还开心吗

1、心情很差

从 21 年年末的西安封城开始,我的心情就一直很差。其中原因,一方面是我对同胞受苦的不忍,另一方面则是对方舱隔离和宠物无害化处理的恐惧。这一切愤恨、恐惧和绝望都在 22 年的年末戛然而止,我也不出意外地在放开后感染上了新冠病毒发了两天烧。

回看这一荒诞的一年,上海封城的乱象让我对其管理水平之低下有了新的认识,戾气日长的日常生活也让我看到了底层互害的可怕。我练出了进门前随手打开健康宝的扫码反射,也习惯了调低期望居家工作的生活。这些记忆既是历史的现身说法,也是对我认知的宝贵教训。我在自己的 Obsidian 知识库中整理了由新闻、图片和感想组成的 2022 清零防疫笔记图谱,以后每年都会翻出来看一看,提醒自己不要忘记这沉痛的一年中发生的骇人的事情。

2、书影音的荒漠之年

由于电影院几乎歇菜了一整年,再加上我心情不好和工作内卷,这一年看的影视剧大部分都是以前看过内容的二刷、三刷,其中又以老港片为主。这些内容有情怀加持,剧情和桥段我又烂熟于心,看起来没有心理负担和理解成本,可以说是纯纯地摆烂了。

不过在状态好的时候我也延迟了几周到几月不等地看了一些热度比较高的新剧和新电影,这里列举下红黑榜。

(1)年度佳片

Netflix Better Call Saul Poster

  1. Bullet Train:很多年没看到这样的暴力美学爽片了,一度让我想起了《杀死比尔》,是难得的一部我不想聊剧情漏洞,只想看电影本身的影片。
  2. Dahmer – Monster: The Jeffrey Dahmer Story:Netflix 版本的达莫案件改编,前半段重口得恰到好处,后半段又令人唏嘘。
  3. Archive 81:这两年 Netflix 难得一见的正常水平科幻惊悚新剧,剧情紧凑节奏鲜明。由于是年初的剧,让我一度对 Netflix 的制作水平又燃起了希望。
  4. Stranger Things S4:个人对这一季不是很满意,苏联那一段完全没必要。不过本季填了不少坑,也有很多感人片段,还是值得一看的。
  5. Better Call Saul S6:大部分时间仍然秉持了神剧标准。但作为一个无脑厌恶 Chuck 的观众,我觉得 Jimmy 值得一个更温暖的结局。

(2)年度烂片

  1. Choose or Die:好标题、好题材,可能想拍成黑镜,但最终拍出了个剧情稀烂的四不像。
  2. Resident Evil(Netflix 版本):期望越大,失望越大!生化危机系列最差改编,没有之一。
  3. 四海:22 年好像只在电影院看了这一部电影,如果再给我一次机会,我宁愿不去看。
  4. Alice in Borderland S2:「嘴遁+说教+回忆」,但还没博人燃。
  5. Keep Breathing:看完了我简直气得呼吸困难。

不难看出,这又是我被 Netflix 「坑害」的一年,除了续集尚有些能保持水准,新剧有大量的平庸之作甚至烂片,电影就更差了。23 年真的要考虑换一家来订阅了。

(3)不读书吗

今年读的书仍然是工具导向为主的技术或管理类书籍,而且数量不多,大多也是跳着读的,一般只有某几章是精读的,所以很难做出推荐和总结。不过在人文类书籍方面,22 年读的这本《午夜将至:核战边缘的肯尼迪、赫鲁晓夫与卡斯特罗》很有意思,联想今年国内的种种怪状和 21 年的一部我超爱的剧 Inside Job ,着实是让我重新审视了我对所谓高层的认知。

(4)还听播客吗

今年播客听得也少了很多,主力播客工具 Pocket Casts 的统计是 3days 9hours ,不过今年用有音转文功能的 Snipd 听了不少英文播客,所以这个统计有些失真。《忽左忽右》应该是我本年最爱的播客节目了,几乎每期必听,还会做很多的延伸阅读,新的一年要多多购买 JustPod 的周边产品和付费节目来做支持才行。

(5)不听音乐吗

Masuerade Album. From Apple Music

今年周杰伦听得少了很多,变得超级爱听伍佰的歌。以至于《再度重相逢》和《Last Dance》两首经典金曲在我的 Apple Music Replay 2022 中高居榜首,可见我的怀旧情绪有多高涨。我的年度歌曲是下面这两首:

  1. Running Up That Hill (A Deal With God):上半年时这首老歌着实是再次爆火了一把,相比起原版我更喜欢怪奇物语版本,搭配剧一起食用味道更佳。
  2. Masquerade:尚未大火的音乐人 Stellar 今年的作品,编曲、歌词都很棒,封面选择有股浓浓的 AI 味也很合我的胃口,他的其它单曲也很好听,推荐收藏。

(6)不玩游戏吗

Red Dead Redemption 2. From Steam

年初时终于把《荒野大镖客2》的剧情通关了,好一部快意恩仇的西部史。是的你没看错,这样的大作我总是延迟几年才去玩,计划明年玩完《战神》。

Faze Clan IEM Champion. From Faze Clan Twitter

今年机缘巧合间熬夜看了 CS:GO IEM 科隆总决赛,见证了 Faze Clan 的夺冠。后面又断断续续看了 Major 、Blast 等比赛,不得不说 CS:GO 比赛的观赏性是真的高。可惜入门和观赛门槛都挺高,所以国内外的人气都比较一般,这一点从与其他游戏的奖金池规模对比上也可见一斑。

题外话顺便聊两句足球。今年除了世界杯就没怎么看球,西甲和中超似乎都没看满整场的时候,欧冠由于巴萨早早出局也就看了个集锦。主队巴萨今年已经衰落成了标准的 1.5 流强队,并正在向二流继续滑坡,这几个赛季不因为杠杆和债务而破产或被私有化我就已经烧高香了,好在大连人今年终于不再在降级区苦苦挣扎了。足球在我生活中的比重越来越小,以前不理解我爸年轻时那么爱看球怎么中年之后就不太关注了,现在似乎逐渐领悟了一些。

3、投资理财赔钱了吗

今年的投资策略非常保守,股票、基金几乎完全不碰,更别提什么期货期权了。对这种经济下行状态下的认知不自信让我在今年变成了一个极端的风险厌恶者。

这样的好处是今年不仅没亏,而且还靠着美元升值和黄金价格波动取得了一些盈余。坏处是现在不抄底可能几年后肠子会悔青。不过我想这个时间点还是手握现金流更稳妥些,留得青山在不怕没柴烧,以后的悔就让以后的我品尝吧,不跟风不梭哈就不会破产。

4、也有一些好事情

敲敲的猫片

我家小猫在 21 年做了个大手术,令人欣慰的是 22 年她活蹦乱跳瞎作了一整年什么事也没有,希望明年能继续作继续闹,铲屎官承受得起!

今年最大的成就可能就是把烟给戒掉了。我倒不是一个数年烟龄的老烟民,总计也就抽了两年左右,但后期随着工作和清零带来的沉闷心情,我的吸烟量达到了惊人的两包一天。Q4 在给自己重新灌输了一遍尼古丁不解决问题只会带来问题的理念后(其实就是《这本书能让你戒烟》一书的核心主旨),我趁着十一假期撑过了戒断反应的最高峰并彻底把烟戒掉了。

我的戒断反应主要集中在牙龈微痛和类似于醉氧的头晕,第一周还是挺难熬的,好在三周后就完全没有任何生理层面的不适和吸烟欲望了。不过,即使是在三个月后的今天,我也仍偶尔会产生想吸烟的情绪苗头。虽然比起戒烟时期,这些缺乏生理机制支撑的小火苗很容易就可以被扑灭,但我不知道这样的情况还会持续多久。所以尼古丁这类东西能不碰就不碰,不要迷信你所谓的意志力。

Argentina the Champion. From FIFA Twitter

从下半年乌克兰变为优势方,到阿根廷在卡塔尔世界杯夺冠梅老板圆梦,再到清零政策的戛然而止,我的世界和生活似乎又有了些光明。我又想提起那句被说烂了的电影台词:

生活就像巧克力,你永远不知道下一颗是什么味道。

买了什么好东西

今年几乎所有的大件都是在春节期间买的,原因是那时还没对宏观经济情况的恶化作过多的思考。春节后就开始避免大额开销,非必要不购物了。所以比起 21 年,今年的购物清单算是寒酸了不少。

1、电子产品

(1)DJI Pocket 2

推荐指数:★★★★☆

买它是因为去环球影城前想到了在迪士尼乐园用手机录像的糟糕体验,就想购入一个更轻量的录像装备。在调研了 GoPro 、Insta360 和 DJI 的其它几款产品后,我最后选了这款没什么运动细胞、中规中矩的 Pocket 2 。除了价格不便宜、配件更贵外,整体能力很均衡,当然光线不佳时还是要用 iPhone 来补上。不过在去了一趟环球影城后因为封控的原因我就没再怎么出门游玩,希望明年可以让它发挥更大的价值吧。

(2)Xbox Series S

推荐指数:★★★★☆

本想买了它再搭配 XGP 订阅能让我重新拾起玩游戏的心情,不曾想没玩几个 XGP 里的游戏,玩的时间最长的 FIFA 22 还是单独购买的,可谓是陪了夫人又折兵。不过对于喜欢宅在家玩主机游戏而家里又只有 1080P 电视的用户来说,Series S 绝对是最高性价比的选择,配合 XGP 整体的费用要比 NS 生态低不少。

(3)米家胶囊咖啡机

推荐指数:★★★★★

没想到 2022 年了,小米贴牌在这类几百块价位的产品中仍然是性价比之选。简单、好看、小巧,不需要复杂的清洗。既然都喝胶囊咖啡了我想也就没必要追求各种咖啡机的高级功能了。

(4)文石 Leaf 阅读器

推荐指数:★★★★★

文石产品现在的文字渲染效果已经是一线水准了,今年用它搭配微信读书粗读了不少书。而作为曾经的 Kindle 用户,Android 系统之方便让我不禁怀疑此前我的 Kindle 吃灰是不是就是源自它封闭的生态而非我的问题(显然不是)。

(5)Beats Fit Pro

推荐指数:★★★☆☆

可能是本年度最让我又爱又恨的一次购物选择。年初的时候,我手中那款做工极差的初代 AirPods Pro 又出现了各种奇怪的小毛病,忍无可忍的我在送修的同时,决定不再等待下半年大概率会出现的新款 AirPods Pro ,转投当时刚发布不久的 Beats Fit Pro 。说爱,是因为这款耳机完全没有 AirPods Pro 那些恼人的如单耳突然没声音、充不进电等问题,立体空间声等 AirPods Pro 有的功能它也都有,降噪能力我觉得也不输当时的同代产品。恨的是其通话能力很拉胯,那羸弱的麦克需要我保持静止才能较好地收音,再加上 Beats 产品发售即价格跳水的传统艺能,我很快就觉得它不香了。2023 年的各位如果想买新的入耳式降噪耳机,那还是考虑 AirPods Pro 2 吧。

(6)飞利浦电动牙刷 HX6806

推荐指数:★★★☆☆

买来替代我满身水渍的欧乐B牙刷,因为是便宜版本所以整体表现中规中矩吧。声音比欧乐B的安静不少,电量要更持久些,也没出现当年欧乐B把我牙龈刷出血的情况,无功无过的一次购物选择。分别使用过这两家的中低端产品后,我对于电动牙刷的购买建议是买个便宜的、电量持久的就行了,刷牙这件事情更重要的还是每天都刷且刷够时间。

2、软件与订阅

在统计了我每年的软件和订阅开销上万后,去年的 flomo/秘塔写作猫/Bear/DigitalOcean Spaces 等产品的订阅都被我停掉了,替换为了免费产品或与其它平台型产品合并。这是一个开源节流的痛苦过程,但也是一个重新审视我真实软件需求的好机会。最后截至年末幸存的软件和服务清单如下。

服务 推荐指数 总结
Apple Music 美区 ★★★★☆ 继续订阅的一年,只要 Spotify 不支持 Homepod 一天,我大概率就会一直订阅
Netflix ★★☆☆☆ 今年继续涨价+烂片更多,由于不怎么在电脑和 iPad 上看它了所以降级至了 1080P 版本,也算是省了点钱
Pocket Casts Plus ★★★☆☆ 继续订阅的一年,也继续没什么大的迭代。不过这家伙 23 年就要从 $9.99 涨价到 $14.99 了,加价不加量着实有些夸张,新的一年大概率是要被换掉了
iCloud 2TB ★★★★★ 今年拍猫的 4K 视频过多,以至于 200GB 版本已经不够用了,不得已只能升级到了 2TB 版本。好在 iCloud 的域名邮箱现在也勉强能用了,省了一笔 Google Workspace 的钱,正负得零,还算可以接受
Setapp ★★★☆☆ 这一年 Setapp 软件列表的扩充还是挺给力的,所以评价由去年的两星变为今年的三星。不过之前的优惠折扣结束了只能原价购买了,钱包有一点受伤
Grammarly ★★★☆☆ 因为几次偶然的正式英文使用场景头脑一热就订阅了一年。虽然功能还是挺好用的,但现在再看实在太冲动了,$12/month 的价格完全被我的低频使用浪费了,我想大部分在国内做代码开发工作的人可能都不需要订阅这款产品
TickTick ★★★★☆ 订阅了滴答清单的国际版用以替换 Microsoft To Do ,收集箱和四象限等接地气的功能大大缓解了我因堆积任务而产生的焦虑感,年末时我还用它替代了一众稍后读工具和随手记类型的笔记工具,这钱花得挺值
Craft ★★★☆☆ 受不了 Notion 的渲染和加载速度,再加上 Pro 版本支持使用 iCloud 存储才决定订阅这款 native 实现的笔记工具。然而重度使用后发现其对 CJK 的支持存在一些隐蔽的 Bug ,到了 Q4 加入 Setapp 的操作着实让我感觉交了两份钱。可能是本年度仅次于 Grammarly 的不值得订阅了
腾讯云香港轻量 VPS ★★★★★ 良心云今年风评不太良心,不过倒尚未影响我的使用,今年继续订阅
搬瓦工 SoftBank VPS ★★★☆☆ 今年莫名其妙被墙了一次,明年可能会改用中继方案而不再订阅了
Jetbrains 全家桶 ★★★☆☆ 去年忘记写这个收费大户了,今年依然继续订阅,不过新版本的卡顿问题比之前更严重了,评分一般

有什么思考

  1. 高增长的 C 端产品千篇一律,而体验好的 B 端产品却是各有千秋。
  2. 要注重发挥长处的优势,而不是急于补全短板。于人于己都是如此。
  3. 绝对化的结果导向大概率会导致南辕北辙的执行。
  4. 润为上计,躺为中计,卷为下下策。躺最难,卷最累,润没那么难。
  5. 外行领导内行是常态,草台班子你我他。

明年怎么看

复制粘贴下上一年的个人 OKR ,把标题改成 23 年。内容变化不大,明年继续加油。

把Notion变为个人网站

2022-08-15 12:43:11

「The all-in-one workspace」是 Notion 的自称,它肯定不是最优秀的笔记软件,但一定是当今最强大的内容生产工具之一。除了可玩性很高的数据库功能,Notion 还支持将页面对外公开,即可以通过简单地启用分享来获取到可在互联网上直接访问的链接。不过免费版不支持为被分享页面配置 SEO ,而且即使是付费版也尚未支持自定义域名。

如果想建立一个可高度自定义的个人网站,或希望把 Notion 的页面作为已有网站的附加内容,那就还是要基于 Notion 建立一个拥有独立域名、支持自定义设置的个人网站。本文就来聊聊基于 Notion 的个人网站有几样建法。

建站方案

基于SaaS产品建站

Notion 如今已经形成了生态,既然官方没提供完整的自定义个人网站方式,那各种 SaaS 产品就会补足市场的需求。当前已经出现了很多这类产品,通常是免费版提供另一个二级域名和自定义主题功能,付费订阅版才有自定义域名、支持 SEO 等高级功能。

这些产品的界面都很简洁,操作也简单易懂。以 Popsy 为例,选择新建站点:

创建 Notion 站点

最后填入站点名称和 Notion 页面的分享链接即可:

完成初始化配置

站点新建完成后,我们可以在左侧的导航栏里完成自定义设置,最后点击 Publish 完成发布即可。不过 Popsy 没有免费套餐,需要先在 Stripe 完成支付配置才可以试用 7 天。

SaaS 类产品的操作确实非常简单,功能上也可以满足用户大部分的自定义需求,很适合非技术人员或不愿意折腾的用户。唯一的缺点只有一个字,那就是「贵」,订阅价格通常在每月 $8~12 不等。贴几张不同产品价格功能表的截图,大家感受下:

Popsy 价格功能表

Simple.ink 价格功能表

Potion 价格功能表

基于FaaS产品建站

除了付费产品,在流量不大时我们还可以借助 FaaS 产品的免费额度来实现建站。目前比较常见的思路分为 Cloudflare Workers 和 Vercel 两类,希望快速可用建议选 Workers ,打算通过开发来实现自定义的则可以使用 Vercel 方案。

基于Cloudflare Workers建站

这个方案的原理是通过 Workers 的转发和响应重写功能对 Notion 进行代理。由于这套逻辑大同小异,所以有网友开发了 Workers 代码的生成器。可视化界面的简单配置即可生成对应的代码,再拷贝到 Cloudflare Workers 中执行即可。

以最为著名的 Fruition 为例,我们首先完成 Step0 和 Step1 中的域名注册、Cloudflare 注册和 DNS 配置,随后在 Step2 中填写好期望的域名和 Notion 页面的分享链接,最后把生成的代码拷贝到新建的 Cloudflare Workers 函数中,再将该域名的路径设置为 Worker 的路由即可。官网的教程非常详细,按指导一步步执行即可。在 Step3 中我们可以做很多自定义工作,而由于该 Worker 的实现主要依赖重写响应,因此我们也可以对生成的代码进行深度修改以满足各种自定义需求。

不过我们从 Fruition 官网教程的截图也能看到,所使用的 Cloudflare 截图已经是改版前的页面了。无独有偶,该教程的细节也存在一些需要与时俱进的调整。在 Step1 的第 4 小节,教程中要求为目标域名添加 A 记录并将 IP 指向 1.1.1.1

If you don’t have any A records imported, add one with your root domain as the Name and 1.1.1.1 as the Content. Otherwise, click Continue on the DNS Record page.

但实际上如果你真的这样配置,那在当前的 Cloudflare 机制下会出现网页无法打开的问题。原因在于现在 Cloudflare 当前已经禁止了这种 DNS 配置方式,对应的 Workers 需要使用保留 IP 地址(Reserved address)比如 192.0.2.0 ,CIDR 为 192.0.2.0/24 ,这其后的技术细节以后可以展开讲讲。

其他基于 Cloudflare Workers 的方案和 Fruition 类似,区别只在于 Worker 中代码实现思路的不同。Cloudflare 作为良心企业,免费版 Worker 每天有 100k 请求的额度,对于绝大部分的基于 Notion 的个人网站来说是完全足够的。即使真的超过了额度,多加 $5 就可以多获得 100 倍的可用请求量,不可谓不划算。

缺点在于网上比较流行的几种 Worker 实现对 SEO 的支持都不算友好,也没有直接支持评论等功能,需要做比较多的二次开发才能满足较为复杂的需求。同时近年来 Cloudflare 的国际 CDN 在国内的访问速度越来越慢,有的地区甚至还会间歇性地无法访问,因此国内用户的使用体验可能会不太好。

基于Vercel建站

既然是折腾和前端相关的东西,怎么能少了 Vercel 呢?比较著名的 Vercel 实现为 ijjk/notion-blog 项目。如果你熟悉 Vercel ,那基础配置比 Cloudflare Workers 方案要简单得多,只要找到 NOTION_TOKENBLOG_INDEX_ID 两个值(可以参考文档)就可以一键启动,然后再把实例绑定到期望的域名即可。

其实现机制和上文的 Cloudflare 方案差异较大,本质上是一个以 Notion 为数据库(直接访问 Notion 的内部 API )的基于 Next.js 的动态博客系统,这也是为什么该项目会要求相关 Notion 页面具备一个类似于其他博客系统的 front matter 配置即 table 用于记录元数据的原因。

虽然部署方式更简单了,但如果想做调整那就要对原项目进行修改。所以整体的自定义复杂度其实还是挺高的,更适合对前端项目有一定积累的用户使用。另外 Vercel 在国内的访问速度没比 Cloudflare 好多少,所以对于国内用户来说同样也可能会有体验问题。

自助反向代理建站

如果手上已经有了一台 VPS 之类的机器,那我们还可以选择通过反向代理 Notion 的方式来复用已有机器自助建站。

以 Nginx 为例,我们首先要保证 Nginx 已经启用了 ngx_http_sub_module 模块,这个模块不一定是默认开启的,有可能会需要手动编译。该模块的主要作用为按配置修改代理后的响应内容。这一点至关重要,不然我们无法解决 Notion 原始响应中各类固定域名和响应字段,进而也就无法完成反向代理。

下面给出仅考虑可正常加载页面的最简单情况下 Nginx location 的配置,并对重点指令进行解释,自定义域名使用 example.foo 指代:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
    location / {
        # 由于 Notion 页面经常比较大,默认 buffer 配置容易出现 502 的情况,所以需要调大 buffer ,具体大小可以按需选择
        proxy_buffers 8 32k;
        proxy_buffer_size 64k;
        
        # rewrite 至共享空间的首页页面,以处理根路径的访问
        rewrite ^/$ https://example.foo/Foo-b1324470dd5d33d04;

        # 将流量代理至 Notion ,网上有很多实现的代理目标是 notion.so ,但 Notion 的域名规则已经发生变化,改为了 {用户名}.notion.site ,故此处也要进行修正,下同
        proxy_pass https://example.notion.site;

        # 设置一系列的代理请求头,保证对于 Notion 来说请求仍可信且可处理
        proxy_set_header Host example.notion.site;
        proxy_set_header Referer https://example.notion.site/;
        proxy_set_header User-Agent $http_user_agent;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header Accept-Encoding "";
        proxy_set_header Accept-Language $http_accept_language;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;

        # 将响应中的 Notion 域名替换为自定义域名,以保证 XHR 等请求也能被正确地反向代理
        sub_filter 'https://www.notion.so' 'https://example.foo';
        sub_filter 'https://example.notion.site' 'https://example.foo';
        
        # 上述逻辑会导致 Notion 前端代码将 requestedOnPublicDomain 判断为 true 并携带此参数向 Notion 后端请求是否需要登录,最终导致强跳至登录页。由于 Notion 前端实现比较复杂,这里简单处理为对 Notion 后端返回的需要登录字段强制覆盖
        sub_filter '"requireLogin":true' '"requireLogin":false';
        
        # 上述替换涉及到了 application/javascript 和 application/json ,但实际上有些 text/plain 响应也包含了需要替换的内容。所以这里设置为替换所有 MIME 类型的响应
        sub_filter_types *;
        
        # 为 off 时表示需要扫描原始响应中的所有内容进行替换,而不是默认的替换一次即停止
        sub_filter_once off;

        # 反向代理时启用 SNI ,避免 TLS 握手出现问题
        proxy_ssl_server_name on;
    }

这类自定义反向代理方案的好处在于能复用已用机器资源,同时如果机器的网络比较优秀,还可以加速对 Notion 的访问,解决了上面几种方案在国内的尴尬使用体验。缺点是过于依赖 Notion 的前后端设计,Notion 如果做了大改动,那实现方式就需要重新探索。其实 Cloudflare Workers 方案也有这个问题,不过作为活跃的开源项目,Fruition 等的社区支持还是比较给力的。

总结

笔记工具远没有持续输出重要。对于上述几个方案的选择,我的建议是没特殊需要就使用 Cloudflare Workers 实现,舍得花钱、想省事就选 SaaS 方案,愿意折腾、乐于折腾再考虑 Vercel 或 Nginx 方案。

再见Hexo——从Hexo迁移至Hugo

2022-08-10 01:36:15

16 年的时候,博客使用的虚拟主机需要做迁移,当时所使用的 Typecho 是一个依赖于数据库的 PHP 博客系统,数据导出过程很艰辛。彼时 Gihub Pages 正大火,我也就跟风转投了静态博客系统 Hexo 。七年过去了,博客还在,但折腾 Hexo 的人是越来越少了。如今我也要和 Hexo 说声再见,拥抱 Hugo 的怀抱了。

为什么改用Hugo

依赖管理问题

静态博客的同步和备份方案一文中,我分享了基于 Github 和 iCloud 的同步备份方案,期望在保证数据安全的同时,写作环境可以在我的两台电脑上无缝切换。

然而实际上,由于 Hexo 依赖于 Node.js ,庞大的 node_modules 并不能直接同步而是需要分别安装和更新,如果忘了运行 npm install -S ,那报错和渲染异常就是家常便饭的事。而 node-sass 这样的库还对 node 版本做了显式要求,导致如果哪台设备改了 nvm 中 node 的版本就会报错。但偏偏又有大量的主题和插件都依赖于 node-sass 。

而 Hugo 是基于 Golang 的二进制程序,安装和升级都很简单。由于内置功能足够多,插件(模块)不再是必需的了,如果有需要,其也都是通过 go mod 管理,轻量而简洁。虽然大部分主题仍然依赖于 Node.js ,但那只是创建和修改主题时的事情,不会影响到写作流程。

诚然,这并不能算作 Hexo 本身的问题,但由于底层的技术选型,导致 Hexo 必然和各类主题及插件的耦合较为严重。在三方组件实现和依赖复杂的情况下,整体的复杂度也就必然会成倍地上涨。

网页生成速度

Hexo 裸安装后的网页生成速度并不算不可接受,和 Hugo 比起来也就几秒到十几秒的差异,没有网上传得那么夸张。但多加了几个像 hexo-all-minifier 这样生命周期靠后的插件后,生成速度确实会肉眼可见地下降。而 Hugo 的网页生成速度则非常稳定,总是保持在秒级别甚至毫秒级别,因此也可以真正意义上地实现本地实时预览。

Hugo的特性

Hugo 对 org-mode 、pandoc 等提供了原生支持,轻度使用体验也不错(深度使用也会遇到坑)。虽然 Hexo 等也可以通过安装插件和转换器等方式来实现,但这又会回到上面的依赖复杂的问题之中。此外,Hugo 的 shortcode 功能也非常强大,如果不考虑 md 文件的通用性,那结合 shortcodes 可以轻松实现很多本需要依赖于插件(模块)才能做到的功能。

快速上手

首先安装 Hugo 并创建站点,以 macOS 系统为例:

1
2
brew install hugo # For macOS
hugo new site sample

这里需要注意的是,如果此前系统中已经安装过旧版本的 Go ,那有可能需要升级后才能使用 homebrew 完成安装。随后选一个喜欢的主题拉取到 /themes 目录,以 even 主题为例:

1
git clone https://github.com/olOwOlo/hugo-theme-even themes/even

主题通常会带有示例配置文件 config.toml ,将其复制到站点的根目录下覆盖默认配置文件,并完成相应配置后,运行 hugo new post/test.md 新建文章,随后运行 hugo server -D 即可查看站点。对于存量的文章,需要将其复制到主题 /content/ 目录下,其中文章类的需要按主题的设计来放置于具体目录,如 even 主题使用 post 目录,则需要将文章复制到 /content/posts 目录下,重新执行上面的命令就能在站点里看到文章了。

如果运行命令时报错,则可能是存量文章的 front matter 格式有不符合 Hugo 要求的情况,此时需要按官方文档进行修改适配。

写作习惯的变化

整体写作习惯其实和 Hexo 的体验差别不大。不过 Hugo 提供了比 Hexo 更丰富的 front matter 默认配置,同时还支持 org-mode 等玩法,所以写作方式上的可玩性会更高些。

主题自定义方式的变化

与 Hexo 主题的完全前端实现不同,Hugo 的主题使用了 Go 的模板语言,有点类似于 PHP 和 JSP ,并向主题暴露了一系列的全局变量和函数,所以其实主题和 Hugo 或 Golang 还是有一定的耦合的。

不过这也使得我们可以通过自定义模板覆盖主题默认模板的方式,来既实现自己的需求,又能最低限度地避免修改主题源代码导致的升级困难。但很多主题没有提供关键位置的钩子模板(也可以说是接口),导致我们经常需要拷贝一部分主题的源代码到自定义模板中,这又对主题的升级造成了一定的影响,可以说是有得必有失了。

向前兼容

迁移工作的一个核心要求就是尽量避免引入 breaking change 。首先要保证存量页面的链接不发生变化,以避免出现 404 的情况。其次要尽量对此前在 Hexo 中使用的各项功能进行支持。

永久链接格式兼容

在 Hexo 中,通常有以下三种 URL 永久链接路径格式:

  1. 日期前缀+英文别称或文件名/2021/07/06/a-better-hexo-theme-even/
  2. 固定前缀+英文别称或文件名/posts/slidev-tutorial/
  3. 使用了 abbrlink 等插件生成 CRC/Hash 值作为路径/posts/8ccq01298/

而 Hugo 默认的永久链接格式为 /{{ 文章目录 }}/{{ 文章文件名 }} ,和上面第二种比较相似但又有所不同。那么我们该如何实现兼容呢?

首先我们要了解 Hugo 可以在根目录的 config.toml 中对永久链接进行自定义配置,例如:

1
2
[permalinks]
  '/' = "/posts/:slug"

因此我们只需要针对不同情况,对该配置进行自定义即可。

  1. 日期前缀+英文别称或文件名:仿照 Hexo 的日期格式,将值配置为 /:year/:month/:day/:title/
  2. 固定前缀+英文别称或文件名:将值改为 /posts/:title 即可。 Hugo front matter 中的 slug 变量表示自定义别名,所以如果此前在 Hexo 使用了自定义的变量,只要仿照此前的配置将 title 改为 slug 即可,例如 /posts/:title
  3. 使用 abbrlink 等插件生成的 CRC/Hash 值作为路径:Hugo 似乎没有 abbrlink 这类插件,不过我们可以仿照这篇文章,在默认内容模板 archetypes/default.md ,再将其中的 slug 配置为一段具备哈希或 CRC 功能的表达式即可。不过存量文章可能需要通过 front-matter 中的 url 变量进行完整路径的显式声明,不然如果表达式的处理结果和 Hexo 中的不同,那链接可就变了。

上面说明了 permalinks 的值,那 key 该如何配置呢?与 Hexo 不同,Hugo 中永久链接的固定前缀(对应上文的情况 2 和情况 3)是根据目录位置生成的,该位置的选择又与主题有关。有的主题使用的是 /content/post/ 目录,有的主题使用的又是 /content/posts 目录,同时这个路径在很多主题的实现中是写死的。因此如果此前你在 Hexo 中所使用的固定前缀和所选 Hugo 主题的不同(如 /articles/),那就会造成链接发生变化的问题。

所以我们需要在 permalinks 配置的 key 上做些文章,把主题所用的路径做一层指定映射,保证最终的路径以我们期望的前缀输出。以主题使用 /content/post/ 作为内容目录、原 Hexo 文章的永久链接格式为 /posts/:slug 为例,对 permalinks 进行以下配置即可:

1
2
[permalinks]
  post = "/posts/:slug"

另外,对于此前在 Hexo 中配置了 html 后缀等情况,可以开启 Hugo 的 Ugly URLs 来实现兼容,细节可以参考官方文档

归档页面路径链接的兼容

前一节提到了主题对内容目录路径的选择可能是不同的,而这也会影响到归档页面的路径。在 Hugo 的大部分主题中,如果主题使用 /content/post/ 作为内容目录,那归档页面路径则默认为 /post/ 且不支持配置。而我们在 Hexo 中通常会使用 /archives/ 作为归档页面的路径,如何才能保持不变呢?

虽然绝大部分主题(也可能是所有)都没有此项配置,但我们可以通过自定义一套模板和页面的方式来绕过主题的限制。以 even 主题的模板和 CSS 样式为例,在 /layouts/_default/ 目录下新建 archives.html 模板,随后填充以下内容,用于按年分组遍历所有文章,并在原主题的框架下输出文章标题列表:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
{{- define "title" }}{{ T "archive" }} - {{ .Site.Title }}{{ end -}}

{{- define "content" }}

{{ $pageList := (where .Site.RegularPages "Type" "post") }}

<section id="archive" class="archive">
  {{- if .Site.Params.showArchiveCount }}
    <div class="archive-title">
      <span class="archive-post-counter">
        {{ T "archiveCounter" (len $pageList) }}
      </span>
    </div>
  {{- end -}}

  {{ range ($pageList.GroupByDate "2006") }}
    <div class="collection-title">
      <h2 class="archive-year">{{ .Key }}</h2>
    </div>

    <ul class="archive-list">
      {{ range (where .Pages "Type" "post") }}
        <div class="archive-post">
          <span class="archive-post-time">
            {{ .PublishDate.Format "01-02" }}
          </span>
          <span class="archive-post-title">
            <a href="{{ .RelPermalink }}" class="archive-post-link">
              {{ .Title }}
            </a>
          </span>
        </div>
      {{ end }}
    </ul>
  {{ end }}
</section>
{{ end }}

随后在 /content/ 目录下新建 archives.md 页面,将 type 指定为刚刚定义的 archives

1
2
3
4
5
6
7
8
---
title: "归档"
layout: "archives"
url: "/archives/"
comment: false
hidden: true
type: archives
---

这时我们就已经可以通过访问 /archives/ 路径来进入到归档页面了,接下来只要在 config.toml 中再将导航栏中的对应按钮指定为预期链接即可。

1
2
3
4
5
[[menu.main]]
  name = "归档"
  weight = 20
  identifier = "archives"
  url = "/archives/"

上文这种实现的效果和很多主题的归档页面相比,主要区别在于单页面内罗列了所有文章,即缺少分页。由于大部分主题的分页逻辑和其内部的其他模板耦合较为严重,同时 Hugo 的分页相关变量被限制不能用于自定义模板之中,所以如果希望自定义的归档页能支持分类,则可能需要对 Hugo 的原生逻辑进行包装即额外实现一套分页能力才行。这里不做展开讲述。

友情链接和自我介绍

和 Hexo 一样,Hugo 也没有直接支持友情链接和自我介绍这类常用页面。在实现上我们要么在 /content/ 目录下自定义页面也就是在页面内维护内容,要么如归档页面一般,通过自定义模板的方式来加载 config.toml 中的配置。两种实现都比较简单,我也更倾向于前者,毕竟这些是低频修改页面,是否可配置区别都不大。

标签和分类的中英文问题

在 Hexo 中,我们通常会在 _config.yml 中配置标签和分类的中英文映射,这样我们在 front matter 中可以使用任意语言标识标签和分类,但生成后两者的 URI 都是英文。然而在 Hugo 中却没有这类简易设置,也许我们可以通过修改主题和永久链接的方式来间接支持,但估计成本较高。所以如果对 URI 有强迫症的读者,还是建议把存量文章的标签和分类改为英文。而如果对此没有特殊需求,那使用中文也可。除了 URL 的分享可读性可能较差外,在 2022 年的今天其实已经不会影响搜索引擎的 SEO 效果了。

支持Git与VPS部署

不知为何 Hugo 官方没有直接支持使用 Git 搭配 Git Hooks 部署站点,对于我这种把博客部署在 VPS 的用户给出的建议方案是 rsync 。其实 rsync 方案是完全可行且成本不高的,不过本着尽量兼容的原则我还是决定在部署时执行以下 shell 脚本来通过 Git 推送生成的 /public/ 目录至 VPS ,而 VPS 上的 Git 库和 Git Hooks 配置则无需改动:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
#! /bin/bash

rm -rf ./public
hugo

rm -rf ./.deploy_git
mkdir .deploy_git
cp -r public/* ./.deploy_git
cd .deploy_git

git init --initial-branch=master
git add -A
git commit -m 'Deploy commit.' --quiet
git push -u foo@bar-server:/var/blog.git HEAD:master --force

cd ..
rm -rf ./.deploy_git

兼容Hexo的RSS形式

使用 Hexo 时博客的 RSS 是全文输出,而换到 Hugo 后 RSS 却变为了输出摘要。作为一个重度 RSS 用户,我自然是深知拉取到的文章还要二次跳转到浏览器才能看原文的体验有多差,所以还是要让 RSS 的表现和此前一致才行。

Hugo 的 RSS 是基于默认 RSS 模板生成的,所以我们只要重新定义一个模板并改为全文输出即可。Hugo 的默认实现中,决定输出内容的是如下这行代码:

1
      <description>{{ .Summary | html }}</description>

我们只需要在 /layouts/ 目录新建 index.rss.xml 覆盖默认模版并将原实现拷贝至其中,接着把代码中的 {{ .Summary | html }} 替换为代表全文内容的表达式 {{ .Content | html }} 即可:

1
      <description>{{ .Content | html }}</description>

even主题迁移

由于此前一直在使用修改过的 Hexo even 主题,为保证前端效果不变,所以主题方面也采用了 Hugo 下的 even 主题,因此会有很多主题层面的额外适配工作。

自定义导航栏

此前使用 Hexo 下 even 主题时自定义了一个用于引导用户的导航栏,那么如何在 Hugo 的 even 主题中实现兼容呢?

首先我们在 /layouts/partials/header/ 下新建 top-nav.html 模板,按需填充内容,如增加 Newsletter 、Telegram Channel 等引导:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
{{- if .Site.Params.enableTopNav }}
  <div class="top-nav">
    {{- if .Site.Params.revue.enabled -}}
      <a href="{{ .Site.Params.revue.home }}" href="_blank" class="top-nav-button">Newsletter</a>
    {{- end -}}
    {{- if .Site.Params.telegram.enabled -}}
      <a href="{{ .Site.Params.telegram.link }}" href="_blank" class="top-nav-button">电报频道</a>
    {{- end -}}
    {{- if .Site.Params.wxOfficialAccount.enabled -}}
      <a href="{{ .Site.Params.wxOfficialAccount.url }}" href="_blank" class="top-nav-button">微信公众号</a>
    {{- end -}}
  </div>
{{- end -}}

随后我们需要找个位置引入该模板。受 even 主题实现的限制,我们需要将该模板放置于 header 块之后才能最低成本地保留原布局。因此我们只有一个选择,那就是将 baseof.html 这个基础模板进行覆盖。拷贝原模板内容至 /layouts/_default/baseof.html 中,并在 header 块之后、main 块之前引入此前定义的 top-nav.html

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
...
  <div class="container" id="mobile-panel">
    {{ if not .Params.hideHeaderAndFooter -}}
    <header id="header" class="header">
        {{ partial "header.html" . }}
    </header>
    {{- end }}
  
    {{- partial "header/top-nav.html" . -}}

    <main id="main" class="main">
...

最后在 config.toml 中完成相关参数配置即可:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
  [params.wxOfficialAccount]
    enabled = true
    url = ""

  [params.telegram]
    enabled = true
    link = ""
    
  [params.revue]
    enabled = true
    home = ""

支持umami访问统计

博客之前一直按「使用Nginx将请求转发至Google Analytics实现后端统计」一文的方式来实现请求统计。但这个方式的问题在于,由于不要求加载 JS ,很多非真实流量(主要为 RSS 阅读器的抓取)也会被统计进来。后来看到「搭建 umami 收集个人网站统计数据」这篇文章,便也用 umami 搭建了一个轻量的统计能力。

even 主题自然没有对 umami 进行原生支持,我们需要做的是先找到一个包含 head 的模版并在 <head/> 标签中添加以下内容:

1
2
3
  {{- if (in (slice (getenv "HUGO_ENV") hugo.Environment) "production") | and .Site.Params.umami.enabled -}}
    <script async defer data-website-id="{{ .Site.Params.umami.id }}" src="{{ .Site.Params.umami.js }}"></script>
  {{- end -}}

然后在配置中完成定义即可:

1
2
3
4
  [params.umami]
    enabled = true
    id = "" # umami 统计 id
    js = "" # umami 的 JS 地址

由于 even 主题没有提供可以直接拓展 <head/> 标签的模板,我的选择是将代码加到此前不得不重写的 baseof.html 中。不得不说,这个实现很丑陋,但成本确实也是最低的。

此外,为了避免本地启动时 umami 将本地请求也进行了统计并将 Referrer 识别为 localhost ,上文的实现中对环境做了判断,即正式生成站点时才会引入 JS 依赖来上报 umami ,本地运行则不引入。

自定义文章末尾页脚

此前在 Hexo 的 even 下我也对文章末尾进行了自定义。对于 Hugo 的 even 主题,改造成本最低的方式为重写 /layouts/partials/post/copyright.html 模板。

首先要和此前展现形式对齐的是「原文链接」。even 主题本身只支持将 Markdown 原文件地址作为文章链接,所以我们需要在该模板中仿照 lionToMarkDown 部分添加以下内容:

1
2
3
4
5
6
  {{ if $.Site.Params.copyrightLink -}}
    <p class="copyright-item">
      <span class="item-title">文章链接</span>
      <span class="item-content"><a class="link-to-markdown" href="{{ .Permalink }}" target="_blank">{{ .Permalink }}</a></span>
    </p>
  {{- end }}

因为暂时没有国际化需要所以文案是固定的中文,如果想更灵活些也可以仿照原实现中的 Markdown link 来做 i18n 。

随后只要在 copyright.html 的最后引入我们自定义的文末模板即可:

1
{{- partial "post/post-footer.html" . -}}

utterances适配

even 主题本身是支持 utterances 的,但用于生成 issue 的唯一标识参数被主题写死为了 issue-term="pathname" 即根据 URI 路径生成,并没有暴露配置。而我在使用 Hexo 时该参数的值是 issueTerm="title" 即根据文章标题生成,不进行适配的话会丢失存量评论。

所以我们需要在 /layouts/partials/ 目录下新建 comments.html 覆盖主题原实现。顺便地,我们可以把另一个参数 label 也改为可配置的,这样一来,生成的 Github issues 便可以自动加上 utterances 标签方便分类:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
{{ if and .IsPage (ne .Params.comment false) -}}

  <!-- utterances -->
  {{- if .Site.Params.utterances.owner}}
    <script src="https://utteranc.es/client.js"
            repo="{{ .Site.Params.utterances.owner }}/{{ .Site.Params.utterances.repo }}"
            issue-term="{{ .Site.Params.utterances.issueTerm }}"
            label="{{ .Site.Params.utterances.label }}"
            theme="github-light"
            crossorigin="anonymous"
            async>
    </script>
    <noscript>Please enable JavaScript to view the <a href="https://github.com/utterance">comments powered by utterances.</a></noscript>
  {{- end }}

{{- end }}

随后我们便可以在 config.toml 中对 utterances 按需进行配置:

1
2
3
4
5
  [params.utterances]       # https://utteranc.es/
    owner = ""              # Your GitHub ID
    repo = ""               # The repo to store comments
    issueTerm = "title"     # 新增配置,可按需选择 issue 生成时的唯一标识方式
    label = "utterances"    # 新增配置,可按需指定 issue label

补齐底部社交图标

主题的社交图标使用的是托管于 iconfont 的私有实现所以直接拓展未支持的新图标较为困难。我在记hexo-theme-even主题优化一文中提到了相同的问题,文中最终选择了使用 Font Awesome 来解决,对于 Hugo 的 even 主题我们也如法炮制进行处理。

首先,在 Font Awesome 官网下载依赖并放置于 /static 目录下。例如我使用的是引入所有图标 JS 的方式,则最终路径为 /static/js/fontawesome.all.min.js 。然后在 config.toml 配置中引入该 JS 文件:

1
2
[params]
  customJS = ["fontawesome.all.min.js"]

接着,我们在 /layouts/partials/ 目录下新建 footer.html 覆盖主题原实现并保留原实现的其他代码,只对 social-links 部分进行如下修改:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
<div class="social-links">
  {{- range $name, $config := .Site.Params.social }}
    {{- if $config.path }}
      <a href="{{ $config.path | safeURL }}" class="iconfont" title="{{ $name }}"><i class="{{ $config.icon }}"></i></a>
    {{- end }}
  {{- end }}
  {{ if .Site.LanguagePrefix -}}
    <a href="{{ .Site.LanguagePrefix | absURL }}/index.xml" type="application/rss+xml" class="iconfont" title="rss"><i class="fas fa-rss"></i></a>
  {{- else -}}
    <a href="{{ .Site.RSSLink }}" type="application/rss+xml" class="iconfont" title="rss"><i class="fas fa-rss"></i></a>
  {{- end }}
</div>

最后在 config.toml 中添加需要的图标配置即可。icon 即图标的完整 class 属性,path 即需要跳转的链接地址。需要注意的是,主题的原逻辑为了实现多语言,将 RSS 图标的逻辑隔离在了通用逻辑之外。这里也保留了原实现,即 RSS 图标是默认出现且不可去除的。如果不需要 RSS 则可以对上面的代码再进行修改,以删除独立的 RSS 逻辑。

1
2
3
4
5
6
7
  [params.social]
    a-email = { title = "Email", icon = "fas fa-envelope", path = "" }
    b-twitter = { title = "Twitter", icon = "fab fa-twitter", path = "" }
    c-github = { title = "Github", icon = "fab fa-github", path = "" }
    d-weixinOfficialAccount = { title = "微信公众号", icon = "fab fa-weixin", path = "" }
    e-telegram = { title = "Telegram", icon = "fab fa-telegram", path = "" }
    f-search = { title = "Search", icon = "fas fa-search", path = "" }

除了 Font Awesome ,最近我还看到了tabler ICONS这个库,直接支持 SVG 同时还是 MIT 协议的开源项目,也值得一试。

此外,由于我们已经覆盖了 footer 模板,那我们也可以对其他内容也进行自定义,比如将友情链接放置于 footer 等,下文的总字数统计也同样均基于自定义的 footer.html 进行处理。

支持总字数统计

even 主题自带每篇文章的字数和预计阅读时间统计,但却没有之前我借助 hexo-wordcount 所实现的全站文章字数统计。检索网络后找到了这么一篇文章 Hugo 总文章数和总字数 ,照猫画虎在 footer.html 中添加以下内容:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
  {{ if .Site.Params.countAllWords.enabled }}
    {{$scratch := newScratch}}
    {{ range (where .Site.Pages "Kind" "page" )}}
    {{$scratch.Add "total" .WordCount}}
    {{ end }}

    <span style="display: block;">
      {{ .Site.Params.countAllWords.prefix }} {{$scratch.Get "total" }} {{ .Site.Params.countAllWords.suffix }}
    </span>
  {{ end }}

随后在 config.toml 中对相关参数进行配置即可:

1
2
3
4
  [params.countAllWords]
    enabled = true
    prefix = "共计"
    suffix = "字"

除了以上逻辑,还可以通过改变 range 的查询范围来按需限定需要进行总字数统计的页面集合。另外配置中的固定文案比较生硬,可以考虑加入 i18n 相关实现来满足多语言切换的需要。

总结

天下武功,唯快不破,Hugo 的速度确实让我印象深刻。但对于从 Hexo 迁移而来,同时还对 Hexo 有很多自定义配置的用户来说,迁移过程中的兼容和适配的成本其实是不低的,实际上目前的迁移仍未实现「基于 LeanCloud 的阅读计数」和「推荐阅读」两项功能的兼容。此外,无论是 Hexo 还是 Hugo ,其主题的深度自定义修改都比较麻烦,以后还是要考虑自己实现一套主题(明年一定)。

旁路由的原理与配置一文通

2022-08-02 12:18:28

最早听到旁路由这个词是在 2020 年折腾 N1 的时候,这台单网口的小盒子只能用网上所说的旁路由方案接入局域网来实现期望的功能。现在回想起来,旁路由这个词有可能就是在那个发烧友大量折腾斐讯 N1/P1/T1 的时期被发明出来的。你没办法在发烧友圈子外的互联网及各种学术材料中找到对旁路由的描述和定义,当然也找不到合适的英文翻译(导致这篇文章的 slug 定义困难);从拓扑上看,旁路由更像是杂糅了二级路由和透明网关的概念,除了实体确实多接了根网线放在主路由旁边,其本身并没有真正地开启一条旁路,做的事情也基本可以和网关的定义对齐。

不过,由于这个说法主要集中于网友的交流讨论之中,而且几年来在有相关需求的广大用户中被广为接受,所以只要这个词不变为一个学术概念,那我倒也不觉得有什么不妥。下文也仍然会用「旁路由」一词来指代在此类拓扑结构中担任代理网关角色的路由器。

此外,本文对于原理的解释偏多且为新手向,对于已经熟知相关概念的读者则可以将本文作为 cheat sheet 食用。

旁路由的适用场景

有人认为旁路由的加入(手动指定方式)不会在其出现问题时影响整个网络的可用性,所以应该把非路由功能都交给旁路由来实现。但实际上如果仅仅是为了达成这个目标,那在主路由上直接分流会是个更好的方案,因为这样不仅会大量减少疑难杂症的出现,而且还可以通过设置 fallback 的方式进一步提升网络的可用性。

所以我认为旁路由实际上只适用于一个场景,那就是出于某种考虑,主路由不能被替换或被大量修改,而主路由的固件又不能满足功能诉求,这时候旁路由便是一个可选的解决方案。

旁路由的原理

网上关于旁路由的配置教程多如牛毛,其中大部分是基于 F 大的 N1 OpenWrt 固件使用教程来写的,也有很多是结合了自己的踩坑经验的细化版本。由于版本太多、完整的说明太少,而且大部分没有讲清楚教程的环境上下文和原理,导致按照这些教程来配置的用户往往不得不一遍遍地折腾重试,甚至会遇到逐步配置最终却断网、访问不了部分网站等令人头疼的问题。

授之以鱼不如授之以渔,为了能彻底讲清楚旁路由该如何配置,我们暂且不谈具体步骤,而是先搞明白旁路由的运作原理。

名词解释

由于计算机网络的术语在不同时期、不同环境下,对于细节的含义其实有比较大的差异,故首先我们先定义好下文中将使用到的各类专有名词的含义,以免出现信息不对称的情况。同时为了方便理解,我们也尽量和 OpenWrt 的名词对齐,并尝试与现实生活场景进行类比。

  1. 路由:将数据从源地址传输到目的地址的行为。可以看出,这个行为抽象涵盖了两个动作,一是找到地址、二是转发数据。可以类比为网购时快递公司将商品从卖家发送给买家的过程。
  2. 路由表:可以简单理解为路由过程中的地址关联信息的合集,也就是现实中发货地址和收货地址的映射关系。
  3. 路由器:本文中指家用的、具备路由功能的实体网络设备,其本身并不一定需要真的承担或只承担路由功能,可以抽象理解为就是一台普通的电脑或服务器。当然,它也可以是一个容器实例、一个虚拟机。对应到现实中就是快递公司。
  4. 接口:这里特指网络接口,指两个网络设备或协议层的连接点。现实中连接路由器时,wan/lan 不同的网线插槽其实就可以理解为接口。只不过接口并不一定是物理实体,所以我们才会在 OpenWrt 的接口设置里看到甚至新增许多物理设备上所没有的接口。
  5. 网关:这里特指网络中的网关,负责执行数据转发的某个抽象设备。这里可能容易与路由器的功能混淆,毕竟路由器如果用来做路由似乎也是在做转发的工作。实际上这是由于术语的历史使用缺少规范导致的边界不够清晰,可以粗略理解为承担路由功能的路由器就是网关,但网关不一定是只能由路由器担任。
  6. IP:本身指 IP 协议,本文为方便也可能将其作为 IP 地址的简称,且默认为 IPv4 。本节所有的名词其实都是基于 IP 协议运作的,而 IP 地址即上文各类「地址」的实际值。可以理解为你用于发送和接收快递的门牌号。
  7. DHCP:IP 地址的管理和分配协议,本文中不单单指协议本身,还指负责执行该协议的设备。可以理解为给你分配门牌号的物业,只不过这个门牌号是动态变化的。
  8. DNS:上文说到数据的传输需要源地址和目的地址,而这个地址就是 IP 地址。但由于 IP 地址难以记忆,所以才会有了可以作为 IP 地址别名的域名,而 DNS 就是负责进行域名和 IP 地址映射转换的系统。本文中 DNS 也指代负责运行该系统的设备。
  9. NAT:出于各种考虑,局域网与因特网的 IP 地址是隔离的,NAT 可以理解为内外网 IP 地址转换的流程。类比网购,相当于快递公司把快递送到附近快递站,快递站的快递员再把货物送到你家门口的流程。
  10. SNAT:NAT 的一种,本质上是在修改网络包的源地址,目的是可以强制网络包返回时经过期望的地址。可以理解为支付宝的作用,即钱虽然表面上是点对点转账的,但经过支付宝后,支付宝就要求相关转账信息的回执必须由它中转一次再告知转账发起人。

网络拓扑

旁路由架构的网络拓扑图

可以看到,无论如何配置,我们都需要保证数据按图中的拓扑进行流转,才能实现我们所希望的只在旁路由增加功能而不修改主路由的目的。由于流量(至少上行流量)总会流经旁路由,所以旁路由实质上就是一层透明代理。

工作原理

那么我们该怎么实现这样的网络拓扑呢?让我们先来看下数据在网络的更底层是如何流转的。

旁路由架构的数据流转示意图

从图中可以看到,当我们从手机等终端设备发出一个数据包时,数据包总是由我们的终端设备经由网关路由至目的地址,目的地址返回数据时也是相同的路径。这是因为终端设备本身通常是不具备路由功能的,单单一个路由表终端设备就搞不定。

既然数据必然经过网关,那么我们只要强制把旁路由作为终端设备对外数据交互的第一层网关即可。至此旁路由的工作原理其实就已经解释清楚了,即在另一台路由器上实现的基于网关的透明代理。而网上各式各样的教程其实都是在教我们解决如何配置网关的问题。

在这一章节还需要说明的是为什么各个教程都要求我们把旁路由的 IP 配置在和主路由相同的 IP 网段。所谓的 IP 网段实际上就是子网,同一子网下的主机(设备)可以直接通信,跨子网则需要通过某种形式转换后才能通信,而这些转换虽然可行但比较复杂,在旁路由这个场景下显然是没有必要的。本着不改变原有网络拓扑的原则,旁路由自然也要配置在和主路由及其他设备相同的子网才行。

旁路由的配置

那么我们该如何配置网关以让数据按上文的工作原理进行流转呢?

(一)旁路由的网关设置

首先我们先来解决旁路由的网关问题。在上文的拓扑图中我们可以看到,旁路由虽然挂着路由器的名字,但它本质上也是网络链路中的一个节点,因此它也需要请求上层网关才能完成数据流转。而主路由是这个网络拓扑的出口,所以旁路由的网关自然要配置为主路由的 IP 地址。这一项配置是必须且不会随终端网关配置方式的变化而改变的,无论如何指定网关请求旁路由,旁路由本身都要依赖此配置才能完成正常的流量转发。

此外,还有子网掩码需要进行配置。前面有提到,在同一子网内的主机之间才能直接通信,而 IP 和子网掩码相组合便能确定设备当前所在的子网。旁路由并不改变网络拓扑,所以需要和主路由在同一子网内。因此将旁路由的子网掩码配置设置为主路由的子网掩码即可。同理,下文的所有子网掩码配置也均需要与主路由的保持一致。

(二)旁路由的DHCP配置

虽然配置了网关后数据流转图中的左半边已经成型,但如果不对旁路由的 DHCP 进行配置,实际上会导致各种各样的疑难杂症或直接无法联网。

原因在于 DHCP 使用了 UDP 协议,UDP 是没有连接的,如果主路由和旁路由同时开启 DHCP,则任意一个 DHCP 服务器都可能会应答终端的申请,进而导致 IP 下发和路由表的混乱造成各种无法连接的疑难杂症。当然,我们可以将两个 DHCP 的子网网段拆分开来解决共存问题,但这个行为在旁路由场景下并没有实际意义。

因此我们需要保证网络中只有一个设备承担 DHCP 功能,出于不改变原网络拓扑和避免无意义 NAT 的考虑,我们通常选择关闭旁路由的 DHCP 功能(对应到 OpenWrt 则选择「忽略此接口」)。

(三)终端网关配置

对于手机、电脑等终端,我们的目标是将其网关配置为旁路由的 IP 。实现方案很多,成本较低的主要为以下两种。

手动指定

顾名思义,只需要在终端设备的网络设置中将网关手动配置为旁路由即可。以 iOS 系统为例:

iOS 网络配置界面

手动填写一个网络上未被占用的 IP 地址,而子网掩码以主路由为准,网关则填写旁路由的 IP 地址。

手动指定的好处在于完全不影响原网络的使用,设备按需配置是否使用旁路由作为网关以实现特定功能。当旁路由故障时,未手动指定的设备仍能正常上网。

缺点在于操作烦琐。手机、电脑还好,但电视或根本没有屏幕的设备设置起来就会很麻烦。

依赖DHCP指定

DHCP 除了可以管理 IP 的分配,还会下发网关和 DNS 服务器信息,因此我们还可以借助 DHCP 的这一机制来为所有终端统一设置网关,而不再需要逐个手动修改。

前面提到,我们关闭了旁路由的 DHCP 功能,因此这个统一下发的工作就要交给主路由来完成。只需要在主路由的 DHCP 配置中将网关配置为旁路由的 IP 地址即可。

OpenWrt 的 DHCP 配置界面

以 OpenWrt 为例,将主路由 DHCP 下发的网关配置为旁路由 IP 地址即可(3 表示网关,6 表示 DNS 服务器地址)。

不过有些路由器的默认固件没有开放该配置项,对于这些设备,除非可以 SSH 连接后手动改配置,不然无法使用此种指定方式。

这种方式的好处显而易见,一次配置全家受用;缺点在于当旁路由出现故障时,所有连接的设备都会无法上网。

(四)DNS的配置

细心的读者可能发现了上文只提到了网关的配置,但未提到很多教程中的 DNS 配置。实际上单单就旁路由本身来说,网关配置完成后整个网络拓扑就已经搭建完毕了。但对于一些特定诉求,比如依赖旁路由进行统一的 DNS 劫持(很多功能的底层都依赖于此),则需要将对应位置的 DNS 也配置为旁路由的 IP 以将域名解析工作也完全交由旁路由处理。

配置步骤总结

手动指定方案

  1. 为旁路由配置和主路由同网段的静态 IP 地址,同时将旁路由的网关和 DNS 指向主路由,子网掩码与主路由保持一致。
  2. 旁路由关闭 DHCP 服务。
  3. 在主路由防火墙开启 SYN-flood 防御的情况下,关闭旁路由防火墙的 SYN-flood 防御(可选)。
  4. 在需要接入旁路由的终端设备中,将网关和 DNS 配置为旁路由 IP 地址,配置同网段的 IP 地址和与主路由相同的子网掩码。

DHCP下发方案

  1. 为旁路由配置和主路由同网段的静态 IP 地址,同时将旁路由的网关和 DNS 指向主路由,子网掩码与主路由保持一致。
  2. 旁路由关闭 DHCP 服务。
  3. 在主路由防火墙开启 SYN-flood 防御的情况下,关闭旁路由防火墙的 SYN-flood 防御(可选)。
  4. 在主路由的 DHCP 配置中,将其下发的网关和 DNS 配置为旁路由的 IP 地址。

疑难杂症的解决

虽然配置步骤看上去很简单,但很多人在实际使用中都会遇到逐步配置却上不了网或网络慢的问题,这里挑几个典型案例来解析。

(一)该不该设置iptables的MASQUERADE

这可能是争议最大的一条,有人说这条规则加上后影响性能而且没意义,但也有很多人表示不配这条就是连不上网(大多为连接不上国内网络)。

这条规则的作用本质上是在旁路由上做 SNAT,只不过修改的地址不需要指定而是动态获取旁路由对应接口网卡的 IP 地址,在 OpenWrt 里被称为「IP 伪装」。

配置 MASQUERADE 后的数据流转示意图

单从旁路由的网络拓扑来说,这条规则确实没意义,因为主路由作为对外出口必做 NAT,但旁路由本身就在局域网内且只是链路上的一环,没有必要再对内网 IP 进行耗费性能的 NAT 操作。

但不要忘了,理论和现实是两回事,物理网络拓扑中的不同设备、不同固件都有可能产生各种奇怪的兼容问题。我自己倒是没有遇到过该问题,但检索网友们的各种帖子,大概可以分为以下几种原因:

  1. 主路由固件数据包处理问题:部分路由器(似乎主要为国产品牌)的无线网在桥接和 iptables 处理过程中,当旁路由将国内流量重新转发回主路由时,主路由根据流中的首个数据包的状态做判断导致后续数据包未进行 NAT 就直接访问了互联网。这篇文章对此有比较详细的讲解(CSDN 也是有很多好文章的)。
  2. 主路由 NAT 硬件加速导致的问题:可能是由于硬件加速流程对数据包的处理出现了类似于原因 1 的问题导致无法正确完成数据交互。由于硬件加速本质上是在运行特殊驱动,而各家厂商的该驱动几乎都是闭源的,所以网上也没见到有探究深层原因的资料。这种情况下把硬件加速关闭即可(会一定程度牺牲主路由 NAT 性能)。
  3. IP 和 MAC 校验机制导致包被丢弃的推论:由旁路由的网络拓扑可知,配置网关后上行流量必然经过旁路由,但在旁路由不进行 NAT 时数据包中的 IP 仍然是终端设备的,所以理论上下行数据并不会经过旁路由而是由主路由直接转发至终端。显然,由于旁路由的存在,IP 地址和 MAC 地址在某个环节会有不匹配的情况,如果主路由对此有校验,那数据包就会被丢弃掉。但这也只是个推论没有证据佐证,同时由于网络可能的复杂性,主路由通常也不会主动做这种校验。

MASQUERADE 配置其实并没有定向地去解决上面这些具体问题,而是通过 NAT 来隐藏终端设备、只向主路由暴露旁路由 IP 的方式,一刀切地避免了上述原因导致的问题。但由于上下行流量都会经过旁路由,所有流量都会被二次 NAT 和二次转发,网络的吞吐会有不小的下降,直观感受就是下载速度变慢了。

所以比较理想的策略是,先不加 MASQUERADE 规则观察是否有问题(尤其是国内流量),如果确实有问题,在权衡可以接受性能的损失后再配上该规则。

(二)LAN和WAN是否需要绑定

具体操作是取消桥接,再设置 WAN 和 LAN 共用同一个网卡(如 eth0 )。这个操作其实和 MASQUERADE 规则的效果类似,因为绑定后经过 WAN 的流量必然会被 SNAT 。适用于确实遇到了疑难杂症且能接受性能损失场景下的备选方案。

(三)是否需要关闭旁路由桥接

在许多教程里,这个操作和添加 MASQUERADE 规则是配套的。但桥接与否实际上并不会影响整体的网络拓扑,这看上去又是一项没有意义的配置。我猜测可能是网上流传的添加 MASQUERADE 规则的方式是下面这条固定命令:

1
iptables -t nat -I POSTROUTING -o eth0 -j MASQUERADE

也就是要求 SNAT 时从 eth0 网卡动态获取 IP 。而在桥接模式下,MASQUERADE 时是需要从 br-lan (常见的桥接后的默认网卡名称,也可能是其他名称,要视实际情况而定)获取 IP 的,直接复制粘贴上面的命令会导致 IP 伪装失败。所以只要把 -o 参数的值改为 br-lan 即可:

1
iptables -t nat -I POSTROUTING -o br-lan -j MASQUERADE

但似乎确实有网友不取消桥接就无法联网,这种情况大概率是旁路由固件对桥接模式的处理有问题(多见于 ARM 架构的固件,这类固件通常要做很多的魔改适配),如果真的遇到这种情况的话则确实可以取消桥接尝试下,毕竟大多数情况下旁路由内部的桥接也没什么实际意义,去掉后由于少了流量判断流程可能还会有非常微小的性能提升。

至于 N1 这种自带无线功能但实际上没人用的设备,在关掉无线后,确实可以顺便取消掉没有意义的桥接。只是在 OpenWrt 取消桥接保存配置时,要确定选中了有线网卡对应的接口比如 LAN/eth0 ,不然如果后台的前端存在体验问题自动选择了 wlan ,那保存后路由器可就直接失联了。

(四)是否需要设置DHCP强制

如果我想通过 DHCP 下发网关但又不想改变主路由的原配置该怎么办呢?在 OpenWrt 的接口 DHCP 配置中有一个选项叫做「强制」,勾选后,此设备会忽略网络上已经存在的 DHCP 服务,强制启动本机的 DHCP 服务,所以似乎我们只需要在旁路由上配置此选项,并在旁路由 DHCP 中配置好网关,主路由不做任何变更,即可实现本段开头的诉求。

但上文提到同一子网中只能有一个用于分配指定网段的 DHCP 服务,因此旁路由强制开启 DHCP 后,还需要主路由具备主动判断网络情况停止提供 DHCP 服务的能力,这个流程才能真正运转起来。但并不是所有的路由器和固件都支持这个能力,目前来看 OpenWrt 作为主路由固件时可以正确检测并停用 DHCP ,其他固件则需要在使用时做下兼容性测试。从工程角度上讲,由于这样的操作过于依赖外部能力,属于和外部组件产生了强耦合,不利于未来维护,故不太推荐此种方案。

(五)旁路由的某些功能无法使用

各种组件内部实现大不相同,如果某个组件的代码对网络结构做了强限定(如上文所说的校验 IP 和 MAC 的匹配关系),那旁路由的加入可能就会打破组件预期的网络结构导致其无法运行。这种情况在组件本身不做适配时基本无解,只能尝试下开启 MASQUERADE 等配置,看看多加一层 NAT 后的网络拓扑是否能符合组件要求,但代价同样是会牺牲性能。

(六)是否应该关闭旁路由防火墙的SYN-flood防御

SYN-flood 是一种常见的攻击方式,SYN 指 TCP 建连三次握手中的第一步报文,flood 指大量发起该步骤的报文。由于 TCP 的实现原理要求服务端接收到 SYN 报文后回复客户端 SYN+ACK 报文表明请求被接受,并在一段时间内等待客户端回复最终的 ACK 报文,那么大量的 SYN 报文就会导致服务端出现大量等待最终资源耗尽挂掉,而攻击者并不需要真的完成建连,只要持续发送 ACK 包即可。

那路由器的防火墙又是如何作防御的呢?以 OpenWrt 为例,虽然 OpenWrt 的防火墙配置已经迁移到了 fw3 ,但翻看代码历史我们就会看到当时 OpenWrt 直接基于 iptables 的早期实现:

1
2
3
4
$IPTABLES -N syn_flood
$IPTABLES -A syn_flood -p tcp --syn -m limit --limit $rate/second --limit-burst $burst -j RETURN
$IPTABLES -A syn_flood -j DROP
$IPTABLES -A INPUT -p tcp --syn -j syn_flood

即借助 iptables 的 SYN 限流能力进行防御,同时此配置位于 default 配置中,会在 NAT 等具体网络操作之前执行。

对应到主路由,wan 口接收到攻击流量后便会进行限流,攻击者无法直接向后攻击到内网主机,那么同样为内网主机的旁路由自然理论上也就不会被攻击到。

但对于旁路由来说,理论上网络中的所有上行流量都会通过它来转发,那当流量超过防火墙的限流阈值时便会触发拦截,进而在终端上表现为网络时断时续。所以在主路由已开启 SYN-flood 防御的情况下,旁路由关闭该配置可以避免出现可能的网络不稳定问题。

旁路由与IPv6

IPv6 在家用网络中通常默认是没有 NAT 转换流程的,同时其动态地址配置方案比 IPv4 要复杂得多,比如 SLAAC 和之前的 DHCP 可以说完全不是一套机制,而 DHCPv6 又分有状态和无状态两类。而前面提到,我们实现旁路由网络拓扑的过程,其实就是在指定一个具备透明代理功能的网关的过程,但 SLAAC/DHCPv6 都没有提供网关下发能力,终端设备总是会以其所交互的主机作为网关,同时大多也不支持直接修改网关。此外,运营商不支持 DHCPv6-PD 、IPv6 子网限定范围等情况,都使得旁路由支持 IPv6 非常困难,在不同场景、不同网络下要面临不同的配置,甚至无方案可配置。

网上比较流行的旁路由 IPv6 实现是个曲线救国的折中方案,即先开启主路由的内网的 IPv6 地址分配进而让旁路由获得内网 IPv6 地址,随后再通过在旁路由开启一个 DHCPv6-Client 的方式获取到公网的 IPv6 地址,这样便可以将主路由的 DHCPv6 下发的 DNSv6 配置为旁路由的 IPv6 地址。此时除了 DNSv6 的解析是在旁路由进行,其他流程仍按原链路直连。而在需要分流的场景中,OpenWrt 的相关组件可以选择在解析域名时放过不需要处理的 IPv6 流量让其正常解析出 AAAA 记录,而对域名名单中的流量强制解析为 A 记录以继续走 IPv4 协议,从而实现和此前的类似的旁路由功能。

当然,这种解析实际上依赖于组件的能力。如果组件并不支持,那通过各种方式强制定义 IPv6 的路由表保证相关 IPv6 流量必然经过旁路由也是一种解决方案。不过无论哪种实现,由于不借助网关配置,其实都已经和本文的旁路由不相关了。

此外,还有种方案是通过 radvd 等支持配置路由单播和优先级的工具,用更高的优先级来指定终端的 IPv6 路由(可以简单理解为 IPv4 的网关),这样就替代了 IPv4 下手动配置或 DHCP 下发网关对应的功能,完美满足本文所说的旁路由网络拓扑,同时对 IPv6 动态地址配置方案的要求很低,但要求终端设备支持路由优先级配置。

总结

旁路由实际上是运行透明代理功能的网关,有人认为这个概念很民科,但我并不认同,毕竟它只是在通过已有的能力来解决特定场景的问题,和我们写代码、做产品没有本质区别,而「旁路由」这个名词也不过是个约定俗成的叫法而已,不应该被批判。

另一方面,由于旁路由在不同设备、不同网络环境有可能遇到很多奇怪问题,其实对于非专业用户来说付出的时间成本很有可能会远大于直接替换主路由的成本。但生命不息,折腾不止,如果是为了收获折腾的快乐、学习到新的知识,那又有何不可呢?

2021数码消费年终总结

2022-02-02 00:55:45

写在前面

这一年工作有了些许新变量,收入多了些,也有了更多的表达欲望和副业想法,唯一不变的大概就只有个人的后消费主义时期下的持续消费。

和往年不同的是,今年很少会看到感兴趣的东西就下单,更多的则是基于自身或合理或过度的需求调研后买入,所以买的玩具更少了,实用(大概)的东西更多了。我为今年的数码消费打上四星满意度和一星的省钱度。

硬件产品

Macbook Air M1

MBA M1

推荐指数:★★★★★

这是我近几年买过的最满意的苹果设备,虽然是最丐的版本,但在轻编程、码字和上网等中度使用场景性能完全够用,续航、发热方面有无数测评,我只追加一句:他们说的都是真的。而且由于不再烫腿,我使用它在床上躺着看视频的时间甚至超过了我的 iPad Pro ,是「真」全能设备。

在刚购入的时候,如 Golang 、VS Code 等常用开发工具还没有推出支持 M1 的正式版本,而 Final Cut Pro 及 Logic Pro 上的很多插件即使借助 Rosetta 2 也无法正常使用,但到了 2021 年下半年这些问题都基本得到了解决,我想生态的快速适配也是苹果敢于在全部产品线推进 M1 系列芯片的原因之一。

至于缺点,我认为主要是产品定位和用户的期望不一致所导致的,如果你认为它性能强大足以替代 MBP ,那 8G 的内存在重度使用(如多开 JetBrains 全家桶、剪辑大型视频)时会经常遇到内存不足的问题,低素质的屏幕也会让你在视觉相关专业场景下显得捉襟见肘,所以购买前明确好它只是你能在床上、沙发上、咖啡厅里无压力携带把玩的生产力工具才能产生愉悦的使用体验。

戴尔 U2720QM 显示器

U2720QM 显示器

推荐指数:★★★★☆

两年前年少无知的我听信网上 24 英寸左右的 4K 显示器才是最适合 MBP 的话买入了老年机型 P2415Q ,但这台显示器的固件过于古老,以至于想使用 HDMI 2.0 还要做一套仪式感十足的设置。另外 23.8 英寸的尺寸无论显示效果有多好,在看了一整天公司的 27 英寸 4K 显示器后很难不感叹家里这块屏幕实在是太小了。

在 2021 年优化(败家)预算到位之后,我做的第一项升级就是加装一台 27 英寸的 4K 显示器。由于这台显示器不用来打游戏也只接 Mac ,所以整体的要求如下:

  • 4K 60Hz:2022 年的今天, Mac OS 仍然只外接 4K 显示器才最省心,2K 的各类字体发虚问题依然令人头痛。至于 60Hz 则完全是预算限制,不然谁会不喜欢高刷呢
  • DCI-P3 色域效果优秀:对于 Mac OS 来说 P3 色域的指标和显示效果关联性最高,虽然不是设计师,但花了钱自然是要追求尽量高的体验
  • Type-C 连接且支持 65w 以上的充电:由于主要的连接设备是两台 19 款的 16 寸 MBP ,电源线、转换器等多根线带来的八爪鱼连接在日常的插拔中很浪费时间,而如果显示器能通过一根 Type-C 线就既支持信号传输又支持高瓦数充电那岂不美哉
  • 好用的支架旋转功能:上上下下左左右右,用惯了 Dell 支架的我已经很难在办公场景下适应那些不灵活的显示器支架了

其实如果不考虑淘宝的定制显示器,那单单高瓦数 Type-C 供电这一项要求在 5000 以下就只有 U2720 这一个选择了,巧合的是这款显示器也正好能满足其他三项要求,因此这也就是我的最终选择。

入手后发现本代 Dell 的固件已经好用多了,各项功能也和宣传无异,我认为是这个价位当前最值得购入的 4K 显示器。

那么为什么不是 5 星推荐呢,原因就在于 Dell 的祖传做工和品控问题,右下角边框缝隙能插卡也许是调侃,但我手上这台插张纸还是没问题的,另外戴尔的祖传漏光也得以延续,使用中我还发现将 MBP 以 Type-C 接入并休眠后显示器会被随机唤醒退出休眠,导致我不得不每次都要在不使用时手动关闭显示器。这些小问题对我来说都还算能接受,读者们则要好好考虑下了。

英特尔 NUC 猎豹峡谷 NUC11PAHi5

nuc

推荐指数:★★★★★

由于家用设备的用电环境差、民用机器和部件的稳定性一般的问题,我是坚定的 all-in-one 反对者,但此前我却一直将 Home lab 搭建在只有 4 核 2.0 GHz 的白群晖 DS920+ 上,性能羸弱的同时,想跑非 Docker 的服务就要承担影响系统稳定性的风险。思前想后还是决定将 Home lab 相关功能独立出来。

我首先尝试了把早年买来用来打游戏的台式 PC 装 Arch Linux 做工作站,既承担 Home lab 的功能,也作为居家工作时的主力机。但几个月后我就放弃了,一是由于屋内环境的限制台式机离床较近风扇声影响休息,几百瓦的 TDP 想 7*24 小时运行也很不环保(费钱),二是由于家中电子设备过剩,我使用台式机办公的时间少之又少,忙于工作也不想折腾 KDE 或 i3wm。重新审视后我总结出了对于我这样的非 WFH 人士所需要的 Home lab 设备标准:

  • 存在感低:散热噪音低、光污染少保证不影响休息,同时机器稳定、开箱即用少折腾
  • 功耗低:环保(省钱)
  • CPU 性能中规中矩:不要求有线程撕裂者或 Intel K 系列 CPU 的超频能力,但也要明显强于赛扬类芯片的性能,这样编译的时候才能少喝咖啡
  • x86 架构:当前 x86 的开发生态仍然是最舒心的,对于 Mac 用户来说,x86 的 Home lab 也可以与 M1 系列芯片互补,解决现世代 Mac 运行 Docker 体验差的问题的同时避免再开虚拟机和 Rosetta 2
  • 体积小、重量轻:DS920+ 的体积在搬家或打扫的时候刚刚好,如果一定要用机箱健身我才会考虑 ATX 系列

这样的要求下,一票主要用途是 NAS 和软路由的一类准系统就被直接排除了,而如 Mac mini 等 ARM 架构的小机器也不合适,几经筛选后我将目光转向了 PC 届的「Mac mini」即英特尔 NUC。

玩机界的同学都知道 8 代 NUC 的口碑和可玩性最高,但目前 8 代由于炒作的原因有些溢价,另外电子产品买新不买旧,在没有黑苹果需求且手头不紧时我认为购买最新一代 NUC (当前为 11 代)才是最优解。而 NUC 这种小盒子机箱很难压住 i7 及以上的 CPU ,能标压稳定跑满 i5 就不错了,因此我最后选择了 NUC11PAHi5 这款大众型号。

京东用日常折扣价购入后配上两条三星 DDR4 3200 笔记本内存装上 Debian 跑起来,运行情况完美符合我上面提到的各个要求,而且发热比预想中好得多,至少中等负荷场景比我的 19 款 MBP 安静得多。总的来说推荐给追求稳定、懒得折腾且不打算 all-in-one 的同学买来作为 Home lab 。

微星 MAG274QRF-QD 显示器

MAG274QRF-QD

推荐指数:★★★☆☆

不怕各位看官笑话,长这么大我还没在高刷新率显示器上玩过 PC 端的游戏,进而导致我的 CSGO 水平很菜(不是),在 PC 台式机从 Home lab 场景退役后,我决定购入一款 2K 显示器让它发挥余热来提升我的 FPS 游戏水平。

我的游戏显示器购买标准如下:

  • 要 2K 不要 4K:显卡带不动 4K (钱包也带不动)
  • 144Hz 高刷屏:上面说了,要用高刷屏解决 FPS 水平差的问题(不是)
  • 支持 G-SYNC:N 卡用户体验不到 FreeSync 的好,原生或 G-SYNC 兼容都可以
  • IPS 屏:不介意漏光,TN 的高响应率使用场景太小,VA 的拖影体验过不能忍,综上还是选择最普遍的 IPS 屏
  • 平均水平的色彩表现:显示器的色彩表现和价格是成正比的,只用来游戏的话不做过多追求

几经研究才发现,2021 这个数码中庸年果然名不虚传,此前被戏称为「大金刚」、「小金刚」的显示器大多换汤不换药没什么吸引人的更新。最后还是选择了看似性价比很高的微星 MAG274QRF-QD。

这台 165Hz 2K 高刷显示器使用了 FastIPS 面板,理论上最快可以达到 1ms 的响应速度,支持 G-SYNC COMPATIBLE,附带着也支持 HDR 和 Type-C 充电,可以说从参数上来看是很优秀的水桶机了。

然而实际使用下来除了微星的固件和 Gaming OSD 对显示器配置调整来说非常方便和造型很酷外,发现了很多「小遗憾」:

  • 支持 8 抖 10 ,但由于是 DP 1.2 ,开启的代价是刷新率要降低到 120Hz
  • 支持 165Hz 刷新率,但由于是 HDMI 2.0 ,连 HDMI 线时只能达到 144Hz
  • 使用了量子点技术所以画面很鲜艳,代价就是肉眼看上去偏红,开了 HDR 不调教的话红到眼睛疼
  • 15w Type-C 充电很鸡肋,笔记本充不上,手机用不着
  • (高刷也没能提高我的 CSGO 水平)

总的来说显示器仍然是个一分价钱一分货的领域,2000 多块自然也不能奢求这台纸面参数优秀的显示器用起来有多完美,对于能接受画面稍微偏红且只用 DP 连接的同学可以考虑购入。

欧普照明护眼灯

Light

推荐指数:★★★★☆

买它其实主要是因为屋里的灯不是智能家居,床上关灯玩电子设备太伤视力,睡前下床关灯又是懒人噩耗,而这款灯支持米家体系,可通过米家 APP 远程控制,进而也可以用 iOS 捷径功能将相应的操作转换为语音指令添加到 Siri 中。有了它之后我躺在床上对 HomePod 喊声「台灯关」就可以关灯入睡了。

台灯是国 AA 级的,显色指数:Ra≥90,照明效果确实很适合伏案读书写字,作为屏幕灯的伪平替也颇为合适,此外色温范围是 2700K-5700K ,亮度也支持无极手触调节,使用体验上是款对得起价格的「现代台灯」。

而不是五星推荐的原因则是如下两点:

  • 支架调节范围有限:只能上下调节,无法左右转动,而由于高度也是不可调的,借给显示器的光明就有些许不足了
  • 稳定性堪忧的 Siri 快捷指令:我不太确定这是米家 APP 的问题还是 iOS 的锅,使用中会偶现 Siri 命令无效的问题影响体验(装逼),在米家 APP 中手动操作倒是没出过问题

舒尔SM58动圈麦克风 + AKG-240监听耳机 + Focusrite 2i2声卡 + sE DM1动圈话筒放大器

Podcast toolkit

推荐指数:★★★★☆

原计划 2021 年要做一档播客,结果又犯了「做事前先买装备」的毛病,播客节目暂时流产于和朋友的试录,入门装备倒是集齐了。

购入前做了很多调研,在认清自家室内电器杂音较大不适合使用电容麦克风后,就一步到位入手了舒尔的 SM58 动圈话筒。不同于电容麦的自带声卡,经典的动圈麦还需要支持卡农 XLR 接口的独立声卡或调音台,对于 SM58 来说外接的声卡还需要支持 48V 幻象供电,而这套装备的入门就体现在我只选择了 2 话放版本的三代 Focusrite 2i2 声卡,而不是几千块起步的高级调音台。但选择丐版声卡搭配高级动圈麦的后果就是实测中 2i2 根本推不动 SM58 ,收到的声音非常小,不得已又入了一款颜值超高的 sE DM1 动圈话筒放大器,才达到了预期的收音水平。而装备党本性的暴露则主要体现在我又直接购入了入门级的 AKG-240 监听耳机,耳机音质比较平,好在个人佩戴起来还比较舒适。

现在再看,我仍然建议有制作播客想法的朋友为核心主播购入一些入门级的专业设备来提升音质,毕竟播客的音质是决定节目受欢迎度上限的因素之一,只是选择的时候可以考虑一些更便宜、更好推的动圈话筒,这样也能省下放大器甚至声卡的钱。监听耳机的边际效益较低,在前期则不建议购买。

Beelink GK55 准系统

GK55

推荐指数:★★★☆☆

20 年追着斐讯高性价比的尾巴,我入手了加价后的 T1 和 N1 ,T1 做电视盒子,N1 用来做旁路由。但好景不长,固件使用体验欠佳的 T1 早早就被我换成了 NVIDIA Shield TV ,而 N1 也在我使用了半年后出现了大概率也是固件问题导致的断网问题需要断电重启才能解决。Shield TV 的良好体验和这些神器的固件堪忧的稳定性让我痛定思痛,决定在路由层面也使用可扩展性和可定制性更高的 x86 架构软路由。

不巧的是那时正是 21 年年初,如果想买 J4125 的软路由就要承受较高的溢价而且没货!多番比对后我偶然看到了一款溢价较小的主打办公影院的准系统——Beelink GK55。

作为当时最新的赛扬处理器,J4125 的性能对前几代实现了碾压,到手后装上 ESXi 6.7 ,新建 OpenWrt 的同时还能再虚拟化个 Windows Server 2019 供特殊场景使用,使用体验非常丝滑,上网冲浪的速度更快了!

功耗层面没有具体测量但根据电费来看感知不强,风扇的声音不贴近几乎听不到,如果一段时间后偶现风扇起飞的情况,那有可能是主动散热结构的风扇在这个小盒子里积灰严重,清灰即可。

这款准系统还在售,我却不太推荐大家购买了,原因如下:

  • 只有两个网口:我是做好了当 AP 的网口也不够用时加交换机的准备才买入的,如果你没有这个打算那还是选择多网口的主机会更可持续发展些
  • 直通困难:同样由于只有两个网口,懒得折腾且菜的我没能在 ESXi 中实现网卡直通,虽然以 J4125 的性能来说不进行网卡直通应该也没多少实际影响,但完美主义者是无法接受这样的结局的!
  • 螃蟹网卡:网卡是瑞昱 RTL8168 ,螃蟹网卡的问题大家应该也有所耳闻,这也是我在购买时就觉得很遗憾的一项纸面参数
  • J4125 已不是最强:N5100 在参数上全面碾压了前代各个产品,买新不买旧!

Nintendo Switch Pro 手柄

Switch Pro 手柄

推荐指数:★★★★★

我手上的 NS 是玩了两年的港版初代,此前已经出现了轻微的摇杆漂移问题,本着一样东西不买两次的原则,我没有选择更换 Joy Con 而是直接购入 Switch Pro 手柄。这只手柄没什么可挑剔的,个人感觉手感比我的一代 Xbox 精英手柄还要好一些,但价格却只有精英手柄的一半不到。另外终于能在主机游戏设备上吃到国行红利了,价格优惠+保修很香。

如果要说缺点,那我觉得一代 Xbox 精英手柄的电池设计仍是我最喜欢的,只要电池管够,续航就不再焦虑。

软件产品

软件 推荐指数 总结
Chevereto V3 ★★★★★ 基于 PHP 的经典图床程序,价格合适且为买断制,功能很完备
uPic ★★★★★ 又一款图床客户端,比 PicGo 的 UE 更加完整且支持 iOS 端
iA Writer ★★★☆☆ 目前的主力码字工具,但深度使用后有很多交互层面的不适应,总体而言还是 WYSIWYG 类编辑器更适合大众
DAMA ★★★★★ 八爷出品的良心买断制软件,和熊猫吃短信一样在注重用户数据安全的同时为图片打码提供了超便捷的实现
ServerCat ★★★★★ 八爷出品的良心买断制软件+1,轻度使用管理多台服务器简直不要太方便,UI 和 UE 设计加分

新增服务订阅

服务 推荐指数 总结
秘塔写作猫 ★★★☆☆ 在线文字纠正工具,对中文的支持非常完整,产品很好用但定价有些高,同时仅有在线版本无法对公司的敏感文字内容进行修正,新的一年希望能找到平替
Bear Pro ★★★★☆ 目前用来替代原生 Notes 体验不错,缺点在于虽然支持了 iCloud 同步,但没有目录管理能力,只适合轻度记录无法满足稍复杂的场景
flomo PRO ★★★☆☆ 很好用的轻量笔记工具,但当前的 PRO 订阅无法为把 flomo 当成私密微博、随手记的轻度用户创造更多价值和便捷,只推荐 flomo 的超级重度用户购买
DigitalOcean Spaces ★★★★★ 兼容 S3 的对象存储,物美价廉,不过部分 CDN 域名国内不可用,需要加一层反向代理
腾讯云香港轻量 VPS ★★★★★ 良心云诚不我欺!
搬瓦工 SoftBank VPS ★★★☆☆ 性价比很低且仅适用于我这种联通用户

服务订阅续费

服务 推荐指数 总结
Apple Music 美区 ★★★★☆ 曲库依然齐全,语言转换依然混乱,推荐依然鸡肋
Netflix ★★★★☆ 烂片烂剧越来越多,纠正了我集中注意力刷剧的毛病,再者就是价格是越来越高了
Pocket Casts Plus ★★★☆☆ 目前用来听播客的主力 APP ,Plus 订阅除了可以跨端同步进度,其他功能存在感很低
iCloud 200GB ★★★★★ 苹果设备越来越多后才能更加体会到 iCloud 的重要性
Setapp ★★☆☆☆ 曾经很实用的软件订阅集合,然而现在除了 Paste 和 Bartender ,其他软件大多用起来味同嚼蜡。推荐在第三方购买折扣码,除了便宜还比官方渠道的购买支持更多设备

总结

愈发感觉这几年可能是个可大可小的历史转折期,在这样的时期应该做的不是消费,而是「少花钱,多看书」。