<?xml version="1.0"?>
<feed xmlns="http://www.w3.org/2005/Atom">
    <id>https://blog.soulter.top</id>
    <title>Soulter&#39;s Blog</title>
    <link href="https://blog.soulter.top" />
    <updated>2026-04-08T00:37:00.000Z</updated>
    <category term="2023" />
    <category term="2024" />
    <category term="保研" />
    <category term="数据库" />
    <category term="计算机系统能力大赛" />
    <category term="总结" />
    <category term="2025" />
    <category term="AdventureX" />
    <category term="毕业旅行" />
    <category term="Agent" />
    <category term="AstrBot" />
    <category term="月记" />
    <category term="2022" />
    <category term="动漫" />
    <category term="紫罗兰永恒花园" />
    <category term="ArchLinux" />
    <category term="Hyprland" />
    <category term="开源项目" />
    <category term="架构" />
    <category term="DIY" />
    <category term="Awtrix" />
    <category term="物联网" />
    <category term="ChatGPT" />
    <category term="QQ机器人" />
    <category term="2026" />
    <category term="游戏" />
    <category term="开源" />
    <category term="数据可视化" />
    <category term="LLM-based Agent" />
    <category term="音频编码" />
    <category term="日本" />
    <entry>
        <id>https://blog.soulter.top/posts/edge-server-tunnel.html</id>
        <title>使用 Frp、Cloudflare 和 Caddy 轻松让你的边缘设备上云</title>
        <link rel="alternate" href="https://blog.soulter.top/posts/edge-server-tunnel.html"/>
        <content type="html">&lt;p&gt;看到这个标题是否在想：这不是轻轻松松吗？边缘设备通过 frp 内网穿透到一个公网服务器，公网服务器上的 Caddy 反代一下穿透过来的流量，再在 Cloudflare（或任何 DNS 服务商）上配一下 DNS 解析不就行了？&lt;/p&gt;
&lt;p&gt;确实是这样 🤓，但是这是一个固定的工作流，如果边缘设备要同时起很多 HTTP 服务，重复的工作就很麻烦。于是我开发了一个 &lt;a href=&#34;https://github.com/Soulter/orion&#34;&gt;Orion&lt;/a&gt; 工具，可以一键完成上面的任务。预期效果是：&lt;/p&gt;
&lt;p&gt;在没有公网 IP 的边缘设备上运行下面脚本：&lt;/p&gt;
&lt;figure class=&#34;highlight bash&#34;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&#34;gutter&#34;&gt;&lt;pre&gt;&lt;span class=&#34;line&#34;&gt;1&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&#34;code&#34;&gt;&lt;pre&gt;&lt;span class=&#34;line&#34;&gt;orion serve -n my_service -p 8000 -- ./your_service&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/figure&gt;
&lt;p&gt;运行之后，即可在 &lt;code&gt;https://my_service.example.com&lt;/code&gt; 上访问到这个服务。&lt;/p&gt;
&lt;p&gt;也可以在边缘设备上查看已经运行的服务以及健康情况：&lt;/p&gt;
&lt;figure class=&#34;highlight bash&#34;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&#34;gutter&#34;&gt;&lt;pre&gt;&lt;span class=&#34;line&#34;&gt;1&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;2&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;3&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;4&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;5&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;6&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;7&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&#34;code&#34;&gt;&lt;pre&gt;&lt;span class=&#34;line&#34;&gt;soulter@ubuntu:~/orion-linux-arm64-v0.0.2$ ./bin/orion list&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;PROCESS STATUS  PID     CONFIG  LOG&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;frpc    running 7471    /home/soulter/.orion/frpc.toml  /home/soulter/.orion/logs/frpc.log&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;frps    not-configured  -       -       -&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;NAME      STATUS   FRPS  PUBLIC  PORT   DOMAIN                     CONFIG&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;my_service  running  ok    ok      38399  my_service.example.com  /home/soulter/.orion/frpc.toml&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/figure&gt;
&lt;h2 id=&#34;过程&#34;&gt;&lt;a class=&#34;markdownIt-Anchor&#34; href=&#34;#过程&#34;&gt;&lt;/a&gt; 过程&lt;/h2&gt;
&lt;figure class=&#34;highlight text&#34;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&#34;gutter&#34;&gt;&lt;pre&gt;&lt;span class=&#34;line&#34;&gt;1&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&#34;code&#34;&gt;&lt;pre&gt;&lt;span class=&#34;line&#34;&gt;Browser -&amp;gt; Cloudflare DNS -&amp;gt; Caddy -&amp;gt; frps(vhostHTTPPort) -&amp;gt; frpc -&amp;gt; local service&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/figure&gt;
&lt;h3 id=&#34;frp&#34;&gt;&lt;a class=&#34;markdownIt-Anchor&#34; href=&#34;#frp&#34;&gt;&lt;/a&gt; FRP&lt;/h3&gt;
&lt;p&gt;&lt;a href=&#34;https://github.com/fatedier/frp&#34;&gt;frp&lt;/a&gt; 支持 HTTP 模式，可以在 frp 客户端中用 &lt;code&gt;type = &amp;quot;http&amp;quot;&lt;/code&gt; 注册一个代理并指定 &lt;code&gt;customDomains&lt;/code&gt;，frp 服务端就可以通过 HTTP 的 &lt;code&gt;Host&lt;/code&gt; 头把请求路由到对应的边缘服务。&lt;/p&gt;
&lt;p&gt;frp 客户端的代理配置大概长这样：&lt;/p&gt;
&lt;figure class=&#34;highlight toml&#34;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&#34;gutter&#34;&gt;&lt;pre&gt;&lt;span class=&#34;line&#34;&gt;1&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;2&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;3&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;4&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;5&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;6&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&#34;code&#34;&gt;&lt;pre&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;section&#34;&gt;[[proxies]]&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;attr&#34;&gt;name&lt;/span&gt; = &lt;span class=&#34;string&#34;&gt;&amp;quot;jetson-orin-nano-llamacpp&amp;quot;&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;attr&#34;&gt;type&lt;/span&gt; = &lt;span class=&#34;string&#34;&gt;&amp;quot;http&amp;quot;&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;attr&#34;&gt;localIP&lt;/span&gt; = &lt;span class=&#34;string&#34;&gt;&amp;quot;127.0.0.1&amp;quot;&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;attr&#34;&gt;localPort&lt;/span&gt; = &lt;span class=&#34;number&#34;&gt;38399&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;attr&#34;&gt;customDomains&lt;/span&gt; = [&lt;span class=&#34;string&#34;&gt;&amp;quot;llamacpp.example.com&amp;quot;&lt;/span&gt;]&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/figure&gt;
&lt;p&gt;需要在 frp 服务端中配置 &lt;code&gt;vhostHTTPPort&lt;/code&gt;，这个参数是专门用来接收 HTTP 请求的监听端口。&lt;/p&gt;
&lt;h3 id=&#34;dns&#34;&gt;&lt;a class=&#34;markdownIt-Anchor&#34; href=&#34;#dns&#34;&gt;&lt;/a&gt; DNS&lt;/h3&gt;
&lt;p&gt;在 Cloudflare 等 DNS 解析服务商中配置&lt;code&gt;通配符域名&lt;/code&gt;，比如 &lt;code&gt;*.example.com&lt;/code&gt;，并解析到 frp 服务器的机器公网 IP。这样之后新增新的子域名时就不需要重新配置 DNS 了。&lt;/p&gt;
&lt;h3 id=&#34;web-server&#34;&gt;&lt;a class=&#34;markdownIt-Anchor&#34; href=&#34;#web-server&#34;&gt;&lt;/a&gt; Web Server&lt;/h3&gt;
&lt;p&gt;如果只有 &lt;code&gt;frp&lt;/code&gt;，其实也可以直接把 &lt;code&gt;vhostHTTPPort&lt;/code&gt; 暴露出去，设置 &lt;code&gt;vhostHTTPPort=80&lt;/code&gt; 即可。使用 Web Server 会更加优雅一些。这里我使用了 &lt;code&gt;Caddy&lt;/code&gt;，它是一个更好用的类似 &lt;code&gt;nginx&lt;/code&gt;, &lt;code&gt;apache&lt;/code&gt; 的 Web Server，它的配置文件非常现代化，不像 &lt;code&gt;nginx.conf&lt;/code&gt; 那样复杂，并且最大的优点是可以可以自动申请并续期 Let’s Encrypt 通配符证书。&lt;/p&gt;
&lt;p&gt;因为通配符证书需要 DNS Challenge，所以这里我用了 Caddy 的 Cloudflare DNS 能力。配置大概是这样：&lt;/p&gt;
&lt;figure class=&#34;highlight plaintext&#34;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&#34;gutter&#34;&gt;&lt;pre&gt;&lt;span class=&#34;line&#34;&gt;1&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;2&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;3&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;4&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;5&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;6&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;7&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;8&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;9&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;10&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;11&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&#34;code&#34;&gt;&lt;pre&gt;&lt;span class=&#34;line&#34;&gt;&amp;#123;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;    acme_dns cloudflare &amp;lt;cloudflare_api_token&amp;gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;&amp;#125;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;*.edge.soulter.top &amp;#123;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;    tls &amp;#123;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;        dns cloudflare &amp;lt;cloudflare_api_token&amp;gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;    &amp;#125;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;    reverse_proxy 127.0.0.1:&amp;lt;vhostHTTPPort&amp;gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;&amp;#125;&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/figure&gt;
&lt;h2 id=&#34;orion&#34;&gt;&lt;a class=&#34;markdownIt-Anchor&#34; href=&#34;#orion&#34;&gt;&lt;/a&gt; Orion&lt;/h2&gt;
&lt;p&gt;Orion 其实就是一个很简单的 frp wrapper，在配置好 Caddy、Cloudflare 之后，使用 Orion 可以一键部署边缘服务，类似：&lt;/p&gt;
&lt;figure class=&#34;highlight bash&#34;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&#34;gutter&#34;&gt;&lt;pre&gt;&lt;span class=&#34;line&#34;&gt;1&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;2&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&#34;code&#34;&gt;&lt;pre&gt;&lt;span class=&#34;line&#34;&gt;orion up -n my_service -p 8000 &lt;span class=&#34;comment&#34;&gt;# 后台运行模式&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;orion serve -n my_service -p 8000 -- ./your_service &lt;span class=&#34;comment&#34;&gt;# 前台运行模式，会自动带起服务进程，Ctrl + C 之后服务进程和 frp 同时结束和清除&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/figure&gt;
&lt;p&gt;然后就能直接访问：&lt;/p&gt;
&lt;figure class=&#34;highlight text&#34;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&#34;gutter&#34;&gt;&lt;pre&gt;&lt;span class=&#34;line&#34;&gt;1&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&#34;code&#34;&gt;&lt;pre&gt;&lt;span class=&#34;line&#34;&gt;https://my-service.example.com&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/figure&gt;
&lt;p&gt;Orion 默认使用的 &lt;code&gt;vhostHTTPPort&lt;/code&gt; 是 38399，因此 Caddy 那里的 reverse_proxy 的端口直接写 38399 即可。&lt;/p&gt;
&lt;p&gt;开源地址：&lt;a href=&#34;https://github.com/Soulter/orion&#34;&gt;https://github.com/Soulter/orion&lt;/a&gt;&lt;/p&gt;
</content>
        <category term="2026" />
        <updated>2026-04-08T00:37:00.000Z</updated>
    </entry>
    <entry>
        <id>https://blog.soulter.top/posts/2025-yearly-record.html</id>
        <title>2025 年 / 总结</title>
        <link rel="alternate" href="https://blog.soulter.top/posts/2025-yearly-record.html"/>
        <content type="html">&lt;p&gt;转眼间就 26 年了。&lt;/p&gt;
&lt;p&gt;这一年也经历了很多，收获了很多。年初偶然的一个操作改变了我的人生轨迹，众多里程碑相继在今年达成——AstrBot 突破 10k Star、AstrBot 和 xxx 合作、AstrBot 登上 xxx 榜单。这些在以前是我想都不敢想的事情。初中的时候曾在某个群里提到自己的梦想是开发一个 2000 人用户的软件，但是被同学说是在做梦。很难想象，这个目标居然真的实现了。&lt;/p&gt;
&lt;p&gt;但是更多的还是看到了自己的同样很多的不足。&lt;/p&gt;
&lt;p&gt;首先是自己的拖延症越来越严重了，开源之夏的活动因为一直拖着，没有及时审核提案，导致最终超过了最后期限，策划的项目因此作废。以及自己早就说要做的 Memory Layer，甚至是去年 11 月就开始了，结果堆了无数个半成品，到现在过了一年多了仍然处于半成品状态。类似的还有出新视频、Local Agent 等等。&lt;/p&gt;
&lt;p&gt;其次是组织能力的欠缺。AstrBotDevs 组织建立到现在，也有不少成员加入，但在很多关键议题上我仍然显得束手无策：项目管理、任务拆分、节奏推进都做得还不够好，整个组织的凝聚力也还有待加强。&lt;/p&gt;
&lt;p&gt;今年开始读书了，从 &lt;a href=&#34;https://i.stdrc.cc/&#34;&gt;RC&lt;/a&gt; 的书单中找了几本感兴趣的书《枪炮、病菌与钢铁》和《存在主义是一种人道主义》，从戴雨森的播客中也收获了几本有意思的书《穿越平行宇宙》和《智能简史》。除了《存在主义是一种人道主义》对我来说实在晦涩难懂以外，其他的三本书都很好看也很有价值，强烈推荐！&lt;/p&gt;
&lt;p&gt;要说自己一年来最大的认知变化，便是意识到认知水平真的很重要。初中高中，甚至本科前半段，我们一直无意识地认为考试就是天，学校就是天。但这一年经历了很多“刷新认知”的事情之后，我开始看到一个更广阔的世界：世界是开放的、多元的，人生的路径远不止一条。这也给我的研究生阶段定下了基调——我要当一个“差学生”。我不会再把主要精力放在课内评价体系里，我会全心全意去研究自己喜欢的领域，去创造所有自己喜欢的东西。&lt;/p&gt;
&lt;p&gt;人类本身又何尝不是在不断提高认知水平的过程中前进的呢。从最早的地平说，到教会推崇的地心说，再到哥白尼的日心说；从银河系的发现，到本星系群的发现，再到宇宙微波背景辐射的发现。光速的限制导致了“光之墙”出现，也成为我们永远无法逾越的边界，它的另一侧与我们不再具有任何因果联系，由于宇宙空间不断膨胀，从那里发出的光永远都无法到达地球。而后来，人们意识到就连我们的宇宙本身也不是唯一的——在无限暴胀理论中，可能存在着无数个平行宇宙。&lt;/p&gt;
&lt;p&gt;随感随想，没有什么华丽的词藻，也没有 AI 润色，到此为止吧。&lt;/p&gt;
&lt;p&gt;新年快乐！希望新的一年能够坚持读书、坚持自己其他爱好、解决拖延症、提高组织能力。&lt;/p&gt;
</content>
        <category term="总结" />
        <category term="2025" />
        <updated>2025-12-27T01:00:00.000Z</updated>
    </entry>
    <entry>
        <id>https://blog.soulter.top/posts/2025.10-monthly-record.html</id>
        <title>对 Agent 产品形态及 AstrBot 发展方向的一些思考</title>
        <link rel="alternate" href="https://blog.soulter.top/posts/2025.10-monthly-record.html"/>
        <content type="html">&lt;p&gt;今天和某 AI 初创合伙人吃了一顿晚餐，我们畅谈了各自对 AI Agent 未来交互形态的思考、目前各种类型的 AI Agent 产品以及我创业的一些想法。我们聊的很开心。在这里我将一些我的思考和见解写下来，希望对未来的自己有帮助。&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;目前多数 General Agent 产品（比如 Manus、OK Computer、Minimax Agent）偏向于单轮对话（或者轮数较低的多轮对话），并没有考虑长周期的用户需求。这类产品目前尚不成熟，完成一个复杂任务的成功率不可观，但随着模型 Agentic 性能的提升（这也是今年所有模型厂商的一个发展趋势），将会以更少的重试次数和 token 数，逐渐完成更复杂的任务。最终确实能够提高生产力，但依旧只是中间形态。某些 General Agent 产品依赖于中心化的基础设施，并且开销巨大，难以支撑大规模的用户量级。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;AI 陪伴类产品（比如 Google 的 Character AI、酒馆、Minimax 的星野 AI、猫箱、筑梦岛等；硬件产品或公司如小智、芙崽、萌友智能、跃然创新等）的难点在于算法的稳定性和效果需要长周期的检验，并且效果难以量化，强依赖用户反馈。并且算法难度、多样性相较于 General Agent 要更五花八门，比如 Memory、情绪模型、多模态交互等。此外，用户的陪伴需求是强因人而异的，千人千面。有人需要解决生理需求、有人需要解决心理需求，技术难以完全覆盖所有人的预期。此外，用户的情绪需求是会变化的，可能今天用户需要一个 AI “女朋友”，但是第二天现实生活中有女生来找用户聊天，用户获得了满足感，就大概率可能放弃继续使用。也源源不断有 AI 陪伴应用宣布死亡，比如阶跃的冒泡鸭、Soul 的异世界回响等。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;产品存在的理由是能够给用户创造价值，好的产品能够持续给用户创造价值。对于 Agent 产品，这里的价值我划分为两类——生产力价值和情绪价值。而 Agent 的终极形态，从我个人而言，应该是同时提供生产力价值和情绪价值的。General 的工具调用能力、Memory 和情绪都需要具备。此外，它应该具有主动性，主动了解用户背景，比如学习用户的使用习惯、总结用户的画像，并适时给予用户建议；主动学习外部信息，并在新的一轮与用户的交互中带来更新的知识。抽象出来就是，以最优的方式持续获得最优质和尽可能多的用户 Context。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;AstrBot 目前已经具备可观的用户基数，其主要的交互模型是接入各个消息平台，通过消息平台的接口来交互。然而，交互只是一个产生与用户 Context 的一个壳，消息平台是以图文为主的交互模式，得到的用户 Context 终究有限，如果要在 AstrBot 上落地 Agent 终极形态，未来 AstrBot 就应该将这个壳和 Agent Core 隔离开，并实现能够产生更多用户 Context 的壳，比如 GUI，也就是 Local Agent；再比如硬件，又或者是 GUI + 硬件。目前 GUI 结合的 Agent 交互形态除了 ChatBot 还有模拟 OS、画布、桌宠、Browser 等等，大家正在积极探索最合适的交互形态。此外，AstrBot 需要开发一个命令式的 SDK，并且这个 SDK 应该实现构建到部署再到反馈的闭环。作为开源社区，AstrBot 社区如果要可持续发展，就不可避免地需要引入商业模式，开源版 + 商业版结合是可行的也是经过开源社区和市场考验的一条出路。具体的商业模式可以是提供 SaaS 一样的服务。虽然这也不可避免地引入开源贡献者的利益分配问题，但这是一件很容易解决的事情。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;创业最重要的是找对合伙人。不需要合格，但一定要合适。技能互补很重要，一个人的成功概率太低。&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
</content>
        <category term="Agent" />
        <category term="AstrBot" />
        <updated>2025-10-12T23:08:00.000Z</updated>
    </entry>
    <entry>
        <id>https://blog.soulter.top/posts/2025.09-monthly-record.html</id>
        <title>黑客松、毕业旅行</title>
        <link rel="alternate" href="https://blog.soulter.top/posts/2025.09-monthly-record.html"/>
        <content type="html">&lt;p&gt;赶着暑假的尾巴，在开学前一天终于把策划已久的 AstrBot v4.0.0 发布了，闲着没事来补充一下博客。&lt;/p&gt;
&lt;p&gt;我其实很喜欢夏天。虽然它热得让人汗流浃背，但我很喜欢夏天的热闹的感觉。蝉声在树梢里吵闹地唱着，街头行人的说话声、吆喝声混杂在一起。我喜欢把这种热闹作为白噪音，然后沉浸在自己的世界。&lt;/p&gt;
&lt;p&gt;对于学生来说，暑假可是头等大事。暑假总是这样，无论是外出的旅行，还是安静地待在房间，每个人都能抓住一些记忆。有人每天刷手机，也许觉得平淡，但总会有一两刻，让人觉得“啊，这就是暑假的味道”。&lt;/p&gt;
&lt;p&gt;今年的暑假，我也尝试了很多新鲜的东西。&lt;/p&gt;
&lt;h3 id=&#34;adventurex&#34;&gt;&lt;a class=&#34;markdownIt-Anchor&#34; href=&#34;#adventurex&#34;&gt;&lt;/a&gt; AdventureX！&lt;/h3&gt;
&lt;p&gt;在七月底，我第一次参加了黑客松，这也是被宣传为“中国有史以来最大的黑客松”的 AdventureX，我有幸成为八百个参赛者之一。以前听到黑客松还以为是只有黑客才能参加的竞赛，互相比拼谁能更快黑掉一个系统，来参加这次活动之后才知道不是这样。大致来说，AdventureX 主办方包下了整个湖畔大学（你没听错，就是马云的那个湖畔大学x）作为活动场地，然后选手们需要在场地内参加活动，在给定时间内（72 小时）和队员一起从零开始打造任何项目出来，最后进行展示。来参加的第一感受就是大家都非常的外向，见到人就上去搭话的那种。&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;../source/images/2025.09-monthly-record/IMG_1446.jpeg&#34; alt=&#34;&#34; /&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;../source/images/2025.09-monthly-record/IMG_1450%E5%A4%A7.jpeg&#34; alt=&#34;&#34; /&gt;&lt;/p&gt;
&lt;p&gt;凭借过往的编程经验，我对这次比赛还是很有信心的。虽然组队上限 5 人，但我们只组了两个人的队。我们从构思 Idea 开始，在 72 小时之内完成了一款软件，最终也获得了腾讯云 CloudStudio 赛道的三等奖和 Dify 赛道的优胜奖。&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;../source/images/2025.09-monthly-record/IMG_1602%E5%A4%A7.jpeg&#34; alt=&#34;&#34; /&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;../source/images/2025.09-monthly-record/IMG_1604%E5%A4%A7.jpeg&#34; alt=&#34;&#34; /&gt;&lt;/p&gt;
&lt;p&gt;我们是 164 展位的 Memora！&lt;/p&gt;
&lt;p&gt;值得吐槽的是，AdventureX 也爆出了很多瓜出来，比如著名的找 💩 任务，以及朱某个人的一些事。此外，我怎么感觉 X 上半个简中区的都来了…&lt;/p&gt;
&lt;p&gt;虽然主办方迷之操作很多…但是提供的平台还是不错的，整体下来体验还是很好的。&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;../source/images/2025.09-monthly-record/IMG_1612%E5%A4%A7.jpeg&#34; alt=&#34;&#34; /&gt;&lt;/p&gt;
&lt;h3 id=&#34;毕业旅行&#34;&gt;&lt;a class=&#34;markdownIt-Anchor&#34; href=&#34;#毕业旅行&#34;&gt;&lt;/a&gt; 毕业旅行！&lt;/h3&gt;
&lt;p&gt;暑假我又去了一次日本。这次，是和我的大学的一些朋友一起，同行加上我一共上 4 人。我们去了关西的大阪、京都、宇治、奈良和神户，以及关东的东京、横滨和镰仓。&lt;/p&gt;
&lt;p&gt;整体体验下来，最难忘、最新奇的体验就是奈良的鹿 🦌、东京的晴空塔 🗼 和镰仓的海 🌊 了。&lt;/p&gt;
&lt;h4 id=&#34;&#34;&gt;&lt;a class=&#34;markdownIt-Anchor&#34; href=&#34;#&#34;&gt;&lt;/a&gt; 🦌&lt;/h4&gt;
&lt;p&gt;我们把京都、宇治、奈良的行程压缩到了一天，到了奈良已经是傍晚时分了。和在京都和大阪见多了的寺庙不同，奈良满街的鹿打消了我们的疲惫。之前在鹿乃子上让我印象深刻的鹿饼终于能亲眼看到了。我们一人买了一个鹿饼，走出店的一刻，一群鹿直接向我们靠近。好在店长出手帮我们解困。&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;../source/images/2025.09-monthly-record/IMG_2074%E5%A4%A7.jpeg&#34; alt=&#34;&#34; /&gt;&lt;/p&gt;
&lt;p&gt;&lt;video controls src=&#34;../source/images/2025.09-monthly-record/IMG_2085.mp4&#34; title=&#34;Title&#34;&gt;&lt;/video&gt;&lt;/p&gt;
&lt;p&gt;&lt;video controls src=&#34;../source/images/2025.09-monthly-record/IMG_2083.mp4&#34; title=&#34;Title&#34;&gt;&lt;/video&gt;&lt;/p&gt;
&lt;h4 id=&#34;-2&#34;&gt;&lt;a class=&#34;markdownIt-Anchor&#34; href=&#34;#-2&#34;&gt;&lt;/a&gt; 🗼&lt;/h4&gt;
&lt;p&gt;和正好在东京上学的群友线下见面，然后我们 5 个人一同去了东京国立博物馆、晴空塔等地方玩。东京国立博物馆就没什么好说的了，含“中华”量极高😢，甚至专门有中文翻译。晴空塔我们是晚上去的，东京的夜景很美呢。&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;../source/images/2025.09-monthly-record/IMG_2302.JPG&#34; alt=&#34;&#34; /&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;../source/images/2025.09-monthly-record/IMG_2303.jpeg&#34; alt=&#34;&#34; /&gt;&lt;/p&gt;
&lt;p&gt;当然了，如果一个人来这个地方肯定不好玩了，更重要的是和朋友们一同度过的时光——充実な一日。&lt;/p&gt;
&lt;h4 id=&#34;-3&#34;&gt;&lt;a class=&#34;markdownIt-Anchor&#34; href=&#34;#-3&#34;&gt;&lt;/a&gt; 🌊&lt;/h4&gt;
&lt;p&gt;我们按照小红书上的攻略，从东京出发，先是到了横滨。我们逛了横滨的中华街和横滨公园。值得吐槽的是，中华街全是卖小笼包的。横滨公园很美，这是一个海滨公园，虽然很晒，但是海风又带给了我们一丝凉意。沿岸停靠着上个世纪二战时的舰船——冰川丸。这艘舰船原本是客轮，在二战时期被改建成了医疗船，被鱼雷三次击中未沉。&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;../source/images/2025.09-monthly-record/IMG_2326.jpeg&#34; alt=&#34;&#34; /&gt;&lt;/p&gt;
&lt;p&gt;目前它早已退役，改建成了博物馆。听朋友说里面能学到很多东西，我后悔没有进去参观~&lt;/p&gt;
&lt;p&gt;从横滨出发，我们到了藤泽市，我们要从藤泽市乘坐“江之电”到镰仓的江之岛。&lt;/p&gt;
&lt;p&gt;到了江之岛，一望无际的大海出现在眼前。&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;../source/images/2025.09-monthly-record/IMG_2342.jpeg&#34; alt=&#34;&#34; /&gt;&lt;/p&gt;
&lt;p&gt;海平面和天空的分界线如此整齐，清晰可见，像是游戏中的世界边缘一样，平滑得几乎不真实。&lt;/p&gt;
&lt;p&gt;这就是太平洋，世界上最大的海洋，宽广得让人感到渺小。我们沿着桥往江之岛进发，海风带着淡淡的咸味和潮湿的气息吹来，偶尔，有几只海鸥掠过头顶，划出低低的鸣叫声。&lt;/p&gt;
&lt;p&gt;爬上江之岛的山，我们一览了全岛的风貌。&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;../source/images/2025.09-monthly-record/IMG_2570.JPG&#34; alt=&#34;&#34; /&gt;&lt;/p&gt;
&lt;p&gt;随后，我们离开江之岛，沿着海岸一路行走，穿过了腰越海滩，来到了镰仓的最后一个目的地，七里滨。&lt;/p&gt;
&lt;p&gt;这是著名动漫《青春猪头少年不会梦到兔女郎学姐》的取景地之一。虽然我没看完这部动漫，但是朋友的介绍还是给我增加了一丝好奇。&lt;/p&gt;
&lt;p&gt;我们沿着滨海公路，走到了七里滨。我们脱下鞋子准备下海玩，我失策了穿的长裤，还是紧身的！但是最后按耐不住激动，还是把裤子脱掉了（反正看不出来是吧…）。&lt;/p&gt;
&lt;p&gt;我们碰巧看到一个老奶奶正在遛狗，我还是第一次见到狗下海，很神奇的一幕…&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;../source/images/2025.09-monthly-record/IMG_2372.jpg&#34; alt=&#34;alt text&#34; /&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;../source/images/2025.09-monthly-record/IMG_2572.JPG&#34; alt=&#34;alt text&#34; /&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;../source/images/2025.09-monthly-record/IMG_2573.JPG&#34; alt=&#34;alt text&#34; /&gt;&lt;/p&gt;
&lt;p&gt;&lt;video controls src=&#34;../source/images/2025.09-monthly-record/IMG_2473.mp4&#34; title=&#34;Title&#34;&gt;&lt;/video&gt;&lt;/p&gt;
&lt;p&gt;这里海浪很大，我们玩得不亦乐乎，C 甚至全身都被海浪打湿了，全身上下全是沙子。和大海邂逅了，我在这里度过了最令人难忘的、只有动漫中才能体验到的夏天。&lt;/p&gt;
&lt;p&gt;夕阳把整个七里滨染成金红色，我们的影子拖得很长，交错在沙滩上。脚印一串串被浪潮冲刷，却又很快被新的脚印填上。&lt;/p&gt;
&lt;p&gt;毕业旅行的价值就在我们的笑声中被不断被填充，最后圆满结束。&lt;/p&gt;
&lt;h3 id=&#34;-4&#34;&gt;&lt;a class=&#34;markdownIt-Anchor&#34; href=&#34;#-4&#34;&gt;&lt;/a&gt; ❤&lt;/h3&gt;
&lt;p&gt;和喜欢的人正式建立了亲密的关系 ❤&lt;/p&gt;
</content>
        <category term="2025" />
        <category term="AdventureX" />
        <category term="毕业旅行" />
        <updated>2025-09-08T00:05:00.000Z</updated>
    </entry>
    <entry>
        <id>https://blog.soulter.top/posts/2025.7-monthly-record.html</id>
        <title>写在毕业之后</title>
        <link rel="alternate" href="https://blog.soulter.top/posts/2025.7-monthly-record.html"/>
        <content type="html">&lt;p&gt;是的，我本科毕业了。本来应该上个月毕业，但是由于大四的选修课出分太晚，导致拿到毕业证的时间和其他人比晚了一个月，一直拖到了现在。&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;../images/2025.7-monthly-record/image.png&#34; alt=&#34;迟来的毕业证和学位证&#34; /&gt;&lt;/p&gt;
&lt;p&gt;最终还是结束了大学四年的时光。多么神圣的时光，绝无仅有、不可复得、无数人工作数年后仍在怀念。四年的时间里认识了很多优秀的同学，尤其感谢计算机与通信工程学院的 806 本科生科创实验室给了我极好的环境和平台，从大二开始基本上课余时间都待在这里，可能比在宿舍的时间还长！&lt;/p&gt;
&lt;p&gt;真的很快啊，本来大家还聚在一起吃饭吹水，谈论课业感受、实习经历，随之而来的毕设答辩和毕业典礼之后，一切都改变了，大家不得不转头向前，面对属于自己的人生课题。&lt;/p&gt;
&lt;p&gt;在月之暗面实习到现在已经有五个月整了，最近 Kimi K2 模型开源引起了不小的轰动，Nature 将其称为「Another DeepSeek Moment」，LMArena、SWEBench 等头部 Benchmark 纷纷夸赞 Kimi K2 的优异表现，Kimi K2 在 OpenRouter 上使用量超越同期老马发布的 Grok 4… 作为主要参与开发工作的实习生，我为 Kimi K2 只提供了小到不能再小的微薄之力，能待在颇具实力、财力又优雅的一家公司实属荣幸。&lt;/p&gt;
&lt;p&gt;软件工程的本质在于和人沟通。一个大型工程的研发和迭代不可能只由一个人完成——这点我深有体会。在一定的人数范围内（通常 &amp;lt;= 5），参与人员的增加会带来成倍的效率 Scale，而当多个技术水平不同的开发者聚集到一起负责一个项目时，如何分工协作就是最大的难题之一——至少对我来说是这样的（在高强度参与开源工作、参与实习、各种和投资人线上线下讨论之后，我越来越发现自己不会和人打交道。社交压力，难以逾越的大山！）。AstrBot 我一个人单机开发了两年有余，突然迎来众多热忱的开发者。由于我们分散在各地，因此沟通就是最大难题。很多时候当一个人提出一个想法寻求和其他人讨论，然而由于现实原因比如工作、学习导致一些人没办法及时参与，慢慢地就会遗忘掉相关的上下文导致没办法继续推进。&lt;/p&gt;
&lt;p&gt;此外，我深有体会的一点是时间真的不够用了。太不够用了！由于实习导致我的其他时间被大幅度压缩，只能抽空参与项目的维护、线下参与讨论和活动、学习 NLP 知识等等。各种其他事情（比如 AstrBot 的 v3.6 更新以及 1w Star 社区活动策划）因此被一直推迟——很多时候不得不排优先级，而这需要强大的时间管理能力，我在这点上还是欠缺的。看着与日俱增的 Issues 和 PR、以及各种挤压的 Todos，愈发焦虑但也无能为力。&lt;/p&gt;
&lt;p&gt;好在除去社交压力之外，其他我正在做的事情都是我所感兴趣的，我想这是我能坚持下来的最大原因吧。所以大多数时候我也不认为我取得如今的成果是“运气好”，又有多少人会在上班的时候写代码，上班前、下班后还要写代码，周末还要写代码呢？（当然了，不只是写代码…在这里只是一个代词）。&lt;/p&gt;
&lt;p&gt;总而言之，自己还是太菜了。未来要喜欢的事情上更加发力，要变得更强。&lt;/p&gt;
&lt;p&gt;想分享的还有很多，受于时间，只能写到这里。&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;../images/2025.7-monthly-record/image-1.png&#34; alt=&#34;求实鼎新&#34; /&gt;&lt;/p&gt;
</content>
        <category term="2025" />
        <category term="月记" />
        <updated>2025-07-19T12:19:42.000Z</updated>
    </entry>
    <entry>
        <id>https://blog.soulter.top/posts/translation-how-to-think-about-agent-frameworks.html</id>
        <title>[译] 如何看待 Agent 框架</title>
        <link rel="alternate" href="https://blog.soulter.top/posts/translation-how-to-think-about-agent-frameworks.html"/>
        <content type="html">&lt;blockquote&gt;
&lt;p&gt;原文: &lt;a href=&#34;https://blog.langchain.dev/how-to-think-about-agent-frameworks/&#34;&gt;https://blog.langchain.dev/how-to-think-about-agent-frameworks/&lt;/a&gt;&lt;br /&gt;
原著者: &lt;a href=&#34;https://blog.langchain.dev/&#34;&gt;LangChain&lt;/a&gt;&lt;br /&gt;
发布时间: 2025-04-20&lt;br /&gt;
翻译时间: 2025-05-06&lt;br /&gt;
译者: &lt;a href=&#34;https://blog.soulter.top&#34;&gt;Soulter&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr /&gt;
&lt;p&gt;TL;DR:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;构建可靠的 Agentic 系统最难的部分是确保 LLM 在每一步都拥有正确的上下文。这包括控制正确的内容进入 LLM 上下文以及执行正确的步骤去生成相关的内容。&lt;/li&gt;
&lt;li&gt;Agentic 系统同时包括 Workflows 和 Agents 两者（和一切这两者之间的事物）。&lt;/li&gt;
&lt;li&gt;大多数 Agentic 框架既不是声明式也不是命令式的编排框架，而只是一组代理抽象。&lt;/li&gt;
&lt;li&gt;Agent 抽象可以很容易去构建，但是他们通常会造成混淆，并难以确保 LLM 在每一步都有正确的上下文。&lt;/li&gt;
&lt;li&gt;各种不同的 Agent 系统都受益于同样的一组有用的功能，这些功能可以由框架提供也可以自行从头构建。&lt;/li&gt;
&lt;li&gt;LangGraph 被公认为是一个同时包含声明式和命令式的编排框架，封装了一系列的 Agent 抽象。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;OpenAI 最近发布了一份关于构建代理的指南，这其中可能包含了一些误导性的观点，如下图所示：&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;../images/translation-how-to-think-about-agent-frameworks/Jq3vh0D2kSE7oNJGjppIkrpJQSEEWuvbG_RG3L-bUFg=.png&#34; alt=&#34;&#34; /&gt;&lt;/p&gt;
&lt;p&gt;这个呼吁最初让我很生气，但是当我开始去撰写回应时，我意识到：思考一个 Agent 框架出来是很复杂的一件事！可能有 100 个不同的 Agent 框架，并且有大量不同的维度去比较这些 Agent 框架，有时他们会让人觉得混淆。现在网络上也有很多关于不同 Agent 框架的炒作、姿态和噪音，但是却很少有对于 Agent 框架的精确分析和思考。这篇博客就是我们这样做的尝试，它将包含：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;背景
&lt;ol&gt;
&lt;li&gt;Agent 是什么？&lt;/li&gt;
&lt;li&gt;构建 Agent 最难的部分是什么？&lt;/li&gt;
&lt;li&gt;什么是 LangGraph&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;li&gt;Agentic 框架的风格
&lt;ol&gt;
&lt;li&gt;“Agents” vs “Workflows”&lt;/li&gt;
&lt;li&gt;声明式 vs 非声明式&lt;/li&gt;
&lt;li&gt;Agent 抽象&lt;/li&gt;
&lt;li&gt;Multi Agent&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;li&gt;普遍的问题
&lt;ol&gt;
&lt;li&gt;框架的价值是什么？&lt;/li&gt;
&lt;li&gt;当模型的性能变得更好时，Workflows 的生态位会被 Agents 挤占吗？&lt;/li&gt;
&lt;li&gt;OpenAI 的看法有什么错误？&lt;/li&gt;
&lt;li&gt;如何比较所有的 Agent 框架？&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h1 id=&#34;背景&#34;&gt;&lt;a class=&#34;markdownIt-Anchor&#34; href=&#34;#背景&#34;&gt;&lt;/a&gt; 背景&lt;/h1&gt;
&lt;p&gt;一些有用的背景信息，帮助大家理解剩下的博客内容。&lt;/p&gt;
&lt;h2 id=&#34;agent-是什么&#34;&gt;&lt;a class=&#34;markdownIt-Anchor&#34; href=&#34;#agent-是什么&#34;&gt;&lt;/a&gt; Agent 是什么？&lt;/h2&gt;
&lt;p&gt;目前，对于 Agent 没有一致的定义，并且通常通过不同的视角来提供。&lt;/p&gt;
&lt;p&gt;OpenAI 采用更高层次、更具有思想领导力的方式来定义 Agent。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Agent 是一个能够代表你独立完成任务的系统。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;我个人不喜欢这个观点。这是一个模糊的陈述，没有帮助我理解 Agent 到底是什么。这只是一个思想领导力（译者注: thought-leadership 不知道怎么翻译），并不实用。&lt;/p&gt;
&lt;p&gt;对比 Anthropic 的定义：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;“Agent” 可以通过不同方式来定义。一些客户将 Agent 定义为可以在较长时间内独立运行的，能够使用多种工具去完成复杂任务的完全自主系统。而其他一些客户使用特定的术语去描述一个更加规范的实现，比如预定义的工作流。在 Anthropic，我们将这些各种各样的变体归类为 Agent 系统，但是在 Workflows 和 Agents 之间给予了明确的架构区别：&lt;/p&gt;
&lt;p&gt;Workflows 是一个通过预定义的代码路径将大语言模型和各种工具精心策划好的系统。&lt;/p&gt;
&lt;p&gt;Agents，是一个由大语言模型动态自行指导任务进程和工具使用的系统，它们自行控制着如何完成任务。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;我更喜欢 Anthropic 的定义，基于以下几点原因：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;他们的定义更加精确和偏向技术。&lt;/li&gt;
&lt;li&gt;他们也引用了 Agenic 系统的概念，并且将 Workflows 和 Agents 二者都归类为 Agentic 系统的变体。我&lt;strong&gt;尤其喜欢&lt;/strong&gt;这一点。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;几乎所有我们所见到的“Agentic 系统”产品都是“Workflows”和“Agents”的结合。&lt;/p&gt;
&lt;p&gt;在 Anthropic 博客的最后，它们将 Agent 定义为“…典型地只是大语言模型在一个循环中基于环境反馈而使用工具的过程”。&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;../images/translation-how-to-think-about-agent-frameworks/80_vcwbjFiQ5YXBCPguA9oBbAlyo8FgcvbVQHl5ELvM=.png&#34; alt=&#34;&#34; /&gt;&lt;/p&gt;
&lt;p&gt;尽管它们一开始给 Agent 所做的定义过于宏大，但这基本上也是 OpenAI 的意思。&lt;/p&gt;
&lt;p&gt;这些类型的 Agent 可以被参数化为：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;模型的使用&lt;/li&gt;
&lt;li&gt;Instruction(system prompt) 的使用&lt;/li&gt;
&lt;li&gt;工具的使用&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;你在一个循环中调用模型，如果/当模型决定去使用一个工具，你就去运行那个工具，然后取得一些发现/反馈（Observation/feedback）。然后，将这些信息回传给模型。你就这样运行直到模型不再打算去使用任何工具（或者它使用了一个专门用于标识结束循环的工具）。&lt;/p&gt;
&lt;p&gt;OpenAI 和 Anthropic 都认为 Workflows 是一种与 Agents 不同的设计模式。模型使用会导致可控性变差，工作流会更具确定性。这就是一个有用的区别！&lt;/p&gt;
&lt;p&gt;OpenAI 和 Anthropic 都明确地认为你并不总是需要使用 Agents。在很多场景下，Workflows 更加简单、可依赖、便宜、快速和高性能。下面是一个来自 Anthropic 博客的一个很好的引用：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;当使用大语言模型构建应用时，我们推荐找一些可能的最简单的解决方案，只在需要的时候增加复杂性。这可能意味着不一定需要构建一个 Agentic 系统，Agentic 系统通常会牺牲延迟和费用来换取更好的性能，你应该权衡这种交换何时才有意义。&lt;/p&gt;
&lt;p&gt;当需要增加复杂性时，Workflows 对于一些定义良好的任务来说会带来可预测性和一致性，然而对于一些需要大规模灵活性和模型驱动决策的任务来说，Agents 是一个更好的选择。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;OpenAI 说得更简单：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;在致力于构建一个 Agent 之前，请验证你的用力能够清晰地满足这些标准，否则，确定性的解决方案可能就够了。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;在实践上，我们可以看到大部分“Agentic 系统”都是这二者的结合。这就是我为什么讨厌讨论什么东西是一个 Agent，但是更喜欢讨论系统如何具有代理性（agentic）。向伟大的 Andrew Ng 致敬，感谢他有&lt;strong&gt;&lt;a href=&#34;https://x.com/AndrewYNg/status/1801295202788983136&#34;&gt;这样的思考方式&lt;/a&gt;&lt;/strong&gt;：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;与其不得不二元地选择某物到底是不是 Agent，我认为，更有用的思考方式是某个系统到底像 “像 Agent” 到了什么程度。不像 “Agent” 这个名词，“Agentic” 这个形容词让我们去深入思考这些系统，并且将所有这些系统都纳入这个不断发展的运动中。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id=&#34;构建-agent-最难的部分是什么&#34;&gt;&lt;a class=&#34;markdownIt-Anchor&#34; href=&#34;#构建-agent-最难的部分是什么&#34;&gt;&lt;/a&gt; 构建 Agent 最难的部分是什么？&lt;/h2&gt;
&lt;p&gt;我认为大部分人都同意构建 Agent 是有难度的。或者，换句话说，构建一个 Agent 的原型很容易，但是构建一个可依赖的，能够支撑关键业务的应用程序呢？很难。&lt;/p&gt;
&lt;p&gt;最艰难的部分绝对是——让 Agent 系统可依赖。你可以做好一个看起来很好的 Demo 然后发到推特上，这很容易，但是你可以让它变成一个能够支撑关键业务的应用程序吗？不做大量工作是不行的。&lt;/p&gt;
&lt;p&gt;我们在几个月前做了一个关于 Agent 开发者的调查，我们问他们：“你们让 Agent 生产可用的最大的限制是什么？”排在第一的回应是“性能质量”。让这些 Agent 发挥作用仍然很困难。&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;../images/translation-how-to-think-about-agent-frameworks/4WyUihx3yOBv4nAefP7LsWVWMUtgipyOJFUAyXWqXcc=.png&#34; alt=&#34;&#34; /&gt;&lt;/p&gt;
&lt;p&gt;是什么导致了 Agent 经常的糟糕的表现？大语言模型搞砸了。&lt;/p&gt;
&lt;p&gt;为什么大语言模型搞砸了？两点原因：1. 模型不够好 2. 错误的（或者不完整的）上下文被传递给了模型。&lt;/p&gt;
&lt;p&gt;从我们的经验来说，通常是第二点。什么原因导致了这个？&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;不完整或者短的 system 信息。&lt;/li&gt;
&lt;li&gt;模糊的（vague）用户输入&lt;/li&gt;
&lt;li&gt;模型没有使用正确的工具&lt;/li&gt;
&lt;li&gt;糟糕的工具定义&lt;/li&gt;
&lt;li&gt;没有传递正确的上下文给模型&lt;/li&gt;
&lt;li&gt;糟糕的工具响应&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;构建值得信赖的 Agentic 系统最难的部分是确保大语言模型在每一步都拥有正确的上下文。这包括控制正确的内容进入大语言模型上下文以及执行正确的步骤去生成相关的内容。&lt;/p&gt;
&lt;p&gt;当我们讨论 Agent 框架时，记住这一点会很有帮助。&lt;strong&gt;任何使控制传递给大语言模型的内容变得更加困难的框架都只会阻碍你。&lt;/strong&gt;向大语言模型传递正确的上下文已经够难了 - 为什么你要让自己变得更难呢？&lt;/p&gt;
&lt;h2 id=&#34;什么是-langgraph&#34;&gt;&lt;a class=&#34;markdownIt-Anchor&#34; href=&#34;#什么是-langgraph&#34;&gt;&lt;/a&gt; 什么是 LangGraph&lt;/h2&gt;
&lt;p&gt;// 请参考原文&lt;/p&gt;
&lt;h1 id=&#34;agentic-框架的风格&#34;&gt;&lt;a class=&#34;markdownIt-Anchor&#34; href=&#34;#agentic-框架的风格&#34;&gt;&lt;/a&gt; Agentic 框架的风格&lt;/h1&gt;
&lt;p&gt;Agentic 框架在几个维度上有一些不同。理解 - 而不是混淆 - 这些维度是正确比较这些 Agentic 框架的关键。&lt;/p&gt;
&lt;h2 id=&#34;workflows-vs-agents&#34;&gt;&lt;a class=&#34;markdownIt-Anchor&#34; href=&#34;#workflows-vs-agents&#34;&gt;&lt;/a&gt; Workflows vs Agents&lt;/h2&gt;
&lt;p&gt;大多数框架包含了高层次的 Agent 抽象。一些框架包含了一些常见的工作流的抽象。LangGraph 是一个较为底层的精心策划的 Agentic 框架，支持 Workflows、Agents 和任何这两者的中间形态。我们认为这是至关重要的。正如上面所提到的，大多数 Agentic 系统产品都是 Workflows 和 Agents 的结合，一个生产可用的框架需要同时支持这两者。&lt;/p&gt;
&lt;p&gt;让我们回想一下构建可靠的 Agents 中难的一部分 - 确保大语言模型拥有正确的上下文。Workflows 能够有用的一部分原因就是它能很容易地将正确的上下文传递给大语言模型——由你来决定数据如何周转。&lt;/p&gt;
&lt;p&gt;当你在思考应该在 Workflow 到 Agent 的哪个范围内构建应用程序时，你应该考虑以下两点：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;可预测性 vs 代理性（Agency）&lt;/li&gt;
&lt;li&gt;门槛低，天花板高&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 id=&#34;可预测性-vs-代理性agency&#34;&gt;&lt;a class=&#34;markdownIt-Anchor&#34; href=&#34;#可预测性-vs-代理性agency&#34;&gt;&lt;/a&gt; 可预测性 vs 代理性(Agency)&lt;/h3&gt;
&lt;p&gt;当你的系统变得更加 Agentic，那他就会变得更加无法预测。&lt;/p&gt;
&lt;p&gt;有时候你会因为用户信任、合规性或者其他原因而想要或者你需要系统变得可预测。&lt;/p&gt;
&lt;p&gt;可靠性并不 100% 和可预测性相关，但是在实践上它们关系非常密切。&lt;/p&gt;
&lt;p&gt;你希望在这条曲线上的位置与你的应用程序息息相关。LangGraph 可用于在这条曲线的任何位置构建应用程序，让你能够移动到您想要的曲线位置。&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;../images/translation-how-to-think-about-agent-frameworks/KaV481QzOJDK9qmE_iI6NP_CuGFhEyi6JwtY2Mz460g=.png&#34; alt=&#34;&#34; /&gt;&lt;/p&gt;
&lt;h3 id=&#34;门槛高但天花板低&#34;&gt;&lt;a class=&#34;markdownIt-Anchor&#34; href=&#34;#门槛高但天花板低&#34;&gt;&lt;/a&gt; 门槛高但天花板低&lt;/h3&gt;
&lt;p&gt;当考虑框架时，考虑他们的下限和上限是很有帮助的一件事：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;低门槛：一个低门槛的框架对初学者很友好&lt;/li&gt;
&lt;li&gt;高门槛：一个高门槛的框架意味着陡峭的学习曲线，需要较多的知识储备才能高效地使用。&lt;/li&gt;
&lt;li&gt;天花板低：一个天花板低的框架意味着其可以实现的功能有限（你很快会超越它）&lt;/li&gt;
&lt;li&gt;天花板高：一个天花板高的框架为高级应用场景提供了广泛的能力和灵活性（它会随着你一起成长？）&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Workflow 框架提供了高天花板，但也带来了高门槛 - 你不得不自行写很多 Agent 逻辑。&lt;/p&gt;
&lt;p&gt;Agent 框架是低门槛的，但也有着低天花板 - 你很容易上手，但对于非平凡的场景还不够。&lt;/p&gt;
&lt;p&gt;LangGraph 的目标是既具有低门槛（内置 Agent 抽象能够很容易上手）也具有高天花板（低级功能，以实现高级用例）&lt;/p&gt;
&lt;h2 id=&#34;声明式-vs-非声明式&#34;&gt;&lt;a class=&#34;markdownIt-Anchor&#34; href=&#34;#声明式-vs-非声明式&#34;&gt;&lt;/a&gt; 声明式 vs 非声明式&lt;/h2&gt;
&lt;p&gt;声明式的框架由几大优点，但也存在缺点。这似乎在程序员间是一个永无止境的争论，每个人都有自己的偏好。&lt;/p&gt;
&lt;p&gt;当人们谈到非声明式时，他们通常会暗示命令式作为替代。&lt;/p&gt;
&lt;p&gt;大多数人们将 LangGraph 作为声明式的框架，这只对一部分。&lt;/p&gt;
&lt;p&gt;首先，当连接好节点和边（workflow）后，这些只不过是一些 Python 或者 TypeScript 的函数而已。因此，LangGraph 是声明式和命令式的混合体。&lt;/p&gt;
&lt;p&gt;其次，除了推荐的声明式 API 之外，我们实际上还支持其他 API。具体来说，我们同时支持函数式 API 和事件驱动 API。虽然我们认为声明式 API 是一种有用的思维模型，但我们也意识到它并不适合所有人。&lt;/p&gt;
&lt;p&gt;关于 LangGraph 的常见评论是，它类似于 Tensorflow（声明式深度学习框架），而 Agents SDK 等框架类似于 Pytorch（命令式深度学习框架）。&lt;/p&gt;
&lt;p&gt;这完全是错误的。像 Agents SDK（以及最初的 LangChain、CrewAI 等）这样的框架既不是声明式的也不是命令式的——它们只是抽象。它们有一个代理抽象（一个 Python 类），其中包含一系列运行代理的内部逻辑。它们实际上不是编排框架。它们只是抽象。&lt;/p&gt;
&lt;h2 id=&#34;agent-抽象&#34;&gt;&lt;a class=&#34;markdownIt-Anchor&#34; href=&#34;#agent-抽象&#34;&gt;&lt;/a&gt; Agent 抽象&lt;/h2&gt;
&lt;p&gt;大多数 Agent 框架包含一个 Agent 抽象。他们通常以涉及一个 prompt、模型和工具开始。之后他们会增加一小部分参数…然后再增加一些…然后再增加更多。到最后你会得到一连串的控制多种行为的参数，所有这些参数都会抽象到一个类内。如果你想了解发生了什么，或者改变逻辑，你必须进入类并修改源代码。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;这些抽象最终使得理解或控制大模型各个步骤中的具体内容变得非常困难。这一点很重要——拥有这种控制对于构建可靠的 Agent 至关重要（如上所述）。这就是代理抽象的危险所在。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;我们经历了惨痛教训才明白这一点。这正是最初 LangChain 链和 Agents 的问题所在。他们提供的抽象实际上阻碍了实际操作。两年前的原始 Agent 抽象只是一个 Agent 类，包含一个模型、一个 prompt 和一些工具。这不是新概念。这没有提供足够的控制权（给到用户），现在也是如此。&lt;/p&gt;
&lt;p&gt;需要明确的是，这些 Agent 抽象确实有其意义。他让上手变得简单，但是我认为这些 Agent 抽象不足以构建一个可靠的 Agent（甚至永远也不够）。&lt;/p&gt;
&lt;p&gt;我们认为最好的去思考这些 Agent 抽象的方式是像 Keras 那样。它们提供了更高级的抽象去让上手变得简单。&lt;strong&gt;但是至关重要的一点是它们是基于一个更低级的框架，这样你就不会超越它。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;这就是为什么我们已经在 LangGraph 之上构建 Agent 抽象，者提供了更加简单的方式去上手 Agents，但是如果你想要跳转到更低级的 LangGraph，那你也可以轻松做到。&lt;/p&gt;
&lt;h2 id=&#34;multi-agent&#34;&gt;&lt;a class=&#34;markdownIt-Anchor&#34; href=&#34;#multi-agent&#34;&gt;&lt;/a&gt; Multi Agent&lt;/h2&gt;
&lt;p&gt;通常情况下，Agentic 系统不仅包含一个 Agent，可能会有多个。OpenAI 在报告中这样写：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;对于很多复杂的 Workflows，分割 prompts 和工具给多个 Agent 能够带来更好的性能和可扩展性。当你的 Agents 无法遵循复杂的 instructions 或者始终选择了错误的工具时，你可能需要去进一步划分你的系统并引入更多不同的 Agents。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Multi Agent 系统的核心是这些 Agents 之间如何交流。同样的，构建 Agents 的难点在于找到合适的上下文给大语言模型。这些代理之间的交流至关重要。&lt;/p&gt;
&lt;p&gt;有很多方式能做到这一点！Handoffs 是其中一种方法。这也是来自 Agents SDK 的代理抽象，我很喜欢这一点。&lt;/p&gt;
&lt;p&gt;但是最好的方式有时其实是 Workflows。&lt;strong&gt;参考 Anthropic 的博客中的所有工作流程图，并将所有的大语言模型调用替换成 Agents。这种 Workflows 和 Agents 的结合有时候能够带来最佳的可靠性。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;../images/translation-how-to-think-about-agent-frameworks/XWGZbrrO9o9YctVRh5ytxH87Rpsw-EHZVuBRf1u1Bxo=.png&#34; alt=&#34;&#34; /&gt;&lt;/p&gt;
&lt;p&gt;同样的，Agentic 系统不仅仅是 Workflows，也不仅仅是一个 Agent。他可以是也经常是这两者的结合。正如 Anthropic 指出的：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;请组合和定制这些模式。&lt;/p&gt;
&lt;p&gt;这些构建块并不一定是规定性的。它们是开发者可以根据不同应用场景进行调整和组合的通用模式。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h1 id=&#34;常见问题&#34;&gt;&lt;a class=&#34;markdownIt-Anchor&#34; href=&#34;#常见问题&#34;&gt;&lt;/a&gt; 常见问题&lt;/h1&gt;
&lt;h2 id=&#34;框架的价值是什么&#34;&gt;&lt;a class=&#34;markdownIt-Anchor&#34; href=&#34;#框架的价值是什么&#34;&gt;&lt;/a&gt; 框架的价值是什么？&lt;/h2&gt;
&lt;p&gt;我们经常看到有人问，构建 Agent 系统是否需要一个框架？Agent 框架能提供什么价值？&lt;/p&gt;
&lt;h3 id=&#34;agent-抽象-2&#34;&gt;&lt;a class=&#34;markdownIt-Anchor&#34; href=&#34;#agent-抽象-2&#34;&gt;&lt;/a&gt; Agent 抽象&lt;/h3&gt;
&lt;p&gt;框架通常很有用，因为它们包含实用的抽象，这使得入门变得简单，并为工程师提供了一种通用的构建方式，从而更容易上手和维护项目。正如上面提到的，Agent 框架确实也有其缺点。对于大多数 Agent 框架，这是它们提供的唯一价值。我们非常努力地确保 LangGraph 不会出现这种情况。&lt;/p&gt;
&lt;h3 id=&#34;短期记忆&#34;&gt;&lt;a class=&#34;markdownIt-Anchor&#34; href=&#34;#短期记忆&#34;&gt;&lt;/a&gt; 短期记忆&lt;/h3&gt;
&lt;p&gt;如今，大多数 Agent 应用都​​涉及某种多轮（例如聊天）组件。LangGraph 提供了生产可用的存储，以实现多轮体验。&lt;/p&gt;
&lt;h3 id=&#34;长期记忆&#34;&gt;&lt;a class=&#34;markdownIt-Anchor&#34; href=&#34;#长期记忆&#34;&gt;&lt;/a&gt; &lt;strong&gt;长期记忆&lt;/strong&gt;&lt;/h3&gt;
&lt;p&gt;虽然还处于早期阶段，但我非常看好 Agent 系统能够从自身经验中学习（例如，跨对话记忆信息）。LangGraph 为跨对话记忆提供了可用于生产的存储。&lt;/p&gt;
&lt;h3 id=&#34;human-in-the-loop&#34;&gt;&lt;a class=&#34;markdownIt-Anchor&#34; href=&#34;#human-in-the-loop&#34;&gt;&lt;/a&gt; &lt;strong&gt;Human-in-the-loop&lt;/strong&gt;&lt;/h3&gt;
&lt;p&gt;许多 Agent 系统通过一些 human-in-the-loop 组件得到了改进。例如，获取用户反馈、批准工具调用或允许编辑工具调用参数。LangGraph 提供了内置支持，以便在生产系统中实现这些工作流程。&lt;/p&gt;
&lt;h3 id=&#34;human-on-the-loop&#34;&gt;&lt;a class=&#34;markdownIt-Anchor&#34; href=&#34;#human-on-the-loop&#34;&gt;&lt;/a&gt; &lt;strong&gt;Human-on-the-loop&lt;/strong&gt;&lt;/h3&gt;
&lt;p&gt;除了允许用户在 Agent 运行时影响其运行之外，允许用户在 Agent 运行结束后检查其轨迹，甚至可以返回到之前的步骤，然后从那里重新运行（包含更改）也非常有用。我们称之为 human-on-the-loop，LangGraph 内置了对此的支持。&lt;/p&gt;
&lt;h3 id=&#34;streaming&#34;&gt;&lt;a class=&#34;markdownIt-Anchor&#34; href=&#34;#streaming&#34;&gt;&lt;/a&gt; &lt;strong&gt;Streaming&lt;/strong&gt;&lt;/h3&gt;
&lt;p&gt;大多数 Agent 应用程序需要一段时间才能运行，因此向最终用户提供更新对于提供良好的用户体验至关重要。LangGraph 提供内置的 streaming of tokens、graph steps 和 arbitrary streams。&lt;/p&gt;
&lt;h3 id=&#34;debuggingobservability&#34;&gt;&lt;a class=&#34;markdownIt-Anchor&#34; href=&#34;#debuggingobservability&#34;&gt;&lt;/a&gt; &lt;strong&gt;Debugging/observability&lt;/strong&gt;&lt;/h3&gt;
&lt;p&gt;构建可靠 Agent 的难点在于确保将正确的上下文传递给大语言模型。能够检查 Agent 执行的确切步骤以及每个步骤的确切输入/输出对于构建可靠 Agent 至关重要。LangGraph 与 LangSmith 无缝集成，提供一流的调试和可观察性。注意：AI 可观察性与传统的软件可观察性不同（这值得另文探讨）。&lt;/p&gt;
&lt;h3 id=&#34;fault-tolerance&#34;&gt;&lt;a class=&#34;markdownIt-Anchor&#34; href=&#34;#fault-tolerance&#34;&gt;&lt;/a&gt; &lt;strong&gt;Fault tolerance&lt;/strong&gt;&lt;/h3&gt;
&lt;p&gt;容错是构建分布式应用程序的传统框架（如 Temporal）的关键组成部分。LangGraph 通过持久的工作流和可配置的重试机制，使容错变得更加简单。&lt;/p&gt;
&lt;h3 id=&#34;optimization&#34;&gt;&lt;a class=&#34;markdownIt-Anchor&#34; href=&#34;#optimization&#34;&gt;&lt;/a&gt; &lt;strong&gt;Optimization&lt;/strong&gt;&lt;/h3&gt;
&lt;p&gt;与其手动调整 prompt，有时更容易的是定义一个评估数据集，然后基于此数据集自动优化代理。LangGraph 目前不支持此功能——我们认为现在实现此功能还为时过早。但我想将其纳入其中，因为我认为这是一个值得考虑的有趣维度，也是我们一直在关注的领域。dspy 是目前最好的框架。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;所有这些价值主张（除了 Agent 抽象之外）都为代理、工作流以及介于两者之间的一切提供了价值。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3 id=&#34;所以你真的需要一个-agentic-框架吗&#34;&gt;&lt;a class=&#34;markdownIt-Anchor&#34; href=&#34;#所以你真的需要一个-agentic-框架吗&#34;&gt;&lt;/a&gt; 所以，你真的需要一个 Agentic 框架吗？&lt;/h3&gt;
&lt;p&gt;如果你的应用程序不需要所有这些功能，或者你想自己构建这些功能，那么你可能不需要。其中一些功能（例如短期记忆）并不复杂。其他一些功能（例如 human-on-the-loop，或特定于大语言模型的可观测下）则更为复杂。&lt;/p&gt;
&lt;p&gt;关于 Agent 抽象，我同意 Anthropic 在其文章中的观点：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;如果你确实在使用一个框架，确保你了解底层代码。对底层原理的错误假设是客户错误的常见来源。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3 id=&#34;当模型的性能变得更好时workflows-的生态位会被-agents-挤占吗&#34;&gt;&lt;a class=&#34;markdownIt-Anchor&#34; href=&#34;#当模型的性能变得更好时workflows-的生态位会被-agents-挤占吗&#34;&gt;&lt;/a&gt; 当模型的性能变得更好时，Workflows 的生态位会被 Agents 挤占吗？&lt;/h3&gt;
&lt;p&gt;支持 Agent 的一个常见的论调是，虽然现在不起作用，但是将来会起作用。因此你只需要简单的，工具调用的 Agent。&lt;/p&gt;
&lt;p&gt;我认为有多种情况是正确的：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;工具调用 Agent 的性能肯定会提升&lt;/li&gt;
&lt;li&gt;控制传递给大语言模型的上下文的内容仍然很重要（garbage in, garbage out）&lt;/li&gt;
&lt;li&gt;对于一些应用，工具调用循环已经足够了&lt;/li&gt;
&lt;li&gt;对于一些其他应用，Workflows 一定会更简单、更便宜、更快和更好。&lt;/li&gt;
&lt;li&gt;对于大多数应用，Agentic 系统的产品是 Workflows 和 Agents 的结合。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;我认为 OpenAI 或 Anthropic 不会辩论这些观点。Anthropic 的帖子是这么说的：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;当使用大语言模型构建应用时，我们推荐找一些可能的最简单的解决方案，只在需要的时候增加复杂性。这可能意味着不一定需要构建一个 Agentic 系统，Agentic 系统通常会牺牲延迟和费用来换取更好的性能，你应该权衡这种交换何时才有意义。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;OpenAI 是这么说的：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;在致力于构建一个 Agent 之前，请验证你的用力能够清晰地满足这些标准，否则，确定性的解决方案可能就够了。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;是否存在这种简单的工具调用就足够了的应用？我认为只有当你使用的模型基于大量特定于你用例的数据进行训练/微调/强化学习时，这种情况才可能出现。这可能通过两种方式实现：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;你的任务是独一无二的。你需要收集大量数据，并训练/微调/强化学习你自己的模型。&lt;/li&gt;
&lt;li&gt;你的任务并非独一无二。大模型实验室正在使用与你的任务相关的数据进行训练/微调/强化学习。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;（附注：如果我在一个我的任务并不独特的领域建立一家垂直初创企业，我会非常担心我的初创企业的长期可行性）。&lt;/p&gt;
&lt;h4 id=&#34;你的任务是独一无二的&#34;&gt;&lt;a class=&#34;markdownIt-Anchor&#34; href=&#34;#你的任务是独一无二的&#34;&gt;&lt;/a&gt; 你的任务是独一无二的&lt;/h4&gt;
&lt;p&gt;我敢打赌大多数使用场景（当然是大多数企业使用场景）会归为这一类。AirBnb 如何处理客户支持与 Klarna 如何处理客户支持与 Rakuten 如何处理客户支持是不同的。这些任务中有很多微妙之处。Sierra——一家在客户支持领域领先的 Agent 公司——并没有构建单个客户支持 Agent，而是构建了一个客户支持代理平台：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Sierra Agent SDK 使开发人员能够使用声明性编程语言构建强大、灵活的 Agent，并使用可组合的技能来表达程序知识&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;它们需要这样去做是因为每一家公司的客户支持都是独一无二的，通用 Agent 的性能不够（效果不好）。&lt;/p&gt;
&lt;p&gt;OpenAI 的 Deep Research 就是一个 Agent 的例子，它使用一个&lt;strong&gt;针对特定任务训练的模型&lt;/strong&gt;，通过简单的工具调用循环来实现。所以这是可以实现的，而且它能够生成出色的 Agent。&lt;/p&gt;
&lt;p&gt;如果你可以在你的特定任务上训练出一个 SOTA 级别的模型，那么是的，你可能不需要一个支持任意工作流的框架，你只需要一个简单的工具调用循环。在这个场景下，Agents 当然会比 Workflows 好（成为首选）。&lt;/p&gt;
&lt;p&gt;我脑海中一个非常开放的问题是：有多少 Agent 公司有能力拥有（这么大量的）数据、工具或者知识去为它们的任务训练一个 SOTA 级别的模型呢？目前，我认为只有大模型实验室才能做到这一点。但这种情况会改变吗？小型垂直领域的初创公司能够训练出一个 SOTA 模型来完成他们的任务吗？我对这个问题非常感兴趣。如果您目前正在做这件事，请与我们联系！&lt;/p&gt;
&lt;h4 id=&#34;你的任务不是独一无二的&#34;&gt;&lt;a class=&#34;markdownIt-Anchor&#34; href=&#34;#你的任务不是独一无二的&#34;&gt;&lt;/a&gt; 你的任务不是独一无二的&lt;/h4&gt;
&lt;p&gt;我认为有些任务足够通用，大模型实验室将能够提供足够好的模型来对这些非通用任务执行简单的工具调用循环。&lt;/p&gt;
&lt;p&gt;OpenAI 通过 API 发布了他们的 Computer-use 模型，该模型基于通用计算机使用数据进行了微调，旨在达到通用任务的良好性能。（附注：我认为它还远远不够好。）&lt;/p&gt;
&lt;p&gt;编程是一个很有趣的例子。编程是相对通用的，并且到目前为止，编程无疑是一个突破性的应用场景。Claude code 和 OpenAI 的 Codex CLI 就是使用这种简单工具调用循环的这方面的两个 Coding Agent 例子。我强烈打赌他们的 base models 训练集中有大量的 Coding 数据和相关任务（可参考&lt;strong&gt;&lt;a href=&#34;https://docs.anthropic.com/en/docs/build-with-claude/tool-use/text-editor-tool?ref=blog.langchain.dev&#34;&gt;这里&lt;/a&gt;****，&lt;/strong&gt;可证明 Anthropic 确实这么做了）。&lt;/p&gt;
&lt;p&gt;有趣的是，由于通用模型（general models）都是基于这些数据来训练的，那么这些数据的具体形状（译注: 原文是 exact shape, 不清楚是不是这么翻译）有多重要？Ben Hylak 之前发过一条&lt;a href=&#34;https://x.com/benhylak/status/1912922457012572364?ref=blog.langchain.dev&#34;&gt;很有趣的推文&lt;/a&gt;，似乎引起了大家的共鸣(resonate)：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;模型不再知道如何使用 Cursor 了。&lt;/p&gt;
&lt;p&gt;它们对 terminal 进行了优化。这就是为什么 Claude 3.7 和 GPT o3 在 Cursor 中表现如此糟糕，而在 Cursor 之外如此优秀的原因。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;这可能表明了两件事情：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;你的任务不得不需要和通用模型训练时使用的任务非常非常接近。你的任务越不相似，通用模型就越不可能适合你的应用场景。&lt;/li&gt;
&lt;li&gt;通用模型在其他特定任务上训练可能会导致在你的任务上的表现下降。我确信用于训练新模型的数据与 Cursor 的应用场景类似（甚至更多）。但如果有与这种形状略有不同的新数据的涌入，其价值将超过任何其他类型的数据。&lt;strong&gt;这意味着目前通用模型很难在大量任务上真正表现出色。&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;blockquote&gt;
&lt;p&gt;即使对于优先使用 Agents 而不是任何类似 Workflows 的应用程序，你仍然可以受益于与低级工作流控制无关的框架的功能：短期记忆存储、长期记忆存储、human-in-the-loop、human-on-the-loop、streaming、容错、调试/可观察性。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3 id=&#34;openai-的看法有什么错误&#34;&gt;&lt;a class=&#34;markdownIt-Anchor&#34; href=&#34;#openai-的看法有什么错误&#34;&gt;&lt;/a&gt; OpenAI 的看法有什么错误？&lt;/h3&gt;
&lt;p&gt;如果我们重新审视 OpenAI 的态度，就会发现这个态度建立在错误的二分法之上，这种二分法将不同维度的 Agentic 框架混为一谈，以夸大其单一抽象的价值。特别地是，它将“声明式与命令式”和 Agent 抽象以及“Workflows 与 Agents”混为一谈。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;最后，它没有抓住对构建生产可用的 Agentic 系统的主要挑战以及框架应该提供的价值，即：一个可靠的编排层，让开发者能够明确控制大语言模型的上下文，同时无缝地处理生产问题，如持久化、容错、human-in-the-loop 的交互过程。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;让我们来分析一下我所质疑的具体部分：&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;../images/translation-how-to-think-about-agent-frameworks/Jq3vh0D2kSE7oNJGjppIkrpJQSEEWuvbG_RG3L-bUFg=.png&#34; alt=&#34;&#34; /&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;“声明式 vs 非声明式图”&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;LangGraph 并非完全声明式——但它的声明性已经足够强了，所以这并非我的主要抱怨。我主要抱怨的是“非声明式”这种说法做了很多工作，而且具有误导性。通常，当人们批评声明式框架时，他们更倾向于更命令式的框架。但 Agents SDK 并非命令式框架。它是一种抽象。更合适的标题应该是“声明式 vs 命令式”或“你需要一个编排框架吗”或“为什么你只需要 Agent 抽象”或“Workflows vs Agents”，这取决于他们想要讨论的内容（他们似乎在下面对两者都进行了讨论）。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;“随着工作流程变得更加动态和复杂，这种方法很快就会变得繁琐和具有挑战性”&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;这与声明式或非声明式无关。这完全取决于 Workflows 与 Agents。你可以轻松地将 Agents SDK 中的 Agent 逻辑表达为声明式图，并且该图与 Agents SDK 一样动态且灵活。&lt;/p&gt;
&lt;p&gt;关于 Workflows 与 Agents 的比较。很多 Workflows 不需要这种程度的动态性和复杂性。OpenAI 和 Anthropic 都承认这一点。如果可以使用工作流，就应该使用工作流。大多数 Agent 系统都是组合使用。没错，如果 Workflows 确实动态且复杂，那么可以使用代理。但不要事事都使用 Agents。OpenAI 在论文的前面部分就明确指出了这一点。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;“通常需要学习专门的领域特定语言”&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;再次强调：Agents SDK 并非命令式框架。它是一种抽象。它本身也是一种领域特定语言（它的抽象）。我认为，目前学习和处理 Agents SDK 的抽象比学习 LangGraph 的抽象更糟糕。很大程度上是因为构建可靠 Agents 的难点在于确保 Agents 拥有正确的上下文，而 Agents SDK 在这方面的混淆程度远高于 LangGraph。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;“更灵活”&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;这完全是错误的。事实恰恰相反。Agents SDK 能做的所有事情，LangGraph 都能做到。Agents SDK 只能实现 LangGraph 功能的 10%。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;“代码优先”&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;使用 Agents SDK，你可以编写它们的抽象。使用 LangGraph，你需要编写大量的常规代码。我不明白 Agents SDK 怎么会更注重代码优先。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;“使用熟悉的编程结构”&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;使用 Agents SDK，你需要学习一整套全新的抽象概念。使用 LangGraph，你需要编写大量的常规代码。还有什么比这更熟悉的呢？&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;“实现更加动态和适应性更强的代理编排”&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;再说一遍，这跟声明式和非声明式无关。这跟 Workflows 和 Agents 有关。参见上文。&lt;/p&gt;
&lt;h3 id=&#34;如何比较所有的-agent-框架&#34;&gt;&lt;a class=&#34;markdownIt-Anchor&#34; href=&#34;#如何比较所有的-agent-框架&#34;&gt;&lt;/a&gt; 如何比较所有的 Agent 框架？&lt;/h3&gt;
&lt;p&gt;我们讨论了 Agent 框架的许多不同组件：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;它们是灵活的编排层，还是仅仅是 Agent 抽象？&lt;/li&gt;
&lt;li&gt;如果它们是灵活的编排层，它们是声明式的还是其他类型的？&lt;/li&gt;
&lt;li&gt;除了 Agent 抽象之外，这个框架还提供了哪些功能？&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;我觉得把这些维度列在电子表格里会很有趣。我尽量保持客观公正（&lt;a href=&#34;https://x.com/hwchase17/status/1913662736963412365?ref=blog.langchain.dev&#34;&gt;我从推特上征求了很多反馈，而且得到了很多好评！&lt;/a&gt;）。&lt;/p&gt;
&lt;p&gt;目前，这个比较包含了 Agents SDK, Google’s ADK, LangChain, Crew AI, LlamaIndex, Agno AI, Mastra, Pydantic AI, AutoGen, Temporal, SmolAgents, DSPy.&lt;/p&gt;
&lt;p&gt;如果我遗漏了一个框架（或者框架有误），请发表评论！&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;你可以在 &lt;a href=&#34;https://docs.google.com/spreadsheets/d/1B37VxTBuGLeTSPVWtz7UMsCdtXrqV5hCjWkbHN8tfAo/edit?usp=sharing&amp;amp;ref=blog.langchain.dev&#34;&gt;here&lt;/a&gt; 找到实时表格。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id=&#34;结论&#34;&gt;&lt;a class=&#34;markdownIt-Anchor&#34; href=&#34;#结论&#34;&gt;&lt;/a&gt; 结论&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;构建可靠的 Agentic 系统最难的部分是确保 LLM 在每一步都拥有正确的上下文。这包括控制正确的内容进入 LLM 上下文以及执行正确的步骤去生成相关的内容。&lt;/li&gt;
&lt;li&gt;Agentic 系统同时包括 Workflows 和 Agents 两者（和一切这两者之间的事物）。&lt;/li&gt;
&lt;li&gt;大多数 Agentic 框架既不是声明式也不是命令式的编排框架，而只是一组代理抽象。&lt;/li&gt;
&lt;li&gt;Agent 抽象可以很容易去构建，但是他们通常会造成混淆，并难以确保 LLM 在每一步都有正确的上下文。&lt;/li&gt;
&lt;li&gt;各种不同的 Agent 系统都受益于同样的一组有用的功能，这些功能可以由框架提供也可以自行从头构建。&lt;/li&gt;
&lt;li&gt;LangGraph 被公认为是一个同时包含声明式和命令式的编排框架，封装了一系列的 Agent 抽象。&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote&gt;
&lt;p&gt;译者案: 在看了很多技术博客后我发现自己的表达能力和总结能力有一定欠缺，因此决定挑一些自己感兴趣的前沿领域的一些技术博客上学习他们的思考方式以及行文思路等，来提升自己的能力。少部分用到了 Google Translator，但是 95% 的内容都为自行翻译，如有纰漏请指出。&lt;/p&gt;
&lt;/blockquote&gt;
</content>
        <category term="2025" />
        <category term="LLM-based Agent" />
        <updated>2025-05-08T16:34:00.000Z</updated>
    </entry>
    <entry>
        <id>https://blog.soulter.top/posts/2025.2-monthly-record.html</id>
        <title>After reaching 1k Stars…</title>
        <link rel="alternate" href="https://blog.soulter.top/posts/2025.2-monthly-record.html"/>
        <content type="html">&lt;p&gt;我在大学给自己定的一个目标是在毕业前能够让 AstrBot 超过 1k Stars，没想到在今年的短短一个月内就超期完成了目标——截至到这篇博客发表前已经接近 5.7k Stars。&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;../images/2025.2-monthly-record/image-2.png&#34; alt=&#34;&#34; /&gt;&lt;/p&gt;
&lt;p&gt;而一切都源于我在今年一月底——赶在除夕前发了一个对这个项目的宣传视频。我以前从没有公开地对这个项目宣传过，看到同类项目的宣传视频后，打算趁着 DeepSeek 的热度顺手投一个视频。当然了，和那些恶意蹭热度的营销号不一样，这个项目我确实花了太多的精力去维护，并且视频也从早剪到晚……甚至担心视频过火，在发表这个视频的第二天我就把 DeepSeek 关键词从视频标题给删掉了。&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;../images/2025.2-monthly-record/2faf7328da122b1019be39005e923d45.png&#34; alt=&#34;&#34; /&gt;&lt;/p&gt;
&lt;p&gt;效果很好，前一天晚上十一点多发的视频，第二天早上起来一看已经有一千多个播放了。因为以前也有相关发视频的经验，我意识到这个视频肯定会破 5w 播放。&lt;/p&gt;
&lt;p&gt;视频发表后的前五天是我维护这个项目以来最忙的五天，平均每天都有两百个人入群，我们需要不停地解答他们的部署问题，从早到晚都在回信息修 Bug。在这里特别感谢 @灯灯喵、@Adios 等同学一起帮着进行了答疑 ❤。当然了，在这里也感谢所有为 AstrBot 提交 PR、Issue、编写插件的大家，让这个项目能够保持这么久的活力！甚至某搬史插件在 QQ 群内被疯传都传到我的学校中的游戏群了 🤯。&lt;/p&gt;
&lt;p&gt;除此之外，还有各种各样的奇妙经历，被真格基金的投资人、月之暗面负责战略的同学、各种初创 Founder、北京市海淀区创新创业协会相关人员找到，成功受邀入职了 AI 独角兽月之暗面、并且也和给字节大模型投毒的 2024 年 NIPS Best Paper 得主以及有关投资人吃了饭、成功加入 GitCode G-Star 毕业项目、AstrBot 成为了被各种营销号疯传的对象（补药啊）…基本上，甚至到现在，“everyday changes”，每天都是和以往完全不同的一天，也切实感受到自己半只脚踏入了 AI、互联网、创业相关圈子中，而这——都源于那个视频。可以说，那个视频彻底地改变了我今年一整年的轨迹。&lt;/p&gt;
&lt;p&gt;闲暇之际，写了这篇博客，主要是想发表一些感慨——Selling yourself 的重要性。&lt;/p&gt;
&lt;p&gt;之前在 X 上看到一篇推文，这也是周鸿祎在某个视频引用的一段话：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Selling yourself 是一个超重要但被低估的技能…东亚文化是一种集体主义，社会鼓吹的是踏实、谦虚、低调，不要突出自己…身边很多人是不擅长或者说不屑于展示自我的…当你过于突出的表达自我时，你会过于羞耻…而我认识的线下创业的朋友，或者互联网上的自媒体博主、短视频操盘手，以及各种超级个体、独立开发者、电商创业者，几乎都非常擅长表达自我。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;发布那个视频虽然看似是个小小的举动，但却引发了许多意想不到的连锁反应，自己从一个默默无闻的开发者，变成了社群中更为活跃的一员。我也逐渐意识到，成功的背后不仅仅是技术的积累和努力，更有如何在合适的时机、通过合适的方式，将自己的努力和成果展示给更多人。宣传成果不需要成果有多么的原创、多么的高深。要是没有自信，那么就请看看现在 AI 相关论文的灌水情况和各种科技营销号所做的内容吧。再说了，只有大胆拿出去给别人用了，才能知道作品的亮点和缺点并朝着正确的方向迭代。&lt;/p&gt;
&lt;p&gt;唯技术论是行不通、走不远的，对于我们来说，不仅要了解技术，还要 have more confidence，将自己的工作宣传出去，并且了解各种商科相关的知识。否则，正如《马斯克传》中提到的: “不掌握商科方面的知识，将来就会工作在掌握商科知识的人之下。”&lt;/p&gt;
</content>
        <category term="2025" />
        <category term="月记" />
        <updated>2025-03-08T12:07:42.000Z</updated>
    </entry>
    <entry>
        <id>https://blog.soulter.top/posts/astrbot-arch-message-handle.html</id>
        <title>AstrBot 的架构设计转变——消息处理篇</title>
        <link rel="alternate" href="https://blog.soulter.top/posts/astrbot-arch-message-handle.html"/>
        <content type="html">&lt;h2 id=&#34;一些碎碎念&#34;&gt;&lt;a class=&#34;markdownIt-Anchor&#34; href=&#34;#一些碎碎念&#34;&gt;&lt;/a&gt; 一些碎碎念&lt;/h2&gt;
&lt;p&gt;我维护 AstrBot 这个项目到现在已经有两年多的时间了。从 2022 年 12 月底 ChatGPT 问世以来，这个项目便在不断更迭，途中也收获了不少的用户和他们的支持。这是我目前为止花费时间和精力最多的一个项目，包括设计整个架构项目、维护项目等等。然而，我却没有将这些思考过程以文章的形式写下来。&lt;/p&gt;
&lt;p&gt;在这个项目正式被遗忘在时间的洪流之前，我觉得非常有必要写几篇文章来好好梳理一下它，来让更多人知道它背后的故事（聊聊它是怎么从一个几十行的代码膨胀到现在将近一万六千行代码的x）。毕竟真正去看源代码、看 commit log 的人应该没有多少吧。&lt;/p&gt;
&lt;p&gt;@z2z63 的一句话很好：“源代码是设计的编译产物，通过读源代码去理解设计就好比反汇编”。接触了这么多语言和项目，我越来越体会到架构设计的重要性。好的架构设计将导致一致的系统实现，更好的可维护性和可扩展性。而坏的设计会导致不良的代码，系统实现没有弹性，不能适应变化，模块间匹配得不好，重复的代码和工作在不同模块中随处可见。这招致在它上面叠加更多坏的设计（实际上就是它在迫使你这样做）。并且当想对代码进行单元测试的时候，发现由于高耦合的代码导致对某个模块进行单元测试成为了不可能。最终，开发者心情变差，没有良好的精神维护项目，就成了屎山。软件的架构设计不仅能影响最终软件的质量，甚至能够影响团队的组织结构。好的架构设计能够反映出好的团队分工，每个人/小团队负责某个模块，并且由于设计语言相通，对接和 Code Review 的效率也会提高。&lt;/p&gt;
&lt;p&gt;同时，我也越来越感受到“软件工程没有银弹“这个道理。第一，任何一个设计都不可能完美应用于所有项目实现中。例如，一些设计思想倾向于对功能做高层次的封装以降低开发者的学习成本，而这带来的高抽象度可能会降低开发者对系统底层的控制，导致灵活性变差。这在 kernel 开发、嵌入式开发中是不可理喻的。再比如，Web 后端领域的微服务架构具有扩展性高、独立性强、高可用性等优点，但它如果用在某些原型项目、初创项目中就是徒增功耗、吕布骑狗。第二，对于一个需要应对动态变化的需求和环境的项目来说，没有一个设计是完美的。需求和环境变化本身就是一个难以预料的因素，没有一个“银弹”能够预见未来。频繁的功能迭代会让原本设计好的分层架构的边界变得模糊和松弛，最后引发下一次设计重构。&lt;/p&gt;
&lt;p&gt;一个真正的工程师（任何领域）应该是善于解决问题的，解决问题的手段是灵活的，而不是教条地记住某些固定的看似高端的方法论便生搬硬套到每个问题中。&lt;/p&gt;
&lt;p&gt;我本想用一篇文章讲述完所有内容，但是写到一半发现太难了，一篇文章难以完整阐述这两年的工作内容，因此我将它拆分成了几篇博客依次分享。&lt;/p&gt;
&lt;h3 id=&#34;项目的早期&#34;&gt;&lt;a class=&#34;markdownIt-Anchor&#34; href=&#34;#项目的早期&#34;&gt;&lt;/a&gt; 项目的早期&lt;/h3&gt;
&lt;p&gt;在这个项目的早期，其实只是为了将 OpenAI 发布的 ChatGPT 接入到 QQ 频道，根本没有考虑到如今 LLM 百家齐放的情况，也没想到要接入如此多的消息平台，QQ、微信、Telegram，甚至是小米音箱。于是结构非常简单：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;一个调 OpenAI 接口的类&lt;/li&gt;
&lt;li&gt;一个接收 QQ 频道事件的类&lt;/li&gt;
&lt;li&gt;消息处理函数、机器人初始化函数等&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;消息处理函数、机器人初始化函数、接收频道事件的类等一些其他的工具函数都写在了 &lt;code&gt;core.py&lt;/code&gt; 下，这个文件在后面一度达到了 1000 行。&lt;/p&gt;
&lt;p&gt;随着 NewBing、逆向 ChatGPT 的出现，并且它们适用的机器人指令不相同，我开始注意到需要对项目进行分层了。&lt;/p&gt;
&lt;p&gt;我将大语言模型提供商抽象为了 Provider 类，并创建 Command 类用于表示机器人指令，每一个大语言模型提供商都拥有一个  Command 子类。这样，只要配合配置文件选择使用不同的提供商，消息处理函数就可以很清晰地去调用它们。此外，在后面的更新中，为了接入 QQ 群，我还引入了 Platform 类，以表示不同的消息平台。&lt;/p&gt;
&lt;p&gt;从这个阶段开始，我其实才真正对 OOP 有一个比较“清晰”的认识。&lt;/p&gt;
&lt;p&gt;然而之后仍然遇到了各种各样的问题。&lt;strong&gt;举几个当时比较难缠的：&lt;/strong&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;（更新项目采用内置Git更新，用户无需调用 git pull，只需要输入 &lt;code&gt;/update&lt;/code&gt; 指令），由于配置文件直接写在 git 工作树中，当用户修改了配置文件，就会导致没办法 git pull，或者需要 --force，但这样的后果是一旦我更新了配置文件，就会导致更新后用户的配置文件被重置。这在当时一度成为非常棘手的问题以至于我尽可能不去更新配置文件。&lt;/li&gt;
&lt;li&gt;指标上报、统计信息记录、初始化机器人、消息处理函数、各消息平台的启动函数、回复消息的函数、各平台的类都集成在了一个 &lt;code&gt;core.py&lt;/code&gt;文件中，使得这个文件越来越臃肿，耦合度越来越高。&lt;/li&gt;
&lt;li&gt;当前对于每一个消息事件的处理方式是新开一个线程进行处理。在多群聊的情况下，几乎每分钟都有几十个消息事件下发，也就是说每分钟都会新开几十个线程，非常吃资源。&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 id=&#34;项目设计的趋势&#34;&gt;&lt;a class=&#34;markdownIt-Anchor&#34; href=&#34;#项目设计的趋势&#34;&gt;&lt;/a&gt; 项目设计的趋势&lt;/h3&gt;
&lt;p&gt;下面几点是在历经这几次大规模重构之后总结出的 AstrBot 的明显的设计方向。&lt;/p&gt;
&lt;h4 id=&#34;简单可依赖&#34;&gt;&lt;a class=&#34;markdownIt-Anchor&#34; href=&#34;#简单可依赖&#34;&gt;&lt;/a&gt; 简单可依赖&lt;/h4&gt;
&lt;p&gt;这是百度的一句 slogan，“简单” 和 “可依赖” 是小团队开发的核心准则，它们不仅提高开发效率，还降低了维护成本。人手越少越要以这个为执行标准，它带来的优势是双向的，既方便用户上手，也方便开发者维护。&lt;/p&gt;
&lt;p&gt;软件架构不是一成不变的，要想做到随时可以修改，架构就必须简单，牺牲简单性的修改应当被抵制。形式永远服从功能，不要过度设计。&lt;/p&gt;
&lt;p&gt;相关具体实践：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;减少复杂的依赖链
&lt;ul&gt;
&lt;li&gt;AstrBot 去除了 Git 更新功能，而直接采用压缩包下载源码的方式。&lt;/li&gt;
&lt;li&gt;谨慎选择依赖库：引入第三方库前，明确其支持的系统架构（通过 pypi 查看&lt;code&gt;.whl&lt;/code&gt;覆盖度），避免因平台兼容性问题引发的 Bug。虽然 pip 支持源代码安装，但是还是不值得信任，因为有些库的源代码安装需要编译 C 代码，如果用户环境没有编译环境（比如 Windows 需要 Microsoft Visual C++ Build Tools），那么安装就会失败。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;精简执行链路
&lt;ul&gt;
&lt;li&gt;每个流程的执行链路尽量短，保持函数、方法和类的单一职责。&lt;/li&gt;
&lt;li&gt;避免业务逻辑过于复杂，确保每个功能模块清晰、直观。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;简单的用户体验
&lt;ul&gt;
&lt;li&gt;一键式部署：优化部署流程，例如通过 Docker 容器化提供跨平台支持。&lt;/li&gt;
&lt;li&gt;清晰的文档：提供简洁易懂的文档，降低上手门槛。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id=&#34;完全模块化&#34;&gt;&lt;a class=&#34;markdownIt-Anchor&#34; href=&#34;#完全模块化&#34;&gt;&lt;/a&gt; 完全模块化&lt;/h4&gt;
&lt;p&gt;并且每个模块都应该有清晰的职责划分和限制的通信方式，实现高内聚和低耦合。模块化设计让功能可以独立开发、测试和部署，同时便于扩展和维护。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;相关具体实践：&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;清晰的模块边界&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;插件化扩展&lt;/strong&gt;
&lt;ul&gt;
&lt;li&gt;采取了注册机制扩展模块，如 Platform、Provider、Command、事件监听器皆可通过插件扩展。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;松耦合设计&lt;/strong&gt;
&lt;ul&gt;
&lt;li&gt;使用事件驱动架构或消息队列作为模块通信方式，避免模块之间的直接依赖。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id=&#34;更易读的代码&#34;&gt;&lt;a class=&#34;markdownIt-Anchor&#34; href=&#34;#更易读的代码&#34;&gt;&lt;/a&gt; 更易读的代码&lt;/h4&gt;
&lt;p&gt;注释、docstrings、命名规则、PEP 8 规范、统一的设计风格等都是提高代码可读性的重要手段。&lt;/p&gt;
&lt;h2 id=&#34;第一阶段一个函数走天下&#34;&gt;&lt;a class=&#34;markdownIt-Anchor&#34; href=&#34;#第一阶段一个函数走天下&#34;&gt;&lt;/a&gt; 第一阶段——一个函数走天下&lt;/h2&gt;
&lt;p&gt;&lt;img src=&#34;https://cf.s3.soulter.top/astrbot-arch-message-handle/5GfYPYN7PDB7COjBeAZiq1RLREAiDb8wZ8GJ9ICbek0=.png&#34; alt=&#34;&#34; /&gt;&lt;/p&gt;
&lt;p&gt;一开始的 AstrBot 几乎所有组件都写在 &lt;code&gt;core.py&lt;/code&gt; 中，其中，在平台接收到消息之后，会调用 &lt;code&gt;msg_oper()&lt;/code&gt; 函数并传入 &lt;code&gt;AstrBotMessage&lt;/code&gt;, &lt;code&gt;session_id: str&lt;/code&gt;, &lt;code&gt;role: str&lt;/code&gt;, &lt;code&gt;platform: str&lt;/code&gt;。然后执行图中的处理过程，然后返回 MessageResult。并且所有的逻辑都完全暴露在这个函数中，可读性非常之差。&lt;/p&gt;
&lt;h2 id=&#34;第二阶段解耦的开始&#34;&gt;&lt;a class=&#34;markdownIt-Anchor&#34; href=&#34;#第二阶段解耦的开始&#34;&gt;&lt;/a&gt; 第二阶段——解耦的开始&lt;/h2&gt;
&lt;p&gt;首先，心头之恨是这冗长的 &lt;a href=&#34;http://core.py&#34;&gt;core.py&lt;/a&gt;，我尽可能解耦了这个文件中逻辑独立的功能为独立的模块，比如 MessageHandler, Updator, PlatformManager, PluginManager, CommandManager 等等。&lt;/p&gt;
&lt;h3 id=&#34;初始化程序&#34;&gt;&lt;a class=&#34;markdownIt-Anchor&#34; href=&#34;#初始化程序&#34;&gt;&lt;/a&gt; 初始化程序&lt;/h3&gt;
&lt;p&gt;&lt;a href=&#34;http://core.py&#34;&gt;core.py&lt;/a&gt; 中的初始化机器人相关的逻辑单独抽离成了 AstrBotBootStrap 类，它依次加载：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;配置&lt;/li&gt;
&lt;li&gt;CommandManager&lt;/li&gt;
&lt;li&gt;PluginManager&lt;/li&gt;
&lt;li&gt;AstrBotUpdator&lt;/li&gt;
&lt;li&gt;InternalCommandHandler&lt;/li&gt;
&lt;li&gt;持久化层&lt;/li&gt;
&lt;li&gt;Provider&lt;/li&gt;
&lt;li&gt;MessageHandler&lt;/li&gt;
&lt;li&gt;PlatformManager&lt;/li&gt;
&lt;li&gt;AstrBotDashboard（可视化面板）&lt;/li&gt;
&lt;li&gt;MetricUploader（指标上报）&lt;/li&gt;
&lt;li&gt;插件&lt;/li&gt;
&lt;li&gt;Platform&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;然后用 &lt;code&gt;asyncio.gather()&lt;/code&gt;将消息平台监听、Dashboard 后端服务器、指标上报、插件注册的长任务这些异步任务收集起来运行。&lt;/p&gt;
&lt;figure class=&#34;highlight python&#34;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&#34;gutter&#34;&gt;&lt;pre&gt;&lt;span class=&#34;line&#34;&gt;1&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;2&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;3&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&#34;code&#34;&gt;&lt;pre&gt;&lt;span class=&#34;line&#34;&gt;tasks = [metrics_upload_task, dashboard_task, *platform_tasks, *&lt;span class=&#34;variable language_&#34;&gt;self&lt;/span&gt;.context.ext_tasks]&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;tasks = [&lt;span class=&#34;variable language_&#34;&gt;self&lt;/span&gt;.handle_task(task) &lt;span class=&#34;keyword&#34;&gt;for&lt;/span&gt; task &lt;span class=&#34;keyword&#34;&gt;in&lt;/span&gt; tasks]&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;keyword&#34;&gt;await&lt;/span&gt; asyncio.gather(*tasks)&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/figure&gt;
&lt;h3 id=&#34;消息处理&#34;&gt;&lt;a class=&#34;markdownIt-Anchor&#34; href=&#34;#消息处理&#34;&gt;&lt;/a&gt; 消息处理&lt;/h3&gt;
&lt;p&gt;&lt;img src=&#34;https://cf.s3.soulter.top/astrbot-arch-message-handle/ZMW176Pd7WfyPX1JTXGSbNbcdz_Ovsth_LMAW1gWvJg=.png&#34; alt=&#34;&#34; /&gt;&lt;/p&gt;
&lt;p&gt;每个消息平台类在实例化的时候都会传入一个 &lt;code&gt;message_handler&lt;/code&gt; 实例对象（MessageHandler 类），当消息平台接收到消息时需要主动调用这个对象中的 &lt;code&gt;handle()&lt;/code&gt;方法来获取 AstrBot 处理该消息的结果。&lt;code&gt;handle()&lt;/code&gt; 方法接受一个 &lt;code&gt;AstrMessageEvent&lt;/code&gt; 对象，对象中包含了 &lt;code&gt;AstrBotMessage&lt;/code&gt; 消息结构等内容。该方法会执行一系列消息处理流程，最终返回消息处理结果。本质是之前的 &lt;code&gt;core.py&lt;/code&gt;中的 &lt;code&gt;msg_oper()&lt;/code&gt;函数。&lt;/p&gt;
&lt;p&gt;其实如果只从这个消息处理部分来说，似乎并没有太大的变化。但是由于已经把其他组件（限流器等）解耦，每个组件依照配置文件各司其职，于是更加灵活，代码可读性也高了不少。&lt;/p&gt;
&lt;p&gt;但是随着插件功能的不断完善，社区群中一位开发者在尝试将 vchat 适配器（微信）以插件的形式接入 AstrBot 时遇到了一些问题，比如不清楚 message_handler 到底该怎么用。这是因为我将 &lt;code&gt;message_handler&lt;/code&gt; 放在了插件的 Context 中，而由于依赖循环引用问题，导致没有写 &lt;code&gt;type hint&lt;/code&gt;，最终难以找到这个实例的使用方法，非常不优雅，并且这让 &lt;code&gt;MessageHandler&lt;/code&gt; 直接和 &lt;code&gt;Platform&lt;/code&gt; 直接耦合在一起，增加了 &lt;code&gt;Platform&lt;/code&gt; 的职责。&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://cf.s3.soulter.top/astrbot-arch-message-handle/JyL3BVMp4-FXDBVCFwSRVVd7adASZX832wYzYkPHqFY=.png&#34; alt=&#34;&#34; /&gt;&lt;/p&gt;
&lt;p&gt;并且，由于频繁的更新没来得及仔细思考和某些反范式的消息平台（如 QQ 官方机器人SDK），导致我对 &lt;code&gt;Platform&lt;/code&gt; 这个类的接口定义和职责较为混乱，比如 &lt;code&gt;send_msg()&lt;/code&gt; 接口的第一个参数是要发送的消息链，&lt;code&gt;type_hint&lt;/code&gt; 写的是 &lt;code&gt;MessageResult&lt;/code&gt;，然而有时可能会直接传入一个字符串。同样是&lt;code&gt;send_msg()&lt;/code&gt;，在当前的设计中需要额外处理文转图的情况，缺乏统一的处理机制，最终每个平台适配器都会实现这个逻辑，代码复用性降低。&lt;/p&gt;
&lt;p&gt;总结一下，无非就是在扩展消息平台这一层面上，&lt;strong&gt;开发者要关注的内容过多，AstrBot 开放的接口对开发者透明，&lt;/strong&gt;且结构混乱，最终会导致开发者写出耦合度高的代码，适配意愿度下降 。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;这里的透明指的是黑盒&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id=&#34;第三阶段事件总线-事件驱动和流水线模型&#34;&gt;&lt;a class=&#34;markdownIt-Anchor&#34; href=&#34;#第三阶段事件总线-事件驱动和流水线模型&#34;&gt;&lt;/a&gt; 第三阶段——事件总线、事件驱动和流水线模型&lt;/h2&gt;
&lt;p&gt;为了解决上述问题，我采用了事件总线模型。&lt;/p&gt;
&lt;p&gt;事件总线是一种用于管理和分发事件的机制，通常来说它用于解耦组件之间的通信。它像一个中央“通道”，允许发送者（事件发布者）将事件发送到总线，而接收者（事件订阅者）从总线获取自己感兴趣的事件。&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://cf.s3.soulter.top/astrbot-arch-message-handle/-xkEpc62Jnc5ZkzswC5g87HWYHbHZyTIyast1B4kSvk=.png&#34; alt=&#34;&#34; /&gt;&lt;/p&gt;
&lt;p&gt;这种设计有很多好处。&lt;/p&gt;
&lt;p&gt;对平台（发送者）来说，它减轻了平台的职责，有点像是 “平台只需要上报消息就行了，而接收者要考虑的东西就很多了” 这个逻辑。平台完全不需要关心处理消息的过程，也不需要持有消息处理器这一对象。只需要持有一个更加抽象的管道，然后在接收到信息，转换成统一的消息格式之后交付给管道即可。由于目前为止 AstrBot 内部已经近乎实现全异步化，因此，在这里我使用了 &lt;code&gt;asyncio&lt;/code&gt; 的 &lt;code&gt;Queue&lt;/code&gt; 来作为连接总线和消息平台的管道。&lt;/p&gt;
&lt;p&gt;对消息处理器（接收者）来说，由于平台将处理消息的职责交给了接收者，那么可以采取更加粒度化的管控，比如对某个平台限流，负载均衡等等。&lt;/p&gt;
&lt;p&gt;了解 Queue 的读者可能会问，单靠 Queue 实现全双工通信会比较复杂，如何解决发送消息的逻辑呢？确实，在之前的设计中，我们调用 message_handler.handle() 可以直接获得到结果，但是这里我们仅是使用 Queue 上报消息。难道再用一个 Queue 接收消息？其实这样也行，现在 AstrBot 的 WebChat 就是这样实现的。但是我使用了一个更加稳妥的办法：设置一个 &lt;code&gt;AstrMessageEvent&lt;/code&gt; 类，每个平台适配器都被要求实现这个类和这个类的 &lt;code&gt;send()&lt;/code&gt; 接口。通常来说，会使用依赖注入这个手段，将平台的回复消息的回调函数实例传给其派生的 &lt;code&gt;AstrMessageEvent&lt;/code&gt; 类，然后把这个类对象传给 Queue。这样消息处理在回复消息的时候只需要调用 &lt;code&gt;send()&lt;/code&gt; 即可，并且其参数只有消息链对象，非常的简洁。&lt;/p&gt;
&lt;p&gt;这样还没完。在之前的消息处理器中，将所有处理消息的功能放在一起，比如限流器、安全检查、指令扫描。这对功能扩展来说非常不友好。在设计 astrbot_plugin_atri 这个插件的时候，我就开始发现其实这个处理消息的过程更像一个流水线（Pipeline），各种功能能够被归类到不同的流水线的阶段（Stage）中。于是，我将这整个消息处理的部分设计为了一个 Pipeline。&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://cf.s3.soulter.top/astrbot-arch-message-handle/K5U17l0kNNXENX2-Sb_GWouSlIssCW_L6TqVdgDYDcY=.png&#34; alt=&#34;&#34; /&gt;&lt;/p&gt;
&lt;p&gt;事件总线持有一个流水线调度器（PipelineScheduler），调度器将根据预先设置好的 &lt;code&gt;STAGES_ORDER&lt;/code&gt; 按顺序执行流水线阶段的 &lt;code&gt;process(event)&lt;/code&gt;方法，这个在多个阶段中顺序执行的过程被称为 &lt;code&gt;事件传播&lt;/code&gt;(Event Propagation)。得益于 Python 生成器的特性，当某个阶段 yield 之后，调度器将会记录该点位，然后继续往下执行后续流水线再返回到该阶段中。&lt;/p&gt;
&lt;p&gt;这样的设计将之前的 MessageHandler 又进行了一次彻底的解耦，在灵活度提升的情况下也提高了代码的可读性。并且考虑到插件扩展功能时可能会在不同的 Stage 做扩展，比如考虑某个 RAG 插件，如果它使用默认的这个消息处理流程，那么它就需要在&lt;code&gt;请求 LLM 前&lt;/code&gt;召回相关文本并修改请求 LLM 的上下文。再比如某个语音转文字插件需要在 Preprocess 阶段将语音消息段转换为文本消息段。&lt;/p&gt;
&lt;h2 id=&#34;主动消息的设计&#34;&gt;&lt;a class=&#34;markdownIt-Anchor&#34; href=&#34;#主动消息的设计&#34;&gt;&lt;/a&gt; 主动消息的设计&lt;/h2&gt;
&lt;p&gt;一个消息平台应当支持被动消息（回复）和主动消息的发送。&lt;/p&gt;
&lt;p&gt;被动消息指的是&lt;strong&gt;消息事件在传播时&lt;/strong&gt;给消息平台发送的信息，使用 &lt;code&gt;send()&lt;/code&gt; 方法即可实现这一目的。被动消息回复的实现不复杂，因为 &lt;code&gt;AstrMessageEvent&lt;/code&gt; 持有 &lt;code&gt;AstrBotMessage&lt;/code&gt;对象，其包含了发送人的所有信息，在实现 &lt;code&gt;send()&lt;/code&gt; 回调的时候直接根据这个对象中发送人信息发送即可。&lt;/p&gt;
&lt;p&gt;而主动消息需要是没有 &lt;code&gt;AstrMessageEvent&lt;/code&gt;的，换言之，插件或者某个长任务可以随时给消息平台发送消息。这该如何解决呢？&lt;/p&gt;
&lt;p&gt;插件在初始化的时候会被传入一个 Context 对象，这个 Context 持有 PlatformManager，这样只需在 Context 中设计好相关发送接口即可。&lt;/p&gt;
&lt;p&gt;那如何确定发送人的信息呢？每个平台都是异构的，所需的信息不尽相同，不同的消息类型（群聊、私聊）所需的信息也不同，并且机器人将消息发给哪个消息平台也很难确定。&lt;/p&gt;
&lt;p&gt;在早期的设计中，Context 中的发送接口商定好发送人的信息统一用一个 dict 来传递，并且在文档中写明了哪个消息平台的发送人信息的格式是什么。&lt;/p&gt;
&lt;p&gt;但是这样还是太复杂和不稳定。我们需要设计一个开发者无感的发送方式。&lt;/p&gt;
&lt;p&gt;于是我设计了一个名为 &lt;code&gt;unique_msg_origin&lt;/code&gt; 的消息结构，其实他就是一个字符串，包含了平台类型、消息类型（群聊还是私聊）、发送源的 session_id，并用 &lt;code&gt;:&lt;/code&gt; 来分隔，如 &lt;code&gt;aiocqhttp:FriendMessage:905617992&lt;/code&gt;。这三个信息已经能够满足所有的消息平台的主动信息发送的需求（因为 &lt;code&gt;session_id&lt;/code&gt; 也是消息平台适配器自己指定的，它们可以在这上面存入更多信息）。&lt;/p&gt;
&lt;p&gt;于是只需要保存好&lt;code&gt;unique_msg_origin&lt;/code&gt;（由于是字符串格式的，也很方便持久化），调用 Context 中的接口传入&lt;code&gt;unique_msg_origin&lt;/code&gt; 即可。&lt;/p&gt;
</content>
        <category term="2025" />
        <category term="开源项目" />
        <category term="架构" />
        <updated>2025-01-23T20:00:00.000Z</updated>
    </entry>
    <entry>
        <id>https://blog.soulter.top/posts/2024-yearly-record.html</id>
        <title>2024 年 / 总结</title>
        <link rel="alternate" href="https://blog.soulter.top/posts/2024-yearly-record.html"/>
        <content type="html">&lt;blockquote&gt;
&lt;p&gt;洋洋洒洒又一篇，忙忙碌碌又一年。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id=&#34;狐狸与刺猬&#34;&gt;&lt;a class=&#34;markdownIt-Anchor&#34; href=&#34;#狐狸与刺猬&#34;&gt;&lt;/a&gt; 狐狸与刺猬&lt;/h2&gt;
&lt;p&gt;我把这一节放在开头，因为它代表了我 2024 年最为深刻的收获之一。&lt;/p&gt;
&lt;p&gt;我很早就发现自己是一个对很多不同领域都同时感兴趣的人了，甚至这种兴趣有时显得“过于广泛”。就拿计算机来说，我能同时对数据库、生成式人工智能、软件工程、游戏开发、虚拟现实、增强现实、混合现实、硬件、无线电感兴趣。而除此之外，天文学、物理学、音乐、经济学、哲学、国际关系、绘画、视频后期、摄影等各式各样的学科也在我的兴趣范围内。如此众多的兴趣，却常常让我不得不压抑自己对于探索新领域的渴望，因为社会似乎普遍把“成功”与“专注”或“深耕”挂钩，他们认为，只有深耕某一领域，才能有所成就。高中时笔记本页脚看到的那句 “门门精通，样样稀松” 的名言，时不时也会像警钟一样警醒着我：一旦你选择了多个方向，便难免成为“泛而不精”的代名词，甚至被贴上“失败者”的标签。&lt;/p&gt;
&lt;p&gt;我曾有一段时间看见那些看似专注于计算机领域，实际上却也在跨界涉猎多个不相干的领域的人，让我感到自己并非孤单。但遗憾的是，从更广阔的环境来看，这样的人还是少。&lt;/p&gt;
&lt;p&gt;今年为准备保研而找导师的时候，这个问题更是赤裸裸地显露了出来。通常来说，导师的研究方向是分得非常细致的，而我以前从来没有想过要只从某个很细的方向去寻求发展，因为我觉得这样会让我在之后的研究生涯中感到枯燥无味，最后陷入后悔。然而在那个时候，我不得不开始做出选择。而身边的人就完全没有这个困惑，仿佛他们都很清晰地知道自己要做什么、要得到什么——并且似乎很早就开始朝着这个方向迈步了。&lt;/p&gt;
&lt;p&gt;跟随着刺猬们的步伐，我发现自己也开始慢慢变成了刺猬。每当发现一个新的感兴趣的技能领域，准备投入时间学习时，兴奋的同时，焦虑也伴随其中。&lt;/p&gt;
&lt;p&gt;难道要想成功，就只能一条路走到黑吗？就必须“一生只做一件事”吗？&lt;/p&gt;
&lt;p&gt;直到几天前，我偶然间看到一个网友的 Blog 推荐了戴森所著的《一面多彩的镜子》并撰写了他自己的看法。出于好奇，我便阅读了一会这本书。戴森在书的开篇提到了狐狸与刺猬的故事：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;“科学家分两类——刺猬和狐狸。我套用了以赛亚·伯林的这一说法，而他又是从古希腊诗人阿基罗库斯那里借用过来的。阿基罗库斯告诉我们，狐狸懂得多种技艺，而刺猬只精通一种。狐狸专广，刺猬专深。狐狸对什么事情都感兴趣，很容易从一个问题转向另一个问题。刺猬只对自己认为重要的几个根本性问题感兴趣，可以连着数年乃至数十年坚持钻研同一组问题。 大多数的伟大发现都是由刺猬作出的，而大多数的小发现是由狐狸作出的。”&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;这让我意识到，自己身上那份“狐狸特质”竟然并非孤立无援。许多伟大的科学家，也都拥有这种跨越多个领域的兴趣和能力，包括约翰·冯·诺伊曼、欧拉、富兰克林、费米、汤斯等传奇人物。更何况这种特质还是具有社会价值的。&lt;/p&gt;
&lt;p&gt;戴森提到：“狐狸对什么事情都感兴趣，很容易从一个问题转向另一个问题。”狐狸的思维是灵活多变的，充满好奇心，他们不受固有框架的限制，探索无垠的未知，会在不断的探索中增加自己的阅历、发现新的可能性。雪莱曾经说过：“⽣命像多彩的玻璃穹顶，将永恒的⽩光染得五彩斑斓” 。我认为，接触不同领域的知识就是给这玻璃穹顶上色的过程。&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://cf.s3.soulter.top/2024-yearly-record/image-19.png&#34; alt=&#34;&#34; /&gt;&lt;br /&gt;
（摄于广西·桂林）&lt;/p&gt;
&lt;p&gt;最后戴森还提到，科学的健康发展同时需要狐狸和刺猬，刺猬深入挖掘事物的本质，狐狸探索我们这个奇妙宇宙的复杂细节。激光就是狐狸物理学家汤斯作出的一个伟大发现。公众受媒体误导，以为伟大的科学家都是刺猬。他预言，在即将到来的 21 世纪里，不管刺猬怎么做，高科技的大众化将给狐狸们提供新的机遇，让他们得以利用有限的手段取得重大的成果。&lt;/p&gt;
&lt;p&gt;不管怎么说，连如此伟大的物理学家都支持狐狸，为我这样的人兜底，那还有什么需要焦虑的呢？这让我不禁感到释然。作为一只狐狸，我将自己的兴趣和自由置于首位，没有任何狐狸或刺猬能够拥有与我相同的方向，因此，我与他们之间的成绩比较自然显得毫无意义。&lt;/p&gt;
&lt;p&gt;这个网友最终反思出自己是一名狐狸，希望我自己也能和他一样，在来年脱离刺猬的评价体系，不再因为 “如果我的刺不比你的长，挖的洞不如你的深，那我便是失败者” 的逻辑而感到焦虑和自卑。&lt;/p&gt;
&lt;h2 id=&#34;仰望星空对人类的意义&#34;&gt;&lt;a class=&#34;markdownIt-Anchor&#34; href=&#34;#仰望星空对人类的意义&#34;&gt;&lt;/a&gt; 仰望星空对人类的意义&lt;/h2&gt;
&lt;p&gt;我提到过我喜欢天文学，但似乎还没有公开谈过我如何喜欢上它和为什么喜欢它。&lt;/p&gt;
&lt;p&gt;如果追溯到有记载的那一刻，这份兴趣大约是在 2013 年之前——也就是我十岁左右。那时，我在 QQ 空间里写下过一篇日志（只是单纯的复制粘贴），它也许是我最早表达天文兴趣的见证之一。&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://cf.s3.soulter.top/2024-yearly-record/image.png&#34; alt=&#34;alt text&#34; /&gt;&lt;/p&gt;
&lt;p&gt;在当时，CCTV 的《纪录》频道曾播放一部名为《了解宇宙如何运行》的纪录片。当时正值中二的我在看到旁白一本正经地说出那些超现实的天文数字——几百亿、上千亿、亿亿亿亿亿并且还不是虚构的时候，顿时就被深深吸引了，开始每天都守在电视机前，期待着这部纪录片的每一集。&lt;/p&gt;
&lt;p&gt;随着对宇宙的了解逐步加深，当时的我对它的神秘感也愈发强烈：原来，宇宙一开始就只是一个质点，经过大爆炸之后开始以光速膨胀；原来，宇宙是一个永远膨胀的浩瀚空间；原来，黑洞这种近乎“bug”的存在，竟能吞噬一切，连光都无法逃脱；原来，太阳将在五十亿年后成为红巨星，它的体积将扩展至接近地球轨道，并最终吞噬掉地球；原来，甚至连科学家自己，也有无法解答的宇宙谜题……当时，有一集谈到我们的银河系中心其实蕴藏了一个巨大的黑洞，使我激动而惊恐，心想人类要完蛋了。但纪录片最后，一位女科学家解释说由于银河系中心实在是离我们太远了，在很大的时间尺度内都不会对人类活动造成影响时，我才释然。&lt;/p&gt;
&lt;p&gt;还有，你能够想象，组成你的左手和你的右手的原子可能是由以前两颗截然不同的超新星爆炸产生的吗？我们都是星之子！&lt;/p&gt;
&lt;p&gt;随着知识面越来越多，问题也就越来越多。电视已经没法满足我了，我开始去家旁边的新华书店寻找天文相关的书阅读。后来，听我的父母说，我当时已经把少年区那一层与天文有关的书都看完了一遍了。并且我能够从当时对我来说比较专业的角度来回答父母和老师的问题：比如黑洞是如何产生的？地球是如何产生的？太阳表面有多热？&lt;/p&gt;
&lt;p&gt;那份对了解宇宙如何运行的渴望，甚至让我在心中悄然埋下了一个远大的梦想——在当时还在上小学的我，确立了一个远大的人生目标，成为一名天文学家。虽然到最后由于各种现实因素没有朝这个方向继续发展，但我一直都对天文感兴趣。成为一名天文爱好者也不赖是吧。&lt;/p&gt;
&lt;p&gt;中学期间，我阅读了霍金的《时间简史》、加来道雄的《平行世界》，收看了很多与天文、时间相关的番剧或者电影，比如《命运石之门》《星际穿越》《火星救援》《地心引力》《超时空接触》《太空旅客》《2012》《盗梦空间》《蝴蝶效应》《太阳浩劫》等等，&lt;s&gt;变得愈加中二。&lt;/s&gt;&lt;/p&gt;
&lt;p&gt;所以，到底是什么原因让我能够这么长时间对天文感兴趣呢？我想，可能就是这里是人类知之甚少的领域，宇宙的浩渺、神秘、幽静给了我无限的施展想象力、天马行空的空间。并且，它给了我一种从更高角度解构人类和人类社会的方式——不管是谁，富人还是穷人，达官贵要还是普通百姓，都不过是由原子组成的物质集合而已。这是更高层次的“人人生而平等”。&lt;/p&gt;
&lt;p&gt;在今年，我和家人进行了为数不多的一次旅行，完成了很多人生中的第一次。&lt;/p&gt;
&lt;p&gt;第一次来到这么大的草原：&lt;/p&gt;
&lt;img src=&#34;https://cf.s3.soulter.top/2024-yearly-record/image-1.png&#34; width=&#34;500&#34; alt=&#34;&#34;&gt;
&lt;p&gt;在狂风呼啸的高海拔草原上，拍到了人生第一张带有明显银河轮廓的照片！&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://cf.s3.soulter.top/2024-yearly-record/image-2.png&#34; alt=&#34;alt text&#34; /&gt;&lt;/p&gt;
&lt;p&gt;推荐一个星座标记网站：&lt;a href=&#34;https://nova.astrometry.net/&#34;&gt;Astronomy&lt;/a&gt;，它能根据照片中的星星从数据库中匹配出星座，然后标记在照片中，甚至解析出你拍照时候的经纬度。比如上面这张照片：&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://cf.s3.soulter.top/2024-yearly-record/image-3.png&#34; alt=&#34;alt text&#34; /&gt;&lt;/p&gt;
&lt;p&gt;第一次看到这个的时候直接惊呼太神奇了。照葫芦画瓢花了三个小时画了一个更美观的：&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://cf.s3.soulter.top/2024-yearly-record/image-4.png&#34; alt=&#34;alt text&#34; /&gt;&lt;/p&gt;
&lt;p&gt;由于正值秋冬之交，银河最亮的一侧——银心已经在北半球看不到了。等到夏天一定要去一个光污染更小的地方再拍一遍！&lt;/p&gt;
&lt;p&gt;在 11 月末，由于我们平常都回宿舍回得很晚，因此和同学玩上了拍星空，找星座。感谢 @Kevin 给我科普了很多星座的知识。我能独自找到猎户座、双子座、昴星团、冬季大三角了！即使是在九级光污染的北京，深夜也能分出星座呢。（推荐 IOS App 《天文通》）&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;img src=&#34;https://cf.s3.soulter.top/2024-yearly-record/image-5.png&#34; alt=&#34;alt text&#34; /&gt;&lt;/th&gt;
&lt;th&gt;&lt;img src=&#34;https://cf.s3.soulter.top/2024-yearly-record/image-6.png&#34; alt=&#34;alt text&#34; /&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;img src=&#34;https://cf.s3.soulter.top/2024-yearly-record/image-7.png&#34; alt=&#34;alt text&#34; /&gt;&lt;/td&gt;
&lt;td&gt;&lt;img src=&#34;https://cf.s3.soulter.top/2024-yearly-record/image-8.png&#34; alt=&#34;alt text&#34; /&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;俯瞰星空，思绪就会飘扬到千万光年之远。你所看到的星光，其实可能是它好几十年前甚至上百年前发出的光了。这些光点背后，究竟隐藏着怎样的宇宙奥秘？在那片星海中，会不会有像我们一样的生命形式？相比之下，地球在宇宙的浩瀚面前，显得何其微不足道。&lt;/p&gt;
&lt;p&gt;俯瞰星空，数以千万计的星辰发出或者反射的星光都会进入你的视野，它们发出的光或许千差万别，却都源自同一个宇宙。它们的历史无声地传递着，尽管我们的眼睛无法辨识每一颗星星的名字，但它们存在的意义，似乎已经穿越了时空，进入了我们的视野。&lt;/p&gt;
&lt;p&gt;俯瞰星空，自然而然就会感受到人类之渺小与无力、自然而然就会认为自己手头上的大事也不过是过眼云烟、自然而然就会认为没必要让自己利益最大化，自然而然就会认为全人类应该团结一心、互帮互助，而不是在自相残杀式的内卷中失去了更为宏伟的目标。与其 “杀敌一千，自损八百”，还不如万众一心，向着成为 &lt;a href=&#34;https://www.youtube.com/watch?v=H7Uyfqi_TE8&#34;&gt;Multiplanetary Species&lt;/a&gt; 的目标进发（虽然说资本家的话不可信。但是有目共睹，马斯克的确在朝着这个目标努力。我和他的愿景出乎意料地一致——让人类文明变成星际文明。所以在 StarShip 这个项目上我是非常希望能够他能成功的）。&lt;/p&gt;
&lt;p&gt;因此，我认为，学会仰望星空，正是一种能提升人类团结与文明程度的途径。通过仰望星空，我们知道地球在宇宙间不过是再渺小不过的一个星体了，就如一条绳上的蚂蚱，人类始终处于命运共同体当中。我们更应珍惜彼此间的联系与合作，不以短视的眼光衡量个人的得失，而是应当在全人类的共同进步中，找到属于自己的价值。&lt;/p&gt;
&lt;h2 id=&#34;这份热情我还能坚持多久&#34;&gt;&lt;a class=&#34;markdownIt-Anchor&#34; href=&#34;#这份热情我还能坚持多久&#34;&gt;&lt;/a&gt; 这份热情我还能坚持多久&lt;/h2&gt;
&lt;p&gt;&lt;img src=&#34;https://cf.s3.soulter.top/2024-yearly-record/image-9.png&#34; alt=&#34;alt text&#34; /&gt;&lt;/p&gt;
&lt;p&gt;最近在疯狂重构自己的某个项目。看到自己多次登上 WakaTime 的编程时间排行榜前列，心情竟然有些失落。我做这些的意义到底是什么呢？&lt;/p&gt;
&lt;p&gt;算下来，我已经把我整个大学的大部分的时间都花在了自己的开源项目上，为保研/找工作等所谓的“人生课题”留出来的时间少之又少。看见极尽功利所能甚至违背法律的行为，即使自己非常反感那种做法，并且永远不可能那样做，但无形对比带来的落差感难免导致心里焦虑万分。&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://cf.s3.soulter.top/2024-yearly-record/image-28.png&#34; alt=&#34;&#34; /&gt;&lt;br /&gt;
（今年的 GitHub Commit 记录）&lt;/p&gt;
&lt;p&gt;星空给予我慰藉。有时这方面想得多了，就会在夜深人静的时候出来看看星空。地球是多么渺小啊。这个时候，就会觉得做给一些他人带来帮助、给社会带来帮助的事才是最正确、最高尚的做法。我真的非常非常敬佩那些无私奉献的人，比如医生（好医生）、志愿者、公益机构、开源工作者们，他们给了我坚持下去的动力。&lt;/p&gt;
&lt;p&gt;当我偶然在知乎上看到一篇介绍 Sindre 的文章，更加坚定了自己的决心。他是一个同时在维护十多个免费开源 Mac App 和 1,000+ NPM 库的全职开源工作者，并且做到了完美的工作与生活的平衡：每天早上 9-10 点起床，去星巴克开始回复邮件列表和 issue，然后开始“hack”（维护开源项目）。直到下午 5 点女朋友下班，他俩会在外面吃个晚饭，然后一起逛商场、看电影、在路边吃小零食…在女朋友睡觉之后再 “hack” 两个小时。我想成为这样的人。&lt;/p&gt;
&lt;p&gt;他在一次访谈里面提到：&lt;/p&gt;
&lt;p&gt;&lt;em&gt;I just like making stuff and I don’t care much about money.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;试想一下如果他生在中国，周围的人会如何看待他？国内社会普遍的的政治正确就是生而为人，必须要搞钱。&lt;/p&gt;
&lt;p&gt;虽然我还没有能力达到想他这样的成就和心态，但却发现我和他的初衷非常相似。&lt;/p&gt;
&lt;p&gt;这份热情我还能坚持多久？好在每当我为此焦虑的时候，总会有人突然站出来支持我。有精神上的，也有物质上的：&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;img src=&#34;https://cf.s3.soulter.top/2024-yearly-record/image-10.png&#34; alt=&#34;alt text&#34; /&gt;&lt;/th&gt;
&lt;th&gt;&lt;img src=&#34;https://cf.s3.soulter.top/2024-yearly-record/image-11.png&#34; alt=&#34;alt text&#34; /&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;img src=&#34;https://cf.s3.soulter.top/2024-yearly-record/image-12.png&#34; alt=&#34;alt text&#34; /&gt;&lt;/td&gt;
&lt;td&gt;&lt;img src=&#34;https://cf.s3.soulter.top/2024-yearly-record/image-13.png&#34; alt=&#34;alt text&#34; /&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;（受于篇幅，这里只贴出近一个月的）&lt;/p&gt;
&lt;p&gt;每当看到这些支持的时候，心里都会迸发出无限的动力。谢谢你们！&lt;/p&gt;
&lt;p&gt;我只是在这个星球上无数个开源人中再普通不过的一个缩影罢了，我做的事情再普通和平凡不过，还远不及那些规模更庞大、影响更深远的开源项目。回望过去三年半，我不后悔于花了这么多时间在这些事情上，虽然不符合国内社会普遍的主流路线，但青春不就是要这样彰显个性吗？至少，人的一生是固定的，在人生的上半场，我拥有更丰富更多元的体验；在人生的下半场，我拥有更多的谈资和更难忘的回忆。在接下来的半年，也是我在大学生涯中最后的半年，我一定要努力在开源领域收获更大的成就，不留下缺憾。&lt;/p&gt;
&lt;h2 id=&#34;其他的一些事情&#34;&gt;&lt;a class=&#34;markdownIt-Anchor&#34; href=&#34;#其他的一些事情&#34;&gt;&lt;/a&gt; 其他的一些事情&lt;/h2&gt;
&lt;h3 id=&#34;人生中第一台装机&#34;&gt;&lt;a class=&#34;markdownIt-Anchor&#34; href=&#34;#人生中第一台装机&#34;&gt;&lt;/a&gt; 人生中第一台装机！&lt;/h3&gt;
&lt;p&gt;在今年暑假之前，我曾向别人宣布——在保研结束之后我一定要自己 DIY 一套主机。保研结束之后，我也兑现了自己的承诺。花了大约一周的时间看评测、选配置。&lt;/p&gt;
&lt;p&gt;我喜欢白色的机箱，于是我给整个配置都尽可能换上了白色——电源、显卡、机箱、风扇、散热器…&lt;/p&gt;
&lt;p&gt;我还喜欢小机箱，小小的不很可爱吗🥰，因此就选了一个 ITX 机箱。&lt;/p&gt;
&lt;p&gt;由于之后要搞 AI，一次还得有一张大显存的卡。综合考虑下，我选了 RTX 4060ti 的 16GB 版本。不得不说，老黄的刀工真精湛啊。16 GB 很好，但是把显存位宽给砍到 128bit 了，想要衡高的显存位宽？换 4070 吧，它有 192bit，然而 4070 又只有 8GB 的版本。想要 16GB 版本？4070s 只有 12GB，4070tis 才有 16GB。&lt;/p&gt;
&lt;p&gt;最终配置单如下：&lt;/p&gt;
&lt;img src=&#34;https://cf.s3.soulter.top/2024-yearly-record/image-14.png&#34; width=&#34;300&#34; alt=&#34;&#34;&gt;
&lt;p&gt;（划掉的文字足以证明我的纠结）&lt;/p&gt;
&lt;img src=&#34;https://cf.s3.soulter.top/2024-yearly-record/image-18.png&#34; width=&#34;500&#34; alt=&#34;&#34;&gt;
&lt;p&gt;最终，在 @nelson 的帮助下，花费了将近 8 个小时才把这个小钢炮搭建完毕。不得不说，ITX 机箱装机真是地狱难度啊。最后的装机成果如下：&lt;/p&gt;
&lt;img src=&#34;https://cf.s3.soulter.top/2024-yearly-record/image-15.png&#34; width=&#34;500&#34; alt=&#34;&#34;&gt;
&lt;p&gt;最后 4060ti 的发热量非常之低，两个风扇完全冗余了。我好像从来没有看见这个显卡的温度上过 60 度，即便是在打游戏。&lt;/p&gt;
&lt;h3 id=&#34;806&#34;&gt;&lt;a class=&#34;markdownIt-Anchor&#34; href=&#34;#806&#34;&gt;&lt;/a&gt; 806&lt;/h3&gt;
&lt;p&gt;我在 806 应该也有两年的时间了。最近发现 806 很多小伙伴都开始维护了自己的 Blog，我的友链数量也从 4 个增加到了 18 个🤗，非常的nice啊，博客文化。之前曾看到中山大学的大佬们人手一个 Blog，并且友链都是同校的同学，因此非常羡慕他们学校的氛围。没想到有一天我科也有这种氛围力。&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://cf.s3.soulter.top/2024-yearly-record/image-16.png&#34; alt=&#34;alt text&#34; /&gt;&lt;br /&gt;
（我的工位一览，小豆泥可爱捏🥰）&lt;/p&gt;
&lt;p&gt;在十一月和队友打了计算机系统能力大赛数据库比赛，拿了不错的成绩。&lt;/p&gt;
&lt;h3 id=&#34;今年去了哪些地方玩&#34;&gt;&lt;a class=&#34;markdownIt-Anchor&#34; href=&#34;#今年去了哪些地方玩&#34;&gt;&lt;/a&gt; 今年去了哪些地方玩&lt;/h3&gt;
&lt;p&gt;今年应该也是我去其他地方玩最多的一年了（雾，好多第一）&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://cf.s3.soulter.top/2024-yearly-record/image-17.png&#34; alt=&#34;alt text&#34; /&gt;&lt;/p&gt;
&lt;p&gt;年初和同学去了日本，先后游览了大阪、京都、札幌、小樽、登别、东京。可能是雪见得少和人少的缘故，还是觉得北海道好玩！想念札幌吃到的汤咖喱，小樽那就像是在动漫里面一样的的小镇风光，登别的温泉。&lt;/p&gt;
&lt;img src=&#34;https://cf.s3.soulter.top/2024-yearly-record/image-20.png&#34; width=&#34;500&#34; alt=&#34;&#34;&gt;
&lt;p&gt;在东京，除了正常需要逛的旅游景点，当然是去圣地巡礼了。主要是巡礼了 MyGO、你的名字、孤独摇滚取景地以及京阿尼工作室。&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;img src=&#34;https://cf.s3.soulter.top/2024-yearly-record/688ecf02ab3da37b5e590ea80d500071.jpeg&#34; alt=&#34;alt text&#34; /&gt;&lt;/th&gt;
&lt;th&gt;&lt;img src=&#34;https://cf.s3.soulter.top/2024-yearly-record/7fd3e71dd21be204601f1beaad9191a2.jpeg&#34; alt=&#34;alt text&#34; /&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;img src=&#34;https://cf.s3.soulter.top/2024-yearly-record/image-24.png&#34; alt=&#34;alt text&#34; /&gt;&lt;/td&gt;
&lt;td&gt;&lt;img src=&#34;https://cf.s3.soulter.top/2024-yearly-record/image-25.png&#34; alt=&#34;alt text&#34; /&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;img src=&#34;https://cf.s3.soulter.top/2024-yearly-record/image-26.png&#34; alt=&#34;alt text&#34; /&gt;&lt;/td&gt;
&lt;td&gt;&lt;img src=&#34;https://cf.s3.soulter.top/2024-yearly-record/image-27.png&#34; alt=&#34;alt text&#34; /&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;篇幅缘故，这里只贴了部分照片。很巧的是在圣地巡礼《MyGO》的时候遇到了同样来圣地巡礼的中国人。最后在最后一张照片所在地点真实地上演了一次名场面（）。&lt;/p&gt;
&lt;p&gt;回国后和家人又去湛江看了一次日落，在海滩边发现了好多寄居蟹，然后抓了好多寄居蟹回家，然而到最后都噶了。&lt;/p&gt;
&lt;img src=&#34;https://cf.s3.soulter.top/2024-yearly-record/image-22.png&#34; width=&#34;500&#34; alt=&#34;&#34;&gt;
&lt;img src=&#34;https://cf.s3.soulter.top/2024-yearly-record/image-23.png&#34; width=&#34;500&#34; alt=&#34;&#34;&gt;
&lt;p&gt;保研期间还去了南京，除了在东南大学答辩之外，印象比较深刻的，还有当时在夫子庙帮一个外国人点了一份茶颜悦色的冰淇淋。当时她在队伍前面，但不知道怎么付款，于是寻求后面的一个人求助，但是那个人不会英语，于是就轮到我上场了。&lt;/p&gt;
&lt;p&gt;十一期间去了湖南南山草原，看到了大草原的景色，拍到了上面如此美丽的星空。值！在回家的途中又去柳州转了一圈。&lt;/p&gt;
&lt;h2 id=&#34;结尾&#34;&gt;&lt;a class=&#34;markdownIt-Anchor&#34; href=&#34;#结尾&#34;&gt;&lt;/a&gt; 结尾&lt;/h2&gt;
&lt;p&gt;体感上今年是我过得最快的一年了吧，这一年最大的关键词应该就是保研了，保研过程从 4 月到 10 月，占了今年的半壁江山。按照时间比例假说，时间感知与人生经历的总长度相关。今后的时间会流逝得越来越快吧。&lt;/p&gt;
&lt;p&gt;在结尾，我想引用一个希腊的神话。代达罗斯的儿子伊卡洛斯在使用蜡制成的羽翼逃离迷宫时，即使在飞行前父亲叮嘱他不要飞得太高，也不要飞得太低，但他还是飞得太高导致羽翼被太阳的热所融化而葬身海洋。在我看来，伊卡洛斯的飞翔更象征着对人类精神的探索与超越。生活的意义正是在于不断体验、不断突破，去别人不敢去的地方，而不管成功和失败。&lt;/p&gt;
&lt;p&gt;这个神话同样在1983 年被诺贝尔物理学奖得主 Subranhmanyan Chandrasekhar 所引用，他说：&lt;strong&gt;“在太阳融化我们翅膀上的蜡之前，看看我们可以飞多高。”&lt;/strong&gt;  我们不应畏惧道路无人。即便坠落，我们也会带着满天的星光，留下属于自己的光辉。&lt;/p&gt;
</content>
        <category term="2024" />
        <category term="总结" />
        <updated>2024-12-16T01:00:00.000Z</updated>
    </entry>
    <entry>
        <id>https://blog.soulter.top/posts/2024-oceanbase-database.html</id>
        <title>OceanBase 数据库内核实现赛 / 自己实现一个数据库</title>
        <link rel="alternate" href="https://blog.soulter.top/posts/2024-oceanbase-database.html"/>
        <content type="html">&lt;h2 id=&#34;引言&#34;&gt;&lt;a class=&#34;markdownIt-Anchor&#34; href=&#34;#引言&#34;&gt;&lt;/a&gt; 引言&lt;/h2&gt;
&lt;p&gt;在十月中旬，我们参加了由 OceanBase 举办的为期半个多月的数据库内核实现赛，我们花了大量的时间实现赛题要求——日均 8 小时。不得不说，这是一个非常能让人 “上瘾” 的比赛 orz。有时候，在熟悉代码、debug 的时候，不知不觉 2 个小时就过去了。在和队友（&lt;a href=&#34;https://blog.bosswnx.xyz&#34;&gt;@Nelson&lt;/a&gt;, &lt;a href=&#34;https://blog.virtualfuture.top/&#34;&gt;@z2z63&lt;/a&gt;）的努力下，我们最终在 2024.11.09 初赛拿了满分，位列全国第 19/1212 名，北京市第 2 名，全校第 1 名。&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://cf.s3.soulter.top/2024-oceanbase-database/image-1.png&#34; alt=&#34;&#34; /&gt;&lt;/p&gt;
&lt;p&gt;这篇文章是我在比赛中的开发记录，由于是边思考边写出来的，未经过后期处理，可能有一些地方表述不清晰。迫于时间和个人对 C++ 的掌握程度有限，有些实现方法会比较抽象，请见谅。&lt;/p&gt;
&lt;p&gt;队友的请参阅：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Nelson：&lt;a href=&#34;https://zhuanlan.zhihu.com/p/5953505884&#34;&gt;OceanBase 2024&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;z2z63：&lt;a href=&#34;https://blog.virtualfuture.top/posts/miniob/&#34;&gt;OceanBase 数据库大赛初赛结束之后&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&#34;数据库的基本思想&#34;&gt;&lt;a class=&#34;markdownIt-Anchor&#34; href=&#34;#数据库的基本思想&#34;&gt;&lt;/a&gt; 数据库的基本思想&lt;/h2&gt;
&lt;p&gt;一个数据库可以是这样:&lt;/p&gt;
&lt;figure class=&#34;highlight python&#34;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&#34;gutter&#34;&gt;&lt;pre&gt;&lt;span class=&#34;line&#34;&gt;1&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;2&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;3&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;4&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;5&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;6&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;7&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;8&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&#34;code&#34;&gt;&lt;pre&gt;&lt;span class=&#34;line&#34;&gt;my_db = &lt;span class=&#34;built_in&#34;&gt;open&lt;/span&gt;(&lt;span class=&#34;string&#34;&gt;&amp;quot;mydb.txt&amp;quot;&lt;/span&gt;, &lt;span class=&#34;string&#34;&gt;&amp;quot;a&amp;quot;&lt;/span&gt;, encoding=&lt;span class=&#34;string&#34;&gt;&amp;quot;utf-8&amp;quot;&lt;/span&gt;)&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;keyword&#34;&gt;def&lt;/span&gt; &lt;span class=&#34;title function_&#34;&gt;insert&lt;/span&gt;(&lt;span class=&#34;params&#34;&gt;query&lt;/span&gt;):&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;	data_list = convert(query)&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;	result = &lt;span class=&#34;string&#34;&gt;&amp;quot; &amp;quot;&lt;/span&gt;.join(data_list)&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;	my_db.write(result)&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;keyword&#34;&gt;def&lt;/span&gt; &lt;span class=&#34;title function_&#34;&gt;get&lt;/span&gt;(&lt;span class=&#34;params&#34;&gt;query&lt;/span&gt;):&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;	&lt;span class=&#34;comment&#34;&gt;# ...&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/figure&gt;
&lt;p&gt;事实上，我刚开始了解数据库时也有这个想法。不过，数据库除了文本类型的数据，还支持很多数据类型，如 int、date、boolean、vector 等。如果全基于字符串存在文件里，势必造成很大的转换开销。此外，还有锁、更新数据、事务等等机制，如果仅靠一个简单的 txt 格式的文件，虽然最终可以实现，但终究不够优雅，且性能 = 💩。&lt;/p&gt;
&lt;p&gt;在 miniob 中，SQL 的执行过程大致如下：&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://cf.s3.soulter.top/20231113155259982/20231113035407628.png&#34; alt=&#34;1&#34; /&gt;&lt;/p&gt;
&lt;p&gt;和所有编程语言一样，当我们发送一个 SQL 请求，程序首先会进行&lt;strong&gt;语法解析&lt;/strong&gt;阶段，生成抽象语法树（AST）。这一步用到了  &lt;code&gt;flex&lt;/code&gt; 和 &lt;code&gt;bison&lt;/code&gt;，前者用于词法分析，将 SQL 语句分割成 Token，后者用于语法分析，将 Token 组合成 AST。在 miniob 中，解析好的数据放在 &lt;code&gt;ParsedSqlNode&lt;/code&gt; 中。&lt;/p&gt;
&lt;p&gt;例如，我们请求: &lt;code&gt;select * from tb where id=1;&lt;/code&gt;，解析后 node 如下：&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://cf.s3.soulter.top/20231113155259982/20231113035456454.png&#34; alt=&#34;2&#34; /&gt;&lt;/p&gt;
&lt;p&gt;解析好数据后，只是 “语法上” 合法，逻辑上是否合法还需要进一步检查，这一步叫做&lt;strong&gt;语义分析&lt;/strong&gt;。miniob 会对 ParsedSQlNode 进行进一步分析，比如检查字段、检查表是否存在、绑定表和表达式、隐式类型转换等等。这一步的结果是生成一个 Statement。&lt;/p&gt;
&lt;p&gt;语义上也没问题之后，我们就进入了&lt;strong&gt;优化&lt;/strong&gt;阶段。优化的目的是尽可能地减少查询的时间和资源消耗。在 miniob 中，优化过程中先生成了逻辑计划，可以把它想象成一个逻辑算子（Logical Operator）构成的树——它表示了整个查询的逻辑结构。每种查询模式（如投影、连接、过滤、排序、索引）都有一个算子，然后组合成逻辑算子树。生成算子树之后，miniob 会对逻辑算子进行 &lt;code&gt;rewrite&lt;/code&gt;，在这一过程中，会将可能的 where 条件下推到底层并对一些结果绝对正确/错误的条件做相关处理，以减少数据在算子中的传递。&lt;/p&gt;
&lt;p&gt;&lt;code&gt;rewrite&lt;/code&gt; 阶段完成之后，就开始生成真正的查询计划，也叫物理查询计划。物理计划会根据 &lt;code&gt;rewrite&lt;/code&gt; 结果的逻辑计划来生成。最终的&lt;strong&gt;执行&lt;/strong&gt;阶段就是按照物理查询计划执行查询，得到结果。&lt;/p&gt;
&lt;p&gt;考虑一个最简单的 Select 请求：select * from tb where id=1。最终生成的物理算子的一种可能的结构如下：&lt;/p&gt;
&lt;figure class=&#34;highlight python&#34;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&#34;gutter&#34;&gt;&lt;pre&gt;&lt;span class=&#34;line&#34;&gt;1&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&#34;code&#34;&gt;&lt;pre&gt;&lt;span class=&#34;line&#34;&gt;（顶层）ProjectPhyOperator -&amp;gt; PredicatePhyOperator -&amp;gt; TableScanPhyOperator（底层）&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/figure&gt;
&lt;p&gt;每一个算子所做的工作都是不同的，比如上面的示例中，最底层的表线性扫描算子将调用某个 &lt;code&gt;Table&lt;/code&gt; 实例中的 &lt;code&gt;FilePageScanner&lt;/code&gt; 来扫描表中的所有记录，得到 &lt;code&gt;Record&lt;/code&gt;，形成 &lt;code&gt;Tuple&lt;/code&gt;，然后中间的过滤算子会根据 where 条件来决定是否选取这个 &lt;code&gt;Tuple&lt;/code&gt;，最顶层的投影算子根据查询字段来筛选出 &lt;code&gt;Tuple&lt;/code&gt; 中的 &lt;code&gt;Cell&lt;/code&gt;(也就是筛选出要显示的列)。最终前端从 ProjectPhyOperator 中拿到选择好的结果并输出。如下图所示：&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://cf.s3.soulter.top/database/20231119124928224.png&#34; alt=&#34;&#34; /&gt;&lt;/p&gt;
&lt;p&gt;如你所见，经典数据库系统中的算子执行过程就像齿轮转动，采用分而治之、层级化思想，将数据一步步处理，最终将结果呈现给用户。&lt;/p&gt;
&lt;h2 id=&#34;实现记录&#34;&gt;&lt;a class=&#34;markdownIt-Anchor&#34; href=&#34;#实现记录&#34;&gt;&lt;/a&gt; 实现记录&lt;/h2&gt;
&lt;h2 id=&#34;update&#34;&gt;&lt;a class=&#34;markdownIt-Anchor&#34; href=&#34;#update&#34;&gt;&lt;/a&gt; Update&lt;/h2&gt;
&lt;p&gt;和 Delete 操作类似，当我们写好语法解析部分和 Statement 之后，单独新建一个 Update 的逻辑算子和物理算子，将要更新的表、字段(FieldMeta)、值(Value) 在算子生成阶段传递给算子。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;FieldMeta 即字段元数据，存储了一个表中某个字段的元数据，如类型、偏移量、长度等。&lt;/li&gt;
&lt;li&gt;Value 即一个确切的值，存储了 Value 类型、具体的值等。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;物理算子的实现上，将底层的 RowRecord 拿到之后，通过其中的 RID 即可在 Table 中找到对应的 Record 记录。然后再通过 FieldMeta 找到字段在 Record 中的偏移量和长度，最后更新为 Value 中的数据即可。如果实现了索引，还需要更新索引，这里我直接先把索引中记录对应的 node 删除，然后重新插入。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Record 类中记录了 &lt;code&gt;RID&lt;/code&gt; 和 &lt;code&gt;Table&lt;/code&gt;，前者记录了这个 Record 的 &lt;code&gt;page_num&lt;/code&gt; 和 &lt;code&gt;slot_num&lt;/code&gt;。一个 &lt;code&gt;File&lt;/code&gt; 含有多个 &lt;code&gt;Page&lt;/code&gt;，一个 &lt;code&gt;Page&lt;/code&gt; 含有多个 &lt;code&gt;Record&lt;/code&gt;。Page 的 Header 中存放了一个  BitMap 用来描述 Page 的空间使用情况，一个 Record 一个 bit。可以通过 &lt;code&gt;slot_num&lt;/code&gt; 来检查这个Record是否曾被删除。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;还应当注意一些语义检测：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;检查 Table 是否存在；&lt;/li&gt;
&lt;li&gt;检查 SET 部分指定的字段名是否存在于指定的 Table 中；&lt;/li&gt;
&lt;li&gt;类型判断和隐式类型转换。&lt;/li&gt;
&lt;li&gt;…&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&#34;隐式类型转换&#34;&gt;&lt;a class=&#34;markdownIt-Anchor&#34; href=&#34;#隐式类型转换&#34;&gt;&lt;/a&gt; 隐式类型转换&lt;/h3&gt;
&lt;p&gt;在 MySQL 中，是支持针对字段和值的隐式类型转换的。例如&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;int cast to float （或者相反）&lt;/li&gt;
&lt;li&gt;chars cast to int （或者相反）&lt;/li&gt;
&lt;li&gt;chars cast to float （或者相反）&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;转换过程的核心代码如下：&lt;/p&gt;
&lt;figure class=&#34;highlight cpp&#34;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&#34;gutter&#34;&gt;&lt;pre&gt;&lt;span class=&#34;line&#34;&gt;1&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;2&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;3&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;4&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;5&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;6&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;7&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;8&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;9&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;10&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;11&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;12&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;13&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;14&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;15&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;16&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;17&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;18&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;19&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;20&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;21&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;22&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&#34;code&#34;&gt;&lt;pre&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;comment&#34;&gt;// 字段类型检查&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;keyword&#34;&gt;for&lt;/span&gt; (&lt;span class=&#34;type&#34;&gt;int&lt;/span&gt; i = &lt;span class=&#34;number&#34;&gt;0&lt;/span&gt;; i &amp;lt; field_metas.&lt;span class=&#34;built_in&#34;&gt;size&lt;/span&gt;(); i++) &amp;#123;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;  &lt;span class=&#34;keyword&#34;&gt;if&lt;/span&gt;(update.targets[i].is_value) &amp;#123;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;    &lt;span class=&#34;keyword&#34;&gt;if&lt;/span&gt; (field_metas[i].&lt;span class=&#34;built_in&#34;&gt;type&lt;/span&gt;() != update.targets[i].value.&lt;span class=&#34;built_in&#34;&gt;attr_type&lt;/span&gt;()) &amp;#123;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;      AttrType to_type = field_metas[i].&lt;span class=&#34;built_in&#34;&gt;type&lt;/span&gt;();&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;      AttrType from_type = update.targets[i].value.&lt;span class=&#34;built_in&#34;&gt;attr_type&lt;/span&gt;();&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;      &lt;span class=&#34;keyword&#34;&gt;if&lt;/span&gt; (to_type == AttrType::INTS &amp;amp;&amp;amp; from_type == AttrType::FLOATS) &amp;#123;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;        &lt;span class=&#34;comment&#34;&gt;// 四舍五入&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;        update.targets[i].value.&lt;span class=&#34;built_in&#34;&gt;set_int&lt;/span&gt;((&lt;span class=&#34;type&#34;&gt;int&lt;/span&gt;)(update.targets[i].value.&lt;span class=&#34;built_in&#34;&gt;get_float&lt;/span&gt;() + &lt;span class=&#34;number&#34;&gt;0.5&lt;/span&gt;));&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;      &amp;#125; &lt;span class=&#34;keyword&#34;&gt;else&lt;/span&gt; &lt;span class=&#34;keyword&#34;&gt;if&lt;/span&gt; (to_type == AttrType::FLOATS &amp;amp;&amp;amp; from_type == AttrType::INTS) &amp;#123;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;        update.targets[i].value.&lt;span class=&#34;built_in&#34;&gt;set_float&lt;/span&gt;((&lt;span class=&#34;type&#34;&gt;float&lt;/span&gt;)update.targets[i].value.&lt;span class=&#34;built_in&#34;&gt;get_int&lt;/span&gt;());&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;      &amp;#125; &lt;span class=&#34;keyword&#34;&gt;else&lt;/span&gt; &lt;span class=&#34;keyword&#34;&gt;if&lt;/span&gt; (to_type == AttrType::CHARS &amp;amp;&amp;amp; from_type == AttrType::INTS) &amp;#123;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;        update.targets[i].value.&lt;span class=&#34;built_in&#34;&gt;set_string&lt;/span&gt;(std::&lt;span class=&#34;built_in&#34;&gt;to_string&lt;/span&gt;(update.targets[i].value.&lt;span class=&#34;built_in&#34;&gt;get_int&lt;/span&gt;()).&lt;span class=&#34;built_in&#34;&gt;c_str&lt;/span&gt;());&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;      &amp;#125; &amp;#123;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;        &lt;span class=&#34;comment&#34;&gt;// ...&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;      &amp;#125; &lt;span class=&#34;keyword&#34;&gt;else&lt;/span&gt; &amp;#123;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;        &lt;span class=&#34;built_in&#34;&gt;LOG_WARN&lt;/span&gt;(&lt;span class=&#34;string&#34;&gt;&amp;quot;type mismatch. to_type=%d, from_type=%d&amp;quot;&lt;/span&gt;, to_type, from_type);&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;        &lt;span class=&#34;keyword&#34;&gt;return&lt;/span&gt; RC::INVALID_ARGUMENT;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;      &amp;#125;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;    &amp;#125;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;  &amp;#125;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;&amp;#125;&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/figure&gt;
&lt;p&gt;对于 chars 类型转 int 或 float 类型，在 MYSQL 中有一个额外的智能的处理逻辑：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;非数字字符不包括小数点。&lt;/li&gt;
&lt;li&gt;当 char* 中遇到第一个非数字字符时，直接返回。若第一个就是非数字字符，那么返回 0。&lt;/li&gt;
&lt;li&gt;当遇到数字字符时，和前面的数字字符组装形成新的值。&lt;/li&gt;
&lt;li&gt;当遇到小数点时，开始加上小数部分。&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&#34;vector向量类型&#34;&gt;&lt;a class=&#34;markdownIt-Anchor&#34; href=&#34;#vector向量类型&#34;&gt;&lt;/a&gt; Vector（向量类型）&lt;/h2&gt;
&lt;p&gt;这题主要实现向量数据库中的向量类型：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;支持创建包含向量类型的表。&lt;/li&gt;
&lt;li&gt;支持插入向量类型的记录。&lt;/li&gt;
&lt;li&gt;支持向量类型的算术运算（加法（+），减法（-），乘法（*），比较运算）。&lt;br /&gt;
在此之上，实现一些简单的距离表达式计算：&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;code&gt;l2_distance&lt;/code&gt;, &lt;code&gt;cosine_distance&lt;/code&gt;, &lt;code&gt;inner_product&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;加类型很好解决，首先在 yacc 和 lex 文件上引入新的类型：&lt;code&gt;VECTOR_T&lt;/code&gt;&lt;/p&gt;
&lt;figure class=&#34;highlight plaintext&#34;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&#34;gutter&#34;&gt;&lt;pre&gt;&lt;span class=&#34;line&#34;&gt;1&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;2&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;3&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;4&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;5&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&#34;code&#34;&gt;&lt;pre&gt;&lt;span class=&#34;line&#34;&gt;// lex&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;VECTOR      RETURN_TOKEN(VECTOR_T);&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;\&amp;quot;\[&amp;#123;WHITE_SAPCE&amp;#125;*[\-]?&amp;#123;DIGIT&amp;#125;+(\.&amp;#123;DIGIT&amp;#125;+)?(&amp;#123;WHITE_SAPCE&amp;#125;*,&amp;#123;WHITE_SAPCE&amp;#125;*[\-]?&amp;#123;DIGIT&amp;#125;+(\.&amp;#123;DIGIT&amp;#125;+)?)*&amp;#123;WHITE_SAPCE&amp;#125;*\]\&amp;quot; yylval-&amp;gt;string=strdup(yytext); RETURN_TOKEN(VECTOR);&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;\&amp;#x27;\[&amp;#123;WHITE_SAPCE&amp;#125;*[\-]?&amp;#123;DIGIT&amp;#125;+(\.&amp;#123;DIGIT&amp;#125;+)?(&amp;#123;WHITE_SAPCE&amp;#125;*,&amp;#123;WHITE_SAPCE&amp;#125;*[\-]?&amp;#123;DIGIT&amp;#125;+(\.&amp;#123;DIGIT&amp;#125;+)?)*&amp;#123;WHITE_SAPCE&amp;#125;*\]\&amp;#x27; yylval-&amp;gt;string=strdup(yytext); RETURN_TOKEN(VECTOR);&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;\[&amp;#123;WHITE_SAPCE&amp;#125;*[\-]?&amp;#123;DIGIT&amp;#125;+(\.&amp;#123;DIGIT&amp;#125;+)?(&amp;#123;WHITE_SAPCE&amp;#125;*,&amp;#123;WHITE_SAPCE&amp;#125;*[\-]?&amp;#123;DIGIT&amp;#125;+(\.&amp;#123;DIGIT&amp;#125;+)?)*&amp;#123;WHITE_SAPCE&amp;#125;*\] yylval-&amp;gt;string=strdup(yytext); RETURN_TOKEN(VECTOR);&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/figure&gt;
&lt;p&gt;这里正则表达式用于匹配 &lt;code&gt;&#39;[1,2,3]&#39;&lt;/code&gt;, &lt;code&gt;&amp;quot;[1,2,3]&amp;quot;&lt;/code&gt;, &lt;code&gt;[1,2,3]&lt;/code&gt; 这三种写法，并忽略了元素之间可能出现的空格，比如&lt;code&gt;[ 1, 2,   3 ]&lt;/code&gt;。&lt;/p&gt;
&lt;figure class=&#34;highlight plaintext&#34;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&#34;gutter&#34;&gt;&lt;pre&gt;&lt;span class=&#34;line&#34;&gt;1&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;2&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;3&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;4&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;5&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;6&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;7&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;8&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;9&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;10&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;11&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;12&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;13&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;14&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;15&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;16&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;17&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;18&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;19&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;20&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;21&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;22&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;23&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;24&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;25&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;26&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;27&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;28&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;29&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;30&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;31&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;32&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;33&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;34&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;35&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;36&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;37&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;38&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;39&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;40&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&#34;code&#34;&gt;&lt;pre&gt;&lt;span class=&#34;line&#34;&gt;insert_stmt:        /*insert   语句的语法解析树*/&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;    INSERT INTO ID VALUES LBRACE value value_list RBRACE &lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;    &amp;#123;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;      $$ = new ParsedSqlNode(SCF_INSERT);&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;      $$-&amp;gt;insertion.relation_name = $3;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;      if ($7 != nullptr) &amp;#123;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;        $$-&amp;gt;insertion.values.swap(*$7);&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;        delete $7;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;      &amp;#125;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;      $$-&amp;gt;insertion.values.emplace_back(*$6);&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;      std::reverse($$-&amp;gt;insertion.values.begin(), $$-&amp;gt;insertion.values.end());&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;      delete $6;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;      free($3);&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;    &amp;#125;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;    ;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;value_list:&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;    /* empty */&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;    &amp;#123;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;      $$ = nullptr;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;    &amp;#125;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;    | COMMA value value_list  &amp;#123; &lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;      if ($3 != nullptr) $$ = $3;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;      else $$ = new std::vector&amp;lt;Value&amp;gt;;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;      $$-&amp;gt;emplace_back(*$2);&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;      delete $2;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;    &amp;#125;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;    ;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;value:&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;    // ...&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;    |&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;    VECTOR &amp;#123;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;      // 如果以双引号或单引号开头，去掉头尾的引号&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;      if ($1[0] == &amp;#x27;\&amp;quot;&amp;#x27; || $1[0] == &amp;#x27;\&amp;#x27;&amp;#x27;) &amp;#123; // 在这里直接去掉头尾的引号，便于后面处理&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;        char *tmp = common::substr($1,1,strlen($1)-2);&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;        $$ = Value::from_vector(tmp); // 在这里解析成 Value 类型。&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;        free(tmp);&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;      &amp;#125; else $$ = Value::from_vector($1);&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;      free($1);&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;    &amp;#125;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;    ;&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/figure&gt;
&lt;p&gt;&lt;code&gt;value.cpp&lt;/code&gt; 文件，&lt;code&gt;from_vector&lt;/code&gt; 将向量字符串传入，解析成 Value 类型。在这里，我使用了 &lt;code&gt;std::vector&lt;/code&gt; 来&lt;strong&gt;暂时&lt;/strong&gt;存储向量的值。&lt;/p&gt;
&lt;figure class=&#34;highlight cpp&#34;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&#34;gutter&#34;&gt;&lt;pre&gt;&lt;span class=&#34;line&#34;&gt;1&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;2&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;3&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;4&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;5&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;6&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;7&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;8&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;9&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;10&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;11&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;12&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;13&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;14&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;15&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;16&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;17&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;18&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;19&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;20&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&#34;code&#34;&gt;&lt;pre&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;comment&#34;&gt;// 从向量字符串创建 Value&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;function&#34;&gt;Value *&lt;span class=&#34;title&#34;&gt;Value::from_vector&lt;/span&gt;&lt;span class=&#34;params&#34;&gt;(&lt;span class=&#34;type&#34;&gt;const&lt;/span&gt; &lt;span class=&#34;type&#34;&gt;char&lt;/span&gt; *s)&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;function&#34;&gt;&lt;/span&gt;&amp;#123;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;  Value *val = &lt;span class=&#34;keyword&#34;&gt;new&lt;/span&gt; &lt;span class=&#34;built_in&#34;&gt;Value&lt;/span&gt;();&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;  val-&amp;gt;&lt;span class=&#34;built_in&#34;&gt;set_vector&lt;/span&gt;(s);&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;  &lt;span class=&#34;keyword&#34;&gt;return&lt;/span&gt; val;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;&amp;#125;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;function&#34;&gt;&lt;span class=&#34;type&#34;&gt;void&lt;/span&gt; &lt;span class=&#34;title&#34;&gt;Value::set_vector&lt;/span&gt;&lt;span class=&#34;params&#34;&gt;(&lt;span class=&#34;type&#34;&gt;const&lt;/span&gt; &lt;span class=&#34;type&#34;&gt;char&lt;/span&gt; *s)&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;function&#34;&gt;&lt;/span&gt;&amp;#123;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;  &lt;span class=&#34;built_in&#34;&gt;reset&lt;/span&gt;();&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;  attr_type_     = AttrType::VECTORS;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;  string vector_ = s;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;  vector_        = vector_.&lt;span class=&#34;built_in&#34;&gt;substr&lt;/span&gt;(&lt;span class=&#34;number&#34;&gt;1&lt;/span&gt;, vector_.&lt;span class=&#34;built_in&#34;&gt;size&lt;/span&gt;() - &lt;span class=&#34;number&#34;&gt;2&lt;/span&gt;);  &lt;span class=&#34;comment&#34;&gt;// 去掉中括号&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;  vector&amp;lt;&lt;span class=&#34;type&#34;&gt;float&lt;/span&gt;&amp;gt;      vec;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;  &lt;span class=&#34;function&#34;&gt;std::istringstream &lt;span class=&#34;title&#34;&gt;iss&lt;/span&gt;&lt;span class=&#34;params&#34;&gt;(vector_)&lt;/span&gt;&lt;/span&gt;;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;  string             token;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;  &lt;span class=&#34;keyword&#34;&gt;while&lt;/span&gt; (std::&lt;span class=&#34;built_in&#34;&gt;getline&lt;/span&gt;(iss, token, &lt;span class=&#34;string&#34;&gt;&amp;#x27;,&amp;#x27;&lt;/span&gt;)) vec.&lt;span class=&#34;built_in&#34;&gt;push_back&lt;/span&gt;(std::&lt;span class=&#34;built_in&#34;&gt;stof&lt;/span&gt;(token));&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;  value_.vector_value_ = &lt;span class=&#34;keyword&#34;&gt;new&lt;/span&gt; &lt;span class=&#34;built_in&#34;&gt;vector&lt;/span&gt;&amp;lt;&lt;span class=&#34;type&#34;&gt;float&lt;/span&gt;&amp;gt;(vec);&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;  length_              = value_.vector_value_-&amp;gt;&lt;span class=&#34;built_in&#34;&gt;size&lt;/span&gt;();&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;&amp;#125;&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/figure&gt;
&lt;p&gt;考虑到从顶层的视角来看，向量类型的长度就是向量的维度，因此这里 length_ 的值就是向量的维度而不是 &lt;code&gt;sizeof(float)*向量维度&lt;/code&gt;。但这也意味着需要在底层做一些额外的处理，把长度转回 &lt;code&gt;sizeof(float)*向量维度&lt;/code&gt;。&lt;/p&gt;
&lt;p&gt;由于用户存入的向量的长度不固定，这里我将 &lt;code&gt;nan&lt;/code&gt; 作为结束的标志（在存入长度&amp;lt;创建时设置的最大长度 的情况下）。&lt;/p&gt;
&lt;figure class=&#34;highlight c++&#34;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&#34;gutter&#34;&gt;&lt;pre&gt;&lt;span class=&#34;line&#34;&gt;1&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;2&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;3&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;4&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;5&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;6&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;7&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;8&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;9&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;10&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;11&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;12&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;13&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;14&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;15&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;16&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;17&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;18&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;19&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;20&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;21&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&#34;code&#34;&gt;&lt;pre&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;function&#34;&gt;&lt;span class=&#34;type&#34;&gt;const&lt;/span&gt; &lt;span class=&#34;type&#34;&gt;char&lt;/span&gt; *&lt;span class=&#34;title&#34;&gt;Value::data&lt;/span&gt;&lt;span class=&#34;params&#34;&gt;()&lt;/span&gt; &lt;span class=&#34;type&#34;&gt;const&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;function&#34;&gt;&lt;/span&gt;&amp;#123;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;  &lt;span class=&#34;keyword&#34;&gt;switch&lt;/span&gt; (attr_type_) &amp;#123;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;    &lt;span class=&#34;comment&#34;&gt;// ...&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;    &lt;span class=&#34;keyword&#34;&gt;case&lt;/span&gt; AttrType::VECTORS: &amp;#123;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;      &lt;span class=&#34;type&#34;&gt;char&lt;/span&gt; *data   = &lt;span class=&#34;keyword&#34;&gt;new&lt;/span&gt; &lt;span class=&#34;type&#34;&gt;char&lt;/span&gt;[&lt;span class=&#34;built_in&#34;&gt;sizeof&lt;/span&gt;(&lt;span class=&#34;type&#34;&gt;float&lt;/span&gt;) * length_ + &lt;span class=&#34;built_in&#34;&gt;sizeof&lt;/span&gt;(&lt;span class=&#34;type&#34;&gt;float&lt;/span&gt;)];&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;      &lt;span class=&#34;type&#34;&gt;int&lt;/span&gt;   offset = &lt;span class=&#34;number&#34;&gt;0&lt;/span&gt;;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;      &lt;span class=&#34;keyword&#34;&gt;for&lt;/span&gt; (&lt;span class=&#34;keyword&#34;&gt;auto&lt;/span&gt; &amp;amp;f : *value_.vector_value_) &amp;#123;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;        &lt;span class=&#34;built_in&#34;&gt;memcpy&lt;/span&gt;(data + offset, &amp;amp;f, &lt;span class=&#34;built_in&#34;&gt;sizeof&lt;/span&gt;(&lt;span class=&#34;type&#34;&gt;float&lt;/span&gt;));&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;        offset += &lt;span class=&#34;built_in&#34;&gt;sizeof&lt;/span&gt;(&lt;span class=&#34;type&#34;&gt;float&lt;/span&gt;);&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;      &amp;#125;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;      &lt;span class=&#34;comment&#34;&gt;// 用 nan 标记数据结束&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;      &lt;span class=&#34;type&#34;&gt;float&lt;/span&gt; nan = std::numeric_limits&amp;lt;&lt;span class=&#34;type&#34;&gt;float&lt;/span&gt;&amp;gt;::&lt;span class=&#34;built_in&#34;&gt;quiet_NaN&lt;/span&gt;();&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;      &lt;span class=&#34;built_in&#34;&gt;memcpy&lt;/span&gt;(data + offset, &amp;amp;nan, &lt;span class=&#34;built_in&#34;&gt;sizeof&lt;/span&gt;(&lt;span class=&#34;type&#34;&gt;float&lt;/span&gt;));&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;      &lt;span class=&#34;keyword&#34;&gt;return&lt;/span&gt; data;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;    &amp;#125;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;    &lt;span class=&#34;keyword&#34;&gt;default&lt;/span&gt;: &amp;#123;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;      &lt;span class=&#34;keyword&#34;&gt;return&lt;/span&gt; (&lt;span class=&#34;type&#34;&gt;const&lt;/span&gt; &lt;span class=&#34;type&#34;&gt;char&lt;/span&gt; *)&amp;amp;value_;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;    &amp;#125; &lt;span class=&#34;keyword&#34;&gt;break&lt;/span&gt;;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;  &amp;#125;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;&amp;#125;&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/figure&gt;
&lt;p&gt;两个向量之间以&lt;code&gt;字典序&lt;/code&gt;的方式比较大小：&lt;/p&gt;
&lt;figure class=&#34;highlight c++&#34;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&#34;gutter&#34;&gt;&lt;pre&gt;&lt;span class=&#34;line&#34;&gt;1&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;2&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;3&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;4&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;5&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;6&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;7&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;8&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;9&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;10&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;11&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;12&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;13&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;14&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;15&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;16&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;17&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&#34;code&#34;&gt;&lt;pre&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;function&#34;&gt;&lt;span class=&#34;type&#34;&gt;int&lt;/span&gt; &lt;span class=&#34;title&#34;&gt;VectorType::compare&lt;/span&gt;&lt;span class=&#34;params&#34;&gt;(&lt;span class=&#34;type&#34;&gt;const&lt;/span&gt; Value &amp;amp;left, &lt;span class=&#34;type&#34;&gt;const&lt;/span&gt; Value &amp;amp;right)&lt;/span&gt; &lt;span class=&#34;type&#34;&gt;const&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;function&#34;&gt;&lt;/span&gt;&amp;#123;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;  &lt;span class=&#34;built_in&#34;&gt;ASSERT&lt;/span&gt;(left.&lt;span class=&#34;built_in&#34;&gt;attr_type&lt;/span&gt;() == AttrType::VECTORS &amp;amp;&amp;amp; right.&lt;span class=&#34;built_in&#34;&gt;attr_type&lt;/span&gt;() == AttrType::VECTORS, &lt;span class=&#34;string&#34;&gt;&amp;quot;invalid type&amp;quot;&lt;/span&gt;);&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;  &lt;span class=&#34;comment&#34;&gt;// 以字典序比较两个 vector&amp;lt;float&amp;gt; 的大小&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;  vector&amp;lt;&lt;span class=&#34;type&#34;&gt;float&lt;/span&gt;&amp;gt; left_ = left.&lt;span class=&#34;built_in&#34;&gt;get_vector&lt;/span&gt;();&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;  vector&amp;lt;&lt;span class=&#34;type&#34;&gt;float&lt;/span&gt;&amp;gt; right_ = right.&lt;span class=&#34;built_in&#34;&gt;get_vector&lt;/span&gt;();&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;  &lt;span class=&#34;type&#34;&gt;int&lt;/span&gt; idx = &lt;span class=&#34;number&#34;&gt;0&lt;/span&gt;;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;  &lt;span class=&#34;keyword&#34;&gt;while&lt;/span&gt; (idx &amp;lt; left_.&lt;span class=&#34;built_in&#34;&gt;size&lt;/span&gt;() &amp;amp;&amp;amp; idx &amp;lt; right_.&lt;span class=&#34;built_in&#34;&gt;size&lt;/span&gt;()) &amp;#123;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;    &lt;span class=&#34;keyword&#34;&gt;if&lt;/span&gt; (left_[idx] &amp;lt; right_[idx]) &lt;span class=&#34;keyword&#34;&gt;return&lt;/span&gt; &lt;span class=&#34;number&#34;&gt;-1&lt;/span&gt;;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;    &lt;span class=&#34;keyword&#34;&gt;else&lt;/span&gt; &lt;span class=&#34;keyword&#34;&gt;if&lt;/span&gt; (left_[idx] &amp;gt; right_[idx]) &lt;span class=&#34;keyword&#34;&gt;return&lt;/span&gt; &lt;span class=&#34;number&#34;&gt;1&lt;/span&gt;;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;    ++idx;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;  &amp;#125;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;  &lt;span class=&#34;keyword&#34;&gt;if&lt;/span&gt; (idx &amp;lt; left_.&lt;span class=&#34;built_in&#34;&gt;size&lt;/span&gt;()) &lt;span class=&#34;keyword&#34;&gt;return&lt;/span&gt; &lt;span class=&#34;number&#34;&gt;1&lt;/span&gt;;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;  &lt;span class=&#34;keyword&#34;&gt;else&lt;/span&gt; &lt;span class=&#34;keyword&#34;&gt;if&lt;/span&gt; (idx &amp;lt; right_.&lt;span class=&#34;built_in&#34;&gt;size&lt;/span&gt;()) &lt;span class=&#34;keyword&#34;&gt;return&lt;/span&gt; &lt;span class=&#34;number&#34;&gt;-1&lt;/span&gt;;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;  &lt;span class=&#34;keyword&#34;&gt;return&lt;/span&gt; &lt;span class=&#34;number&#34;&gt;0&lt;/span&gt;;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;&amp;#125;&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/figure&gt;
&lt;p&gt;实现三个距离函数：&lt;/p&gt;
&lt;p&gt;主要是添加了一个 &lt;code&gt;VectorDistanceExpr&lt;/code&gt; 类型（继承自 &lt;code&gt;Expression&lt;/code&gt;）。&lt;/p&gt;
&lt;p&gt;同样需要现在 yacc 和 lex 文件中补充新的 Token 类型，然后在解析语法树时创建 &lt;code&gt;VectorDistanceExpr&lt;/code&gt;  ，不再赘述。&lt;/p&gt;
&lt;p&gt;在 &lt;code&gt;VetorDistanceExpr&lt;/code&gt; 的 &lt;code&gt;get_value&lt;/code&gt; 方法中，添加相关的计算逻辑。&lt;/p&gt;
&lt;figure class=&#34;highlight c++&#34;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&#34;gutter&#34;&gt;&lt;pre&gt;&lt;span class=&#34;line&#34;&gt;1&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;2&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;3&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;4&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;5&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;6&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;7&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;8&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;9&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;10&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;11&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;12&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;13&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;14&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;15&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;16&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;17&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;18&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&#34;code&#34;&gt;&lt;pre&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;function&#34;&gt;RC &lt;span class=&#34;title&#34;&gt;VectorDistanceExpr::get_value&lt;/span&gt;&lt;span class=&#34;params&#34;&gt;(&lt;span class=&#34;type&#34;&gt;const&lt;/span&gt; Tuple &amp;amp;tuple, Value &amp;amp;value)&lt;/span&gt; &lt;span class=&#34;type&#34;&gt;const&lt;/span&gt; &lt;/span&gt;&amp;#123;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;  Value left_value;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;  Value right_value;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;  &lt;span class=&#34;comment&#34;&gt;// ... 根据 tuple 得到 value 并检查合法性&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;  &lt;span class=&#34;keyword&#34;&gt;switch&lt;/span&gt; (type_) &amp;#123;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;    &lt;span class=&#34;keyword&#34;&gt;case&lt;/span&gt; Type::L2_DISTANCE: &amp;#123;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;      &lt;span class=&#34;type&#34;&gt;float&lt;/span&gt; sum = &lt;span class=&#34;number&#34;&gt;0&lt;/span&gt;;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;      &lt;span class=&#34;keyword&#34;&gt;for&lt;/span&gt; (&lt;span class=&#34;type&#34;&gt;size_t&lt;/span&gt; i = &lt;span class=&#34;number&#34;&gt;0&lt;/span&gt;; i &amp;lt; left.&lt;span class=&#34;built_in&#34;&gt;size&lt;/span&gt;(); i++) &amp;#123;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;        sum += (left[i] - right[i]) * (left[i] - right[i]);&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;      &amp;#125;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;      sum = &lt;span class=&#34;built_in&#34;&gt;sqrt&lt;/span&gt;(sum);&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;      value.&lt;span class=&#34;built_in&#34;&gt;set_float&lt;/span&gt;(sum);&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;    &amp;#125; &lt;span class=&#34;keyword&#34;&gt;break&lt;/span&gt;;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;    &lt;span class=&#34;comment&#34;&gt;// ... 其余两个&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;  &amp;#125;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;  &lt;span class=&#34;comment&#34;&gt;// ..&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;  &lt;span class=&#34;keyword&#34;&gt;return&lt;/span&gt; RC::SUCCESS;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;&amp;#125;&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/figure&gt;
&lt;h2 id=&#34;join&#34;&gt;&lt;a class=&#34;markdownIt-Anchor&#34; href=&#34;#join&#34;&gt;&lt;/a&gt; Join&lt;/h2&gt;
&lt;p&gt;miniob 已经实现了基本的多表联查功能，主要使用了 &lt;code&gt;Nested-Loop Join&lt;/code&gt; 算子。我们的工作主要是实现 &lt;code&gt;INNER JOIN&lt;/code&gt; 语法解析即可。&lt;/p&gt;
&lt;p&gt;首先在 yacc 文件下，补充 Join 相关的语法。唯一需要注意的是由于语法树是递归顺序解析的，因此需要将解析得到的 join_list reverse 一下。&lt;/p&gt;
&lt;p&gt;在 &lt;code&gt;parse_defs.cpp&lt;/code&gt; 文件下加一个 &lt;code&gt;JoinSqlNode&lt;/code&gt;:&lt;/p&gt;
&lt;figure class=&#34;highlight cpp&#34;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&#34;gutter&#34;&gt;&lt;pre&gt;&lt;span class=&#34;line&#34;&gt;1&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;2&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;3&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;4&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;5&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&#34;code&#34;&gt;&lt;pre&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;keyword&#34;&gt;struct&lt;/span&gt; &lt;span class=&#34;title class_&#34;&gt;JoinSqlNode&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;&amp;#123;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;  RelationSqlNode relation;  &lt;span class=&#34;comment&#34;&gt;///&amp;lt; Relation to join with&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;  std::vector&amp;lt;ConditionSqlNode&amp;gt; conditions;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;&amp;#125;;&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/figure&gt;
&lt;p&gt;然后理论上就已经实现了。&lt;/p&gt;
&lt;p&gt;结果发现一直不过测，开始还以为是 &lt;code&gt;Nested-Loop Join&lt;/code&gt; 太慢了，花了一些时间重新实现了 &lt;code&gt;Block Nested-Loop Join&lt;/code&gt;，结果仍然不过。最后发现是在 Optimize 阶段的 &lt;code&gt;Predicates&lt;/code&gt; 算子下推步骤时，原框架在使用迭代器遍历 &lt;code&gt;vector&lt;/code&gt; 时，删除了元素但没有更新迭代器，导致了错误。。。。不得不吐槽 miniob 的代码质量。。。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;以及最后在实现与 OR 有关的的下推步骤时，发现判断是否要将 &lt;code&gt;ComparisonExpr&lt;/code&gt; 下推的逻辑都有问题。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3 id=&#34;block-nested-loop-join&#34;&gt;&lt;a class=&#34;markdownIt-Anchor&#34; href=&#34;#block-nested-loop-join&#34;&gt;&lt;/a&gt; Block Nested-Loop Join&lt;/h3&gt;
&lt;p&gt;既然提到了 BNL，那就简单介绍一下 BNL 的原理。&lt;/p&gt;
&lt;p&gt;Block Nested-Loop Join（BNL） 是 Nested-Loop Join（NLJ） 的一种优化，其思想是将左表（外部表）分块（缓冲），来减少对右表的访问次数。比如我们有两个表 A 和 B，A 有 1000 行，B 有 10000 行，我们可以将 A 分成 10 个块，每个块 100 行，然后对于每个块，我们只需要扫描 B 一次，而不是 NLJ 的 1000 次。&lt;/p&gt;
&lt;p&gt;从原理上来说，实现起来很容易，加一个 vector 存储左表的记录（tuple）作为一个块，然后再做一些处理即可。实际上，在 miniob 中，tuple 在算子中是以指针的形式传递的，而不是复制。这就导致在下一次算子循环中，原来存的指针会指向新的 tuple，这就导致了错误。处理起来比较麻烦，所以最终也没有用上这个优化。&lt;/p&gt;
&lt;h2 id=&#34;simple-sub-query&#34;&gt;&lt;a class=&#34;markdownIt-Anchor&#34; href=&#34;#simple-sub-query&#34;&gt;&lt;/a&gt; Simple Sub Query&lt;/h2&gt;
&lt;h3 id=&#34;需求分析&#34;&gt;&lt;a class=&#34;markdownIt-Anchor&#34; href=&#34;#需求分析&#34;&gt;&lt;/a&gt; 需求分析&lt;/h3&gt;
&lt;p&gt;在简单子查询中，我们要需要在 WHERE 子句中支持 SELECT 语句，如下：&lt;/p&gt;
&lt;figure class=&#34;highlight json&#34;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&#34;gutter&#34;&gt;&lt;pre&gt;&lt;span class=&#34;line&#34;&gt;1&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&#34;code&#34;&gt;&lt;pre&gt;&lt;span class=&#34;line&#34;&gt;SELECT * FROM tb WHERE tb.id IN (SELECT tb2.id FROM tb2);&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/figure&gt;
&lt;p&gt;除此之外：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;添加针对 IN、NOT IN、EXISTS、NOT EXISTS 的支持。&lt;/li&gt;
&lt;li&gt;添加对常量值列表的支持。如 &lt;code&gt;SELECT * FROM tb WHERE tb.id IN (1, 2, 3);&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;需要注意的是，子查询的一些特性：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;子查询的结果集只能有&lt;strong&gt;一列&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;对于 IN、NOT IN、EXISTS、NOT EXISTS，子查询的结果集可以是&lt;strong&gt;任意行数&lt;/strong&gt;，但是对于除此之外的情况，子查询的结果集只能有&lt;strong&gt;一行&lt;/strong&gt;。(MYSQL ERROR 1242 (21000))&lt;/li&gt;
&lt;li&gt;常量值列表仅会用在 IN、NOT IN、EXISTS、NOT EXISTS 中。&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&#34;实现&#34;&gt;&lt;a class=&#34;markdownIt-Anchor&#34; href=&#34;#实现&#34;&gt;&lt;/a&gt; 实现&lt;/h3&gt;
&lt;p&gt;考虑到子查询在 WHERE 中，我们就将其全部放在 Conditions 中处理。&lt;/p&gt;
&lt;figure class=&#34;highlight c++&#34;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&#34;gutter&#34;&gt;&lt;pre&gt;&lt;span class=&#34;line&#34;&gt;1&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;2&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;3&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;4&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;5&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&#34;code&#34;&gt;&lt;pre&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;keyword&#34;&gt;struct&lt;/span&gt; &lt;span class=&#34;title class_&#34;&gt;ConditionSqlNode&lt;/span&gt; &amp;#123;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;  std::unique_ptr&amp;lt;Expression&amp;gt; left_expr;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;  std::unique_ptr&amp;lt;Expression&amp;gt; right_expr;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;  CompOp                      comp_op;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;&amp;#125;;&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/figure&gt;
&lt;p&gt;好在 Condition 的左右两边都被抽象成了 Expression，我们只需要在 Expression 类下加入一个新的类型 &lt;code&gt;SubqueryExpr&lt;/code&gt; 即可。然后在 expression 的语法解析树中加一个 &lt;code&gt;LBRACE select_stmt RBRACE&lt;/code&gt; 的语法规则，就能完成语法解析。&lt;/p&gt;
&lt;p&gt;还有常量值列表，就更容易了，开一个 &lt;code&gt;ValueListExpr&lt;/code&gt; 类型，然后存 &lt;code&gt;std::vector&amp;lt;Value&amp;gt; values_;&lt;/code&gt; 即可。&lt;/p&gt;
&lt;p&gt;对于 EXISTS、NOT EXISTS，由于它的查询 SQL 是这样的：&lt;code&gt;SELECT * FROM tb WHERE EXISTS (SELECT * FROM tb2 WHERE tb.id = tb2.id);&lt;/code&gt; 它是没有 left expression 的！在这里，为了统一性，我单独创了一个 &lt;code&gt;SpecialPlaceholderExpr&lt;/code&gt; 类型，用于表示没有 left 的情况，它没有实现任何方法。&lt;/p&gt;
&lt;p&gt;那该如何“查”这个子查询呢？我在创建主表的 SelectStmt 的时候，对其 &lt;code&gt;conditions&lt;/code&gt; 做了检测，如果监测到 &lt;code&gt;SubqueryExpr&lt;/code&gt;，就对其下的 &lt;code&gt;SelectSqlNodes&lt;/code&gt; 再创建一次 SelectStmt。这样就能递归地查找到所有的子查询并创建 stmt。&lt;/p&gt;
&lt;p&gt;同样地，在逻辑查询计划和物理查询计划中，也提取出 &lt;code&gt;SubqueryExpr&lt;/code&gt; 并创建对应的逻辑计划算子和物理计划算子。所有的子查询顶层算子都暂时存储在自身的 &lt;code&gt;SubqueryExpr&lt;/code&gt; 中备用。&lt;/p&gt;
&lt;p&gt;到了外表执行物理算子了，由于子查询在 Condition 中，最终外表的 &lt;code&gt;ComparitorExpr::get_value()&lt;/code&gt; 方法中的 &lt;code&gt;left&lt;/code&gt; 或者 &lt;code&gt;right&lt;/code&gt; 可能是 &lt;code&gt;SubqueryExpr&lt;/code&gt;。我们需要在这里判断子查询的情况。&lt;/p&gt;
&lt;p&gt;一共有三种情况：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;left_ 是子查询 且 right_ 是子查询&lt;/li&gt;
&lt;li&gt;left_ 是子查询 或 right_ 是子查询&lt;/li&gt;
&lt;li&gt;left_ 是常量值列表 或 right_ 是常量值列表&lt;/li&gt;
&lt;li&gt;其他情况&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;其他情况也就是 miniob 的默认处理方式。对于第一种情况，由于子查询仅能返回一行，我们额外需要注意在对 left/right 调用 &lt;code&gt;get_value()&lt;/code&gt; 方法后，还要再调用一次，以保证其只返回一行，如果第二次 &lt;code&gt;get_value()&lt;/code&gt; 成功了，就直接报错。&lt;/p&gt;
&lt;p&gt;第二和第三种情况稍微复杂一些，由于 IN、NOT IN、EXISTS、NOT EXISTS 可能需要遍历整个子查询的结果集，我们需要开一个循环来对 left/right 调用 &lt;code&gt;get_value()&lt;/code&gt; 方法。并在这个循环中写好一些 fast-break 的逻辑。比如：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;对于 IN，如果在子查询中找到了第一个匹配的，就直接返回 true/false。&lt;/li&gt;
&lt;li&gt;对于 EXISTS，如果在子查询中找到了一个匹配的，就直接返回 true/false。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;尤其注意对其他比较操作符的处理，比如 &lt;code&gt;=&lt;/code&gt;, &lt;code&gt;!=&lt;/code&gt;, &lt;code&gt;&amp;gt;&lt;/code&gt;, &lt;code&gt;&amp;lt;&lt;/code&gt; 等等，子查询都只能返回一行。&lt;/p&gt;
&lt;p&gt;尤其注意对 &lt;code&gt;SpecialPlaceholderExpr&lt;/code&gt; 的处理，如果 left 是这个表达式类型，那么不需要对其调用 &lt;code&gt;get_value()&lt;/code&gt; 方法。&lt;/p&gt;
&lt;p&gt;在 SubqueryExpr 的 &lt;code&gt;get_value()&lt;/code&gt; 方法中，如果检测到物理算子还没有 open，就先 open，然后调用 &lt;code&gt;get_next()&lt;/code&gt; 方法得到 value，就大功告成了。&lt;/p&gt;
&lt;p&gt;总体上简单子查询的大体逻辑就是这样，很多的细节限于篇幅都没有展开。&lt;/p&gt;
&lt;p&gt;我很想说的一点是为了实现子查询的异常处理，比如子查询不能返回多行，我们需要修改一下前端部分。在 &lt;code&gt;plain_communicator.cpp&lt;/code&gt; 文件下，记录了将表头、tuple 打印到输出流的逻辑。由于针对子查询不能返回多行的检测是在执行物理算子时动态检查的，也就是说，在获取整个查询的第一行结果时，检测到子查询输出了第二行时，表头已经写入输出流了。所以，我们应该将写入表头的操作放在“成功获取到整个查询的第一行”之后。&lt;/p&gt;
&lt;p&gt;此外还有一点，由于这里子查询的实现重复调用了物理算子的 open 和 close，因此一定要注意保证在重复关闭/打开算子具有幂等性——即多次调用不会产生副作用。说人话就是&lt;strong&gt;记得在 close 时清理算子使用的资源&lt;/strong&gt;。我在写子查询的时候就遇到了这个问题，由于 OrderBy 关闭算子时没有将 tuple_idx_ (用于标记当前 emit 到第几个 tuple)置 0、tuple_ 置空，导致了重复调用物理算子时出现得到的结果不一样：&lt;/p&gt;
&lt;figure class=&#34;highlight plaintext&#34;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&#34;gutter&#34;&gt;&lt;pre&gt;&lt;span class=&#34;line&#34;&gt;1&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;2&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;3&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;4&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;5&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;6&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;7&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;8&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;9&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;10&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;11&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;12&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;13&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;14&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;15&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;16&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;17&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;18&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&#34;code&#34;&gt;&lt;pre&gt;&lt;span class=&#34;line&#34;&gt;tb:&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;+----+----+&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;| id | name |&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;+----+----+&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;| 0  | a   |&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;| 1  | b   |&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;| 0  | c   |&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;+----+----+&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;tb2:&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;+----+&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;| id |&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;+----+&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;| 0  |&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;| 1  |&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;| 5  |&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;| 2  |&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;+----+&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/figure&gt;
&lt;p&gt;查询语句是：&lt;/p&gt;
&lt;figure class=&#34;highlight sql&#34;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&#34;gutter&#34;&gt;&lt;pre&gt;&lt;span class=&#34;line&#34;&gt;1&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&#34;code&#34;&gt;&lt;pre&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;keyword&#34;&gt;SELECT&lt;/span&gt; &lt;span class=&#34;operator&#34;&gt;*&lt;/span&gt; &lt;span class=&#34;keyword&#34;&gt;FROM&lt;/span&gt; tb &lt;span class=&#34;keyword&#34;&gt;WHERE&lt;/span&gt; tb.id &lt;span class=&#34;operator&#34;&gt;&amp;gt;&lt;/span&gt; (&lt;span class=&#34;keyword&#34;&gt;SELECT&lt;/span&gt; tb2.id &lt;span class=&#34;keyword&#34;&gt;FROM&lt;/span&gt; tb2);&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/figure&gt;
&lt;p&gt;正确情况下会返回一行，但是由于上述问题在将 tb.id=1 与 tb2 比较时，跳过了 tb2.id=0，导致没有返回结果。&lt;/p&gt;
&lt;h2 id=&#34;complicated-sub-query&#34;&gt;&lt;a class=&#34;markdownIt-Anchor&#34; href=&#34;#complicated-sub-query&#34;&gt;&lt;/a&gt; Complicated Sub Query&lt;/h2&gt;
&lt;h3 id=&#34;需求分析-2&#34;&gt;&lt;a class=&#34;markdownIt-Anchor&#34; href=&#34;#需求分析-2&#34;&gt;&lt;/a&gt; 需求分析&lt;/h3&gt;
&lt;p&gt;复杂子查询和简单子查询类似，但是多了子表之间的嵌套。比如：&lt;/p&gt;
&lt;figure class=&#34;highlight sql&#34;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&#34;gutter&#34;&gt;&lt;pre&gt;&lt;span class=&#34;line&#34;&gt;1&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&#34;code&#34;&gt;&lt;pre&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;keyword&#34;&gt;SELECT&lt;/span&gt; &lt;span class=&#34;operator&#34;&gt;*&lt;/span&gt; &lt;span class=&#34;keyword&#34;&gt;FROM&lt;/span&gt; tb &lt;span class=&#34;keyword&#34;&gt;WHERE&lt;/span&gt; tb.id &lt;span class=&#34;operator&#34;&gt;=&lt;/span&gt; (&lt;span class=&#34;keyword&#34;&gt;SELECT&lt;/span&gt; tb2.id &lt;span class=&#34;keyword&#34;&gt;FROM&lt;/span&gt; tb2 &lt;span class=&#34;keyword&#34;&gt;WHERE&lt;/span&gt; tb2.id &lt;span class=&#34;keyword&#34;&gt;IN&lt;/span&gt; (&lt;span class=&#34;keyword&#34;&gt;SELECT&lt;/span&gt; tb3.id &lt;span class=&#34;keyword&#34;&gt;FROM&lt;/span&gt; tb3));&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/figure&gt;
&lt;p&gt;这其实还好，由于简单子查询的设计，我们已经可以这样嵌套了。&lt;/p&gt;
&lt;p&gt;并且，子表中还能使用外表的字段，比如：&lt;/p&gt;
&lt;figure class=&#34;highlight sql&#34;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&#34;gutter&#34;&gt;&lt;pre&gt;&lt;span class=&#34;line&#34;&gt;1&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&#34;code&#34;&gt;&lt;pre&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;keyword&#34;&gt;SELECT&lt;/span&gt; &lt;span class=&#34;operator&#34;&gt;*&lt;/span&gt; &lt;span class=&#34;keyword&#34;&gt;FROM&lt;/span&gt; tb &lt;span class=&#34;keyword&#34;&gt;WHERE&lt;/span&gt; tb.id &lt;span class=&#34;operator&#34;&gt;=&lt;/span&gt; (&lt;span class=&#34;keyword&#34;&gt;SELECT&lt;/span&gt; tb2.id &lt;span class=&#34;keyword&#34;&gt;FROM&lt;/span&gt; tb2 &lt;span class=&#34;keyword&#34;&gt;WHERE&lt;/span&gt; tb2.id &lt;span class=&#34;operator&#34;&gt;=&lt;/span&gt; tb.id);&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/figure&gt;
&lt;p&gt;诶，这就有点意思了。本质上，只是需要让子表的物理算子中持有外表的&lt;strong&gt;当前的&lt;/strong&gt; tuple 即可。&lt;/p&gt;
&lt;h3 id=&#34;实现-2&#34;&gt;&lt;a class=&#34;markdownIt-Anchor&#34; href=&#34;#实现-2&#34;&gt;&lt;/a&gt; 实现&lt;/h3&gt;
&lt;p&gt;在算子执行时，&lt;code&gt;PredicatorPhysicalOperator&lt;/code&gt; 会在取出 tuple 后会执行 ConjunctionExpr 表达式，这个表达式又会执行若干个 ComparitorExpr 表达式。而在 &lt;code&gt;Expression::get_value()&lt;/code&gt; 方法中，是有传递下来当前 tuple 的。我们可以借此在 open 子表物理算子的时候传给它。&lt;/p&gt;
&lt;p&gt;总体上，算子的执行顺序主要有以下两种（在实现了 order-by 和 group-by 之后）：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;普通: proj -&amp;gt; orderby -&amp;gt; predicate -&amp;gt; …expr&lt;/li&gt;
&lt;li&gt;聚合: proj -&amp;gt; orderby -&amp;gt; groupby -&amp;gt; predicate -&amp;gt; …expr&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;我们将 外表tuple 传给 proj，然后逐层传递给到 predicate，在 predicate 中使用 JoinedTuple 来存储外表的 tuple 即可！这样，predicate 在执行 expr（如comparitorexpr）的时候，这个 expr 就能持有外表的 tuple 了。妙不可言！&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;为什么要在 open 的时候就传而不是 next？因为某些算子如 order-by 在 open 的时候就会把其子算子 next 完，以排序/分组等。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;此外，这题需要预先实现 alias。实际上，我将这题和 alias 放在一起写了。&lt;/p&gt;
&lt;h2 id=&#34;alias&#34;&gt;&lt;a class=&#34;markdownIt-Anchor&#34; href=&#34;#alias&#34;&gt;&lt;/a&gt; Alias&lt;/h2&gt;
&lt;h2 id=&#34;需求分析-3&#34;&gt;&lt;a class=&#34;markdownIt-Anchor&#34; href=&#34;#需求分析-3&#34;&gt;&lt;/a&gt; 需求分析&lt;/h2&gt;
&lt;p&gt;这一题需要实现别名，包括表、列的别名。比如：&lt;/p&gt;
&lt;figure class=&#34;highlight sql&#34;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&#34;gutter&#34;&gt;&lt;pre&gt;&lt;span class=&#34;line&#34;&gt;1&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&#34;code&#34;&gt;&lt;pre&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;keyword&#34;&gt;SELECT&lt;/span&gt; tb.id &lt;span class=&#34;keyword&#34;&gt;AS&lt;/span&gt; num, tb.name &lt;span class=&#34;keyword&#34;&gt;AS&lt;/span&gt; name &lt;span class=&#34;keyword&#34;&gt;FROM&lt;/span&gt; tb &lt;span class=&#34;keyword&#34;&gt;AS&lt;/span&gt; t &lt;span class=&#34;keyword&#34;&gt;WHERE&lt;/span&gt; t.num &lt;span class=&#34;operator&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;number&#34;&gt;1&lt;/span&gt;;&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/figure&gt;
&lt;p&gt;并且，需要支持在子查询中使用别名，比如：&lt;/p&gt;
&lt;figure class=&#34;highlight sql&#34;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&#34;gutter&#34;&gt;&lt;pre&gt;&lt;span class=&#34;line&#34;&gt;1&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&#34;code&#34;&gt;&lt;pre&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;keyword&#34;&gt;SELECT&lt;/span&gt; t.id, t.name &lt;span class=&#34;keyword&#34;&gt;FROM&lt;/span&gt; tb &lt;span class=&#34;keyword&#34;&gt;AS&lt;/span&gt; t &lt;span class=&#34;keyword&#34;&gt;WHERE&lt;/span&gt; t.id &lt;span class=&#34;keyword&#34;&gt;IN&lt;/span&gt; (&lt;span class=&#34;keyword&#34;&gt;SELECT&lt;/span&gt; t2.id &lt;span class=&#34;keyword&#34;&gt;FROM&lt;/span&gt; tb2 &lt;span class=&#34;keyword&#34;&gt;AS&lt;/span&gt; t2 &lt;span class=&#34;keyword&#34;&gt;WHERE&lt;/span&gt; t2.id &lt;span class=&#34;operator&#34;&gt;=&lt;/span&gt; t.id);&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/figure&gt;
&lt;p&gt;我在生成 Stmt 的时候就把所有的表和字段的别名全部替换成了真实的名字。&lt;/p&gt;
&lt;h2 id=&#34;实现-3&#34;&gt;&lt;a class=&#34;markdownIt-Anchor&#34; href=&#34;#实现-3&#34;&gt;&lt;/a&gt; 实现&lt;/h2&gt;
&lt;p&gt;语法分析部分就不赘述了。在生成 SelectStmt 中，我增加了 &lt;code&gt;alias2name&lt;/code&gt;, &lt;code&gt;name2alias&lt;/code&gt;, &lt;code&gt;field_alias2name&lt;/code&gt; 这三个哈希表和 &lt;code&gt;loaded_relation_names&lt;/code&gt; 这个列表（vector）。然后用于子查询会递归调用 &lt;code&gt;SlectStmt::create()&lt;/code&gt; 方法，因此我们就中把上述四个变量作为参数传递即可。比如，在外层的 &lt;code&gt;SelectStmt::create()&lt;/code&gt; 方法中，遍历并添加了 alias 和 name 的对应关系，紧接着是创建这个外层的 SelectStmt 的子查询，传递这些参数，那么这个子查询就能够根据传递进来的 alias 和 name 的对应关系来恢复真实的名字。这还有一个好处，即外层表也不会受到子查询的影响。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;在最后我发现其实 MYSQL 不支持在 where 中使用别名，所以没必要将 field 的别名替换。&lt;/p&gt;
&lt;figure class=&#34;highlight json&#34;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&#34;gutter&#34;&gt;&lt;pre&gt;&lt;span class=&#34;line&#34;&gt;1&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;2&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&#34;code&#34;&gt;&lt;pre&gt;&lt;span class=&#34;line&#34;&gt;select t1.id as num&lt;span class=&#34;punctuation&#34;&gt;,&lt;/span&gt; t1.col1 as age from table_alias_1 t1 where num &amp;gt; age;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;-- ERROR &lt;span class=&#34;number&#34;&gt;1054&lt;/span&gt; (&lt;span class=&#34;number&#34;&gt;42&lt;/span&gt;S22) at line &lt;span class=&#34;number&#34;&gt;179&lt;/span&gt;&lt;span class=&#34;punctuation&#34;&gt;:&lt;/span&gt; Unknown column &amp;#x27;num&amp;#x27; in &amp;#x27;where clause&amp;#x27;&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/figure&gt;
&lt;/blockquote&gt;
&lt;p&gt;具体来说：在 SelectStmt 中，会处理如下步骤：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;初始化。检测 alias2name 等是否为空指针，如是则创建。&lt;/li&gt;
&lt;li&gt;绑定表。遍历 loaded_relation_names，找到对应的表实例加入到当前 SelectStmt 的 table_map 中。通过这个方式加入的表都是外部的表，这里我还做了一个标记用来识别这个 table 是不是子查询中的 from 来的表。&lt;/li&gt;
&lt;li&gt;遍历查询语句的 relations 以检查 alias 是否重复。&lt;/li&gt;
&lt;li&gt;遍历查询语句的 relations，加入到 loaded_relation_names 中。并且，如果 alias 不为空，则还加入到 &lt;code&gt;alias2name&lt;/code&gt;, &lt;code&gt;name2alias&lt;/code&gt;（防止重复存到空的 alias 导致 duplicate error）。&lt;/li&gt;
&lt;li&gt;遍历要查询的表达式，将其中的 alias 替换为真实的名字。&lt;strong&gt;这里需要递归处理。因为比如聚合表达式的子表达式才是真正的字段。如 SUM(&lt;a href=&#34;http://t.id&#34;&gt;t.id&lt;/a&gt;)&lt;/strong&gt;。如果表达式是 StarExpr，并且有别名，直接报错。（*不允许有别名）。此外，如果该字段表达式有别名，则加入到 field_alias2name 中。&lt;/li&gt;
&lt;li&gt;遍历 WHERE 子句中的条件，将其中的 alias 替换为真实的名字。&lt;strong&gt;这里需要递归处理。因为可能有子查询&lt;/strong&gt;。&lt;/li&gt;
&lt;li&gt;如果有子查询，在调用创建子查询的 create 方法中把当前的尚书的几个变量指针传递进去。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;大体就是这样，还有一些小问题。&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;在处理类似 &lt;code&gt;SELECT * from tb where id in (select value from tb2 where value &amp;gt; 1)&lt;/code&gt; 这种子查询带有 WHERE 子句并且 Condition 的 Field 没有指定表的情况时，在绑定 UnboundFieldExpr 的时候，原本的代码是会报错的，因为此时要查询的表不只有一个，实际上还有来自外层查询的表 &lt;code&gt;tb&lt;/code&gt;。因此，我们必须将原本的逻辑“如果没有指定表并且要查询的表&amp;gt;1则报错”更改为遍历所有要查询的表。&lt;/li&gt;
&lt;/ol&gt;
&lt;figure class=&#34;highlight cpp&#34;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&#34;gutter&#34;&gt;&lt;pre&gt;&lt;span class=&#34;line&#34;&gt;1&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;2&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;3&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;4&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;5&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;6&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;7&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;8&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;9&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;10&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;11&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;12&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;13&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;14&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;15&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;16&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;17&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;18&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;19&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&#34;code&#34;&gt;&lt;pre&gt;&lt;span class=&#34;line&#34;&gt;Table *table = &lt;span class=&#34;literal&#34;&gt;nullptr&lt;/span&gt;;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;keyword&#34;&gt;if&lt;/span&gt; (&lt;span class=&#34;built_in&#34;&gt;is_blank&lt;/span&gt;(table_name)) &amp;#123;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;  &lt;span class=&#34;comment&#34;&gt;// 由于子查询 **可能** 会引进外部的表，这里我们只能通过字段名来确定表。&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;  &lt;span class=&#34;type&#34;&gt;bool&lt;/span&gt; found = &lt;span class=&#34;literal&#34;&gt;false&lt;/span&gt;;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;  &lt;span class=&#34;keyword&#34;&gt;for&lt;/span&gt; (Table *table_ : context_.&lt;span class=&#34;built_in&#34;&gt;query_tables&lt;/span&gt;()) &amp;#123;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;    &lt;span class=&#34;keyword&#34;&gt;if&lt;/span&gt; (table_-&amp;gt;&lt;span class=&#34;built_in&#34;&gt;table_meta&lt;/span&gt;().&lt;span class=&#34;built_in&#34;&gt;field&lt;/span&gt;(field_name) != &lt;span class=&#34;literal&#34;&gt;nullptr&lt;/span&gt;) &amp;#123;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;      &lt;span class=&#34;keyword&#34;&gt;if&lt;/span&gt; (found) &amp;#123;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;        &lt;span class=&#34;built_in&#34;&gt;LOG_INFO&lt;/span&gt;(&lt;span class=&#34;string&#34;&gt;&amp;quot;ambiguous field name: %s, cannot determine table for this field.&amp;quot;&lt;/span&gt;, field_name);&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;        &lt;span class=&#34;keyword&#34;&gt;return&lt;/span&gt; RC::INVALID_ARGUMENT;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;      &amp;#125;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;      found = &lt;span class=&#34;literal&#34;&gt;true&lt;/span&gt;;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;      table = table_;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;    &amp;#125;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;  &amp;#125;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;  &lt;span class=&#34;keyword&#34;&gt;if&lt;/span&gt; (!found) &amp;#123;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;    &lt;span class=&#34;built_in&#34;&gt;LOG_INFO&lt;/span&gt;(&lt;span class=&#34;string&#34;&gt;&amp;quot;no such field in from list: %s&amp;quot;&lt;/span&gt;, field_name);&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;    &lt;span class=&#34;keyword&#34;&gt;return&lt;/span&gt; RC::SCHEMA_FIELD_MISSING;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;  &amp;#125;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;&amp;#125; &lt;span class=&#34;keyword&#34;&gt;else&lt;/span&gt; ...&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/figure&gt;
&lt;p&gt;基本上就这样。对于字段的别名，最终要显示在表头，这个很简单不再赘述。&lt;/p&gt;
&lt;p&gt;下面是在实现复杂子查询和别名时的提交 log：&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;发现复杂子查询要实现 OR，Alias 还有一个字段别名，并且简单子查询炸了，因为没考虑子查询没有使用外部表的情况。（这个问题在上面 &lt;code&gt;简单子查询&lt;/code&gt; 一节中已经提及）&lt;/p&gt;
&lt;p&gt;首先是 Alias 字段别名的问题：（在上面依然已经提及）&lt;/p&gt;
&lt;p&gt;我们来整理一下：一开始是一个 UnboundFieldExpr，可能带有 alias。在bind_expression中会将其与表绑定，转换为FieldExpr。我们要做的工作是在 bind_expression 前将&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;主查询的 condition&lt;/li&gt;
&lt;li&gt;子查询的 condition&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;中的可能使用了别名的字段（UnboundFieldExpr）中的 field_name 转为 真实的 field_name，将alias 转换为其 alias 即可，和处理 table 别名的情况类似。&lt;/p&gt;
&lt;p&gt;接下来是 OR 的部分，我们新建一个对 OR 的语法解析，然后在 ConditionSqlNode 中加一个 conjunction_type，这里的类型我用了 char，节省空间。1byte！&lt;/p&gt;
&lt;p&gt;然后在 FilterStmt 中加了一个 conjunciton_types 用于存储 where 查询字句中所有的 conjunction 连接符。然后在逻辑查询计划中据此确定用哪个。&lt;/p&gt;
&lt;p&gt;这里我们只支持纯 AND 或者 纯 OR。&lt;/p&gt;
&lt;p&gt;交一发！复杂子查询和简单子查询过了，alias 还有问题，update-select/vector_basic/vector_format/update 炸了。&lt;/p&gt;
&lt;p&gt;真是炸炸又炸炸呀。&lt;/p&gt;
&lt;p&gt;首先，来看看 vector_basic/vector_format，的炸点：&lt;/p&gt;
&lt;figure class=&#34;highlight plaintext&#34;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&#34;gutter&#34;&gt;&lt;pre&gt;&lt;span class=&#34;line&#34;&gt;1&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&#34;code&#34;&gt;&lt;pre&gt;&lt;span class=&#34;line&#34;&gt;SELECT C1, C2, L2_DISTANCE(C1, C2), L2_DISTANCE(&amp;#x27;[1,1,1]&amp;#x27;, &amp;#x27;[1,2,3]&amp;#x27;), COSINE_DISTANCE(&amp;#x27;[1,1,1]&amp;#x27;, C2) FROM TEST;&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/figure&gt;
&lt;p&gt;OK，能够复现，convert_alias_to_name 由于刚刚修改了字段名，少了对 ExprType::VECTOR_DISTANCE_EXPR 的判断导致 static_cast 到 UnboundField 出错。&lt;/p&gt;
&lt;p&gt;然后看看 update 的炸点：&lt;/p&gt;
&lt;figure class=&#34;highlight json&#34;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&#34;gutter&#34;&gt;&lt;pre&gt;&lt;span class=&#34;line&#34;&gt;1&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;2&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;3&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&#34;code&#34;&gt;&lt;pre&gt;&lt;span class=&#34;line&#34;&gt;UPDATE update_table_1 SET t_name=&amp;#x27;&lt;span class=&#34;number&#34;&gt;1&lt;/span&gt;&amp;#x27; WHERE id_false=&lt;span class=&#34;number&#34;&gt;72&lt;/span&gt;;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;- FAILURE&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;+ failed to receive response from observer&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/figure&gt;
&lt;p&gt;猜测是不存在的 field 导致报错。事实也确实如此。在 bind_expression 当中，忘记写某个 field 没有找到 table 时的异常情况了。&lt;/p&gt;
&lt;p&gt;交一发！结果是其他都解决了，还差一个 update_select_t2&lt;/p&gt;
&lt;figure class=&#34;highlight plaintext&#34;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&#34;gutter&#34;&gt;&lt;pre&gt;&lt;span class=&#34;line&#34;&gt;1&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;2&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;3&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;4&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;5&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;6&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;7&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;8&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;9&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;10&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;11&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;12&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;13&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;14&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&#34;code&#34;&gt;&lt;pre&gt;&lt;span class=&#34;line&#34;&gt;UPDATE Update_select_t2 SET t_name=52,col1=178.78 WHERE id=9;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;UPDATE Update_select_t2 SET t_name=(select Update_select_t1.col1 from Update_select_t1 where Update_select_t1.id=6),col1=(select min(Update_select_t1.col1) from Update_select_t1) where id=3;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;SELECT * FROM Update_select_t2;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;  1 | U0S6 | 3 | 8&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;  2 | U0S6 | 3 | 8&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;  2 | U0S6 | 3 | 8&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;- 3 | U0S6 | 3 | 8&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;+ 3 | U0S6 | 1 | 8&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;  3 | U0S6 | 3 | 8&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;  3 | U0S6 | 3 | 8&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;  4 | U0S6 | 3 | 8&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;  &lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;CREATE TABLE Update_select_t1(id int, t_name char(10), col1 int, col2 int);&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;CREATE TABLE Update_select_t2(id int, t_name char(10), col1 int, col2 int);&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/figure&gt;
&lt;p&gt;说实话，刚看到这个 diff 的时候有点不知所措。是什么原因呢 🤔？第二个 Update 语句要更新 t_name 为 &lt;code&gt;Update_select_t1.col1&lt;/code&gt; 中的值，而这是个 int 类型，然而 diff 中显示会出现 &lt;code&gt;3 | U0S6 | 3 | 8&lt;/code&gt; 这一列。int 类型还能是 &lt;code&gt;U0S6&lt;/code&gt;？说明了什么？这条语句插入失败了，或者没有任何更新。那为什么没有出现 &lt;code&gt;-FAILURE&lt;/code&gt; 或者 &lt;code&gt;-SUCCESS&lt;/code&gt; 呢？如果暂且相信这个 diff，那说明 UPDATE 的输出结果至少是没问题的。注意到更新的是 &lt;code&gt;id=3&lt;/code&gt; 的行，并且 id=3 有 3 个匹配的行，在这三行中，预期的输出是 col2=3，而有一行的输出是 col2=1。这给了我一个猜测——&lt;strong&gt;在更新到第一行匹配行的时候报错了，但是还是更新了。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;让我们来看代码：&lt;/p&gt;
&lt;figure class=&#34;highlight c++&#34;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&#34;gutter&#34;&gt;&lt;pre&gt;&lt;span class=&#34;line&#34;&gt;1&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;2&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;3&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;4&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;5&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;6&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;7&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;8&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;9&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;10&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;11&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;12&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;13&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;14&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;15&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;16&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;17&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;18&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;19&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;20&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;21&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;22&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;23&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;24&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;25&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;26&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;27&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;28&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;29&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;30&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;31&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;32&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;33&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;34&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;35&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;36&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;37&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;38&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;39&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;40&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;41&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;42&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;43&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;44&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;45&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;46&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;47&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;48&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;49&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;50&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;51&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;52&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&#34;code&#34;&gt;&lt;pre&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;keyword&#34;&gt;while&lt;/span&gt; (&lt;span class=&#34;built_in&#34;&gt;OB_SUCC&lt;/span&gt;(rc = child-&amp;gt;&lt;span class=&#34;built_in&#34;&gt;next&lt;/span&gt;())) &amp;#123;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;    Tuple *tuple_ = child-&amp;gt;&lt;span class=&#34;built_in&#34;&gt;current_tuple&lt;/span&gt;(); &lt;span class=&#34;comment&#34;&gt;// 获得当前正在更新的 tuple&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;    &lt;span class=&#34;keyword&#34;&gt;auto&lt;/span&gt;   tuple  = &lt;span class=&#34;built_in&#34;&gt;dynamic_cast&lt;/span&gt;&amp;lt;RowTuple *&amp;gt;(tuple_);&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;    &lt;span class=&#34;built_in&#34;&gt;ASSERT&lt;/span&gt;(tuple != &lt;span class=&#34;literal&#34;&gt;nullptr&lt;/span&gt;, &lt;span class=&#34;string&#34;&gt;&amp;quot;tuple cannot cast to RowTuple here!&amp;quot;&lt;/span&gt;);&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;    rc = table_-&amp;gt;&lt;span class=&#34;built_in&#34;&gt;visit_record&lt;/span&gt;(tuple-&amp;gt;&lt;span class=&#34;built_in&#34;&gt;record&lt;/span&gt;().&lt;span class=&#34;built_in&#34;&gt;rid&lt;/span&gt;(), [&lt;span class=&#34;keyword&#34;&gt;this&lt;/span&gt;, tuple, trx, &amp;amp;updateIndexTasks](Record &amp;amp;record) &amp;#123;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;      &lt;span class=&#34;keyword&#34;&gt;for&lt;/span&gt; (&lt;span class=&#34;type&#34;&gt;size_t&lt;/span&gt; i = &lt;span class=&#34;number&#34;&gt;0&lt;/span&gt;; i &amp;lt; exprs_.&lt;span class=&#34;built_in&#34;&gt;size&lt;/span&gt;(); i++) &amp;#123;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;        Value cell;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;        RC    rc = exprs_[i]-&amp;gt;&lt;span class=&#34;built_in&#34;&gt;get_value&lt;/span&gt;(*tuple, cell);&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;        &lt;span class=&#34;comment&#34;&gt;// 子查询返回空值的情况&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;        &lt;span class=&#34;keyword&#34;&gt;if&lt;/span&gt; (rc == RC::RECORD_EOF &amp;amp;&amp;amp; field_metas_[i].&lt;span class=&#34;built_in&#34;&gt;nullable&lt;/span&gt;()) &amp;#123;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;          cell.&lt;span class=&#34;built_in&#34;&gt;set_null&lt;/span&gt;();&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;        &amp;#125; &lt;span class=&#34;keyword&#34;&gt;else&lt;/span&gt; &lt;span class=&#34;keyword&#34;&gt;if&lt;/span&gt; (&lt;span class=&#34;built_in&#34;&gt;OB_FAIL&lt;/span&gt;(rc)) &amp;#123;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;          &lt;span class=&#34;built_in&#34;&gt;LOG_WARN&lt;/span&gt;(&lt;span class=&#34;string&#34;&gt;&amp;quot;cannot get value from expression: %s&amp;quot;&lt;/span&gt;, &lt;span class=&#34;built_in&#34;&gt;strrc&lt;/span&gt;(rc));&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;        &amp;#125;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;        &lt;span class=&#34;keyword&#34;&gt;if&lt;/span&gt; (cell.&lt;span class=&#34;built_in&#34;&gt;is_null&lt;/span&gt;() &amp;amp;&amp;amp; !field_metas_[i].&lt;span class=&#34;built_in&#34;&gt;nullable&lt;/span&gt;()) &amp;#123;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;          &lt;span class=&#34;built_in&#34;&gt;LOG_WARN&lt;/span&gt;(&lt;span class=&#34;string&#34;&gt;&amp;quot;field %s is not nullable, but the value is null&amp;quot;&lt;/span&gt;, field_metas_[i].&lt;span class=&#34;built_in&#34;&gt;name&lt;/span&gt;());&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;          &lt;span class=&#34;keyword&#34;&gt;return&lt;/span&gt; RC::INVALID_ARGUMENT;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;        &amp;#125;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;        &lt;span class=&#34;comment&#34;&gt;// we get the value again to check if the subquery is legal&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;        &lt;span class=&#34;keyword&#34;&gt;if&lt;/span&gt; (exprs_[i]-&amp;gt;&lt;span class=&#34;built_in&#34;&gt;type&lt;/span&gt;() == ExprType::SUB_QUERY) &amp;#123;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;          Value test_cell_;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;          RC rc_ = exprs_[i]-&amp;gt;&lt;span class=&#34;built_in&#34;&gt;get_value&lt;/span&gt;(*tuple, test_cell_);&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;          &lt;span class=&#34;keyword&#34;&gt;if&lt;/span&gt; (rc_ != RC::RECORD_EOF) &amp;#123;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;            &lt;span class=&#34;built_in&#34;&gt;LOG_WARN&lt;/span&gt;(&lt;span class=&#34;string&#34;&gt;&amp;quot;subquery should return only one value&amp;quot;&lt;/span&gt;);&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;            &lt;span class=&#34;keyword&#34;&gt;return&lt;/span&gt; RC::INVALID_ARGUMENT;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;          &amp;#125;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;        &amp;#125;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;        &lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;        &lt;span class=&#34;keyword&#34;&gt;if&lt;/span&gt; (cell.&lt;span class=&#34;built_in&#34;&gt;attr_type&lt;/span&gt;() != field_metas_[i].&lt;span class=&#34;built_in&#34;&gt;type&lt;/span&gt;()) &amp;#123;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;          Value to_value;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;          rc = Value::&lt;span class=&#34;built_in&#34;&gt;cast_to&lt;/span&gt;(cell, field_metas_[i].&lt;span class=&#34;built_in&#34;&gt;type&lt;/span&gt;(), to_value);&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;          &lt;span class=&#34;keyword&#34;&gt;if&lt;/span&gt; (&lt;span class=&#34;built_in&#34;&gt;OB_FAIL&lt;/span&gt;(rc)) &amp;#123;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;            &lt;span class=&#34;built_in&#34;&gt;LOG_WARN&lt;/span&gt;(&lt;span class=&#34;string&#34;&gt;&amp;quot;cannot cast from %s to %s&amp;quot;&lt;/span&gt;, &lt;span class=&#34;built_in&#34;&gt;attr_type_to_string&lt;/span&gt;(cell.&lt;span class=&#34;built_in&#34;&gt;attr_type&lt;/span&gt;()), &lt;span class=&#34;built_in&#34;&gt;attr_type_to_string&lt;/span&gt;(field_metas_[i].&lt;span class=&#34;built_in&#34;&gt;type&lt;/span&gt;()));&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;            &lt;span class=&#34;keyword&#34;&gt;return&lt;/span&gt; RC::INVALID_ARGUMENT;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;          &amp;#125;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;          cell = to_value;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;        &amp;#125;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;        tuple-&amp;gt;&lt;span class=&#34;built_in&#34;&gt;set_cell_at&lt;/span&gt;(field_metas_[i].&lt;span class=&#34;built_in&#34;&gt;field_id&lt;/span&gt;(), cell, record.&lt;span class=&#34;built_in&#34;&gt;data&lt;/span&gt;());&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;      &amp;#125;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;      updateIndexTasks.&lt;span class=&#34;built_in&#34;&gt;push_back&lt;/span&gt;([&lt;span class=&#34;keyword&#34;&gt;this&lt;/span&gt;, record] &amp;#123;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;        &lt;span class=&#34;comment&#34;&gt;// 复制 record 对象，避免 use-after-free&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;        &lt;span class=&#34;keyword&#34;&gt;return&lt;/span&gt; table_-&amp;gt;&lt;span class=&#34;built_in&#34;&gt;update_index&lt;/span&gt;(record, field_metas_);&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;      &amp;#125;);&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;      &lt;span class=&#34;keyword&#34;&gt;return&lt;/span&gt; RC::SUCCESS;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;    &amp;#125;);&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;    &lt;span class=&#34;keyword&#34;&gt;if&lt;/span&gt; (rc != RC::SUCCESS) &amp;#123;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;      &lt;span class=&#34;keyword&#34;&gt;return&lt;/span&gt; rc;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;    &amp;#125;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;  &amp;#125;&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/figure&gt;
&lt;p&gt;外层循环一行行的 tuple，内层根据要更新的字段表达式更新这一行的值。注意到了什么？更新某一个字段表达式的操作是可以被打断的——更新这一行的操作不是原子性的。让我们回到有问题的SQL，第一个 t_name 设置为等于一个子查询的返回值，但是如果这个子查询返回了多行呢，就会导致这个更新操作被打断，但是前面已经更新了 col1。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;t_name 明明在 col1 前面，为什么会先更新 col1？可能的原因是在语法解析的时候由于递归特性，先解析了 col1 的表达式，但是没有 reverse。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;我们只需要在外层循环中加一个 vector 存储这一行的所有更新操作，然后在循环结束后再执行即可。&lt;/p&gt;
&lt;p&gt;修复之后，接下来还差 alias。&lt;/p&gt;
&lt;figure class=&#34;highlight json&#34;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&#34;gutter&#34;&gt;&lt;pre&gt;&lt;span class=&#34;line&#34;&gt;1&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;2&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;3&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;4&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;5&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;6&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;7&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;8&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;9&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&#34;code&#34;&gt;&lt;pre&gt;&lt;span class=&#34;line&#34;&gt;select t1.id as num&lt;span class=&#34;punctuation&#34;&gt;,&lt;/span&gt; t1.col1 as age&lt;span class=&#34;punctuation&#34;&gt;,&lt;/span&gt; t1.feat1&lt;span class=&#34;punctuation&#34;&gt;,&lt;/span&gt; t2.* from table_alias_1 t1&lt;span class=&#34;punctuation&#34;&gt;,&lt;/span&gt; table_alias_2 t2 where t1.id &amp;lt; t2.id;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;- &lt;span class=&#34;number&#34;&gt;27&lt;/span&gt; | &lt;span class=&#34;number&#34;&gt;17&lt;/span&gt; | &lt;span class=&#34;number&#34;&gt;81.1&lt;/span&gt; | &lt;span class=&#34;number&#34;&gt;46&lt;/span&gt; | &lt;span class=&#34;number&#34;&gt;44&lt;/span&gt; | &lt;span class=&#34;number&#34;&gt;93.9&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;+ FAILURE&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;-- below are some requests executed before(partial) --&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;-- init data&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;CREATE TABLE table_alias_1(id int&lt;span class=&#34;punctuation&#34;&gt;,&lt;/span&gt; col1 int&lt;span class=&#34;punctuation&#34;&gt;,&lt;/span&gt; feat1 float);&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;CREATE TABLE table_alias_2(id int&lt;span class=&#34;punctuation&#34;&gt;,&lt;/span&gt; col2 int&lt;span class=&#34;punctuation&#34;&gt;,&lt;/span&gt; feat2 float);&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;INSERT INTO table_alias_1 VALUES (&lt;span class=&#34;number&#34;&gt;27&lt;/span&gt;&lt;span class=&#34;punctuation&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;number&#34;&gt;40&lt;/span&gt;&lt;span class=&#34;punctuation&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;number&#34;&gt;39.0&lt;/span&gt;);&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;INSERT INTO table_alias_1 VALUES (&lt;span class=&#34;number&#34;&gt;78&lt;/span&gt;&lt;span class=&#34;punctuation&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;number&#34;&gt;91&lt;/span&gt;&lt;span class=&#34;punctuation&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;number&#34;&gt;41.4&lt;/span&gt;);&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/figure&gt;
&lt;p&gt;直接给了一个failure。让我们看看怎么个事。&lt;/p&gt;
&lt;figure class=&#34;highlight json&#34;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&#34;gutter&#34;&gt;&lt;pre&gt;&lt;span class=&#34;line&#34;&gt;1&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;2&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;3&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;4&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;5&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;6&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;7&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&#34;code&#34;&gt;&lt;pre&gt;&lt;span class=&#34;line&#34;&gt;miniob &amp;gt; select t1.id as num&lt;span class=&#34;punctuation&#34;&gt;,&lt;/span&gt; t1.col1 as age&lt;span class=&#34;punctuation&#34;&gt;,&lt;/span&gt; t1.feat1&lt;span class=&#34;punctuation&#34;&gt;,&lt;/span&gt; t2.* from table_alias_1 t1&lt;span class=&#34;punctuation&#34;&gt;,&lt;/span&gt; table_alias_2 t2 where t1.id &amp;lt; t2.id;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;punctuation&#34;&gt;[&lt;/span&gt;DEBUG&lt;span class=&#34;punctuation&#34;&gt;]&lt;/span&gt; SELECT&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;punctuation&#34;&gt;[&lt;/span&gt;DEBUG&lt;span class=&#34;punctuation&#34;&gt;]&lt;/span&gt; ID&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;punctuation&#34;&gt;[&lt;/span&gt;DEBUG&lt;span class=&#34;punctuation&#34;&gt;]&lt;/span&gt; DOT&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;punctuation&#34;&gt;[&lt;/span&gt;DEBUG&lt;span class=&#34;punctuation&#34;&gt;]&lt;/span&gt; ID&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;punctuation&#34;&gt;[&lt;/span&gt;DEBUG&lt;span class=&#34;punctuation&#34;&gt;]&lt;/span&gt; AS&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;FAILURE&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/figure&gt;
&lt;p&gt;在解析阶段就报了个错。少了一个 ID DOT ‘*’ 的情况。&lt;/p&gt;
&lt;p&gt;那我们就在 rel_attr 处加一个这个情况，然后鉴于是 *，我们在 expresison 的语法树处做个简单的if判断，将其换为 StarExpr&lt;/p&gt;
&lt;p&gt;测试一下，依然报错。&lt;/p&gt;
&lt;p&gt;看了一下 DEBUG，原来少的是 relattr AS ID 的情况（漏了这里）&lt;/p&gt;
&lt;p&gt;而 ID DOT ‘*’ 其实在代码层面有相关处理，因此不用在语法树解析做处理。&lt;/p&gt;
&lt;p&gt;… 多次提测之后 …&lt;/p&gt;
&lt;figure class=&#34;highlight plaintext&#34;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&#34;gutter&#34;&gt;&lt;pre&gt;&lt;span class=&#34;line&#34;&gt;1&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;2&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;3&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&#34;code&#34;&gt;&lt;pre&gt;&lt;span class=&#34;line&#34;&gt;select count(*) as num, avg(t1.col1) score from table_alias_1 t1, table_alias_2 t2 where t1.id &amp;lt; t2.id;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;- 13 | 65.85&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;+ FAILURE&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/figure&gt;
&lt;p&gt;同样还是语法错误。&lt;/p&gt;
&lt;p&gt;这让我反思，应该对整个 expression 的上级语法树节点 &lt;code&gt;expressions&lt;/code&gt; 来加 AS ID 和 ID 比较好。而不是单单在 UnboundField 里面加。&lt;/p&gt;
&lt;p&gt;最终，在 &lt;code&gt;expressions&lt;/code&gt; 的语法树节点中加入了 &lt;code&gt;ID AS ID&lt;/code&gt; 和 &lt;code&gt;ID&lt;/code&gt; 的语法规则，并且把 alias 的存储直接放在了 Expression 抽象类中。成功过测。&lt;/p&gt;
&lt;h2 id=&#34;aggregation-and-groupby&#34;&gt;&lt;a class=&#34;markdownIt-Anchor&#34; href=&#34;#aggregation-and-groupby&#34;&gt;&lt;/a&gt; Aggregation-And-GroupBy&lt;/h2&gt;
&lt;p&gt;我们只需要实现 having 子句。很容易可以想到在 select_stmt 中加一个 havings，类型是 Condition。&lt;/p&gt;
&lt;p&gt;本质上还是筛选，我们只需要在 GROUP BY 算子后再加一个 Predicate 算子即可：对 GROUP BY 的结果进行筛选。&lt;/p&gt;
&lt;p&gt;然而，需要注意的是，可能会有 &lt;code&gt;SELECT name, SUM(score) from tb GROUP BY name HAVING COUNT(*) &amp;gt; 10;&lt;/code&gt; 这种情况。在 HAVING 子句中有一个聚合表达式，并且这个聚合表达式没有出现在查询表达式中。&lt;/p&gt;
&lt;p&gt;在这种情况下，不可能单单仅靠 Predicate 来筛选了，因为聚合表达式需要对所有符合条件的 tuple 进行计算和聚合。而 Predicate 只是对 tuple 进行筛选。&lt;/p&gt;
&lt;p&gt;这里，我的做法是把 Having 子句中所有聚合表达式全部交由 GROUP BY 管理，然后 GROUP BY 返回的 tuple 就会包含 真正要查询的结果和 HAVING 子句中的聚合表达式的结果。Predicate 就可以借此进行筛选。并且由于 Project 的存在，最终用户看到的只是真正要查询的结果。&lt;/p&gt;
&lt;p&gt;实现方面很简单，只要修改逻辑算子和物理算子的相关代码即可。不赘述。&lt;/p&gt;
&lt;h2 id=&#34;create-table-select&#34;&gt;&lt;a class=&#34;markdownIt-Anchor&#34; href=&#34;#create-table-select&#34;&gt;&lt;/a&gt; Create-Table-Select&lt;/h2&gt;
&lt;p&gt;要实现：&lt;/p&gt;
&lt;figure class=&#34;highlight plaintext&#34;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&#34;gutter&#34;&gt;&lt;pre&gt;&lt;span class=&#34;line&#34;&gt;1&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;2&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&#34;code&#34;&gt;&lt;pre&gt;&lt;span class=&#34;line&#34;&gt;CREATE TABLE tb1 AS SELECT * FROM tb2;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;CREATE TABLE tb2(col1 int, col2 char(10)) AS SELECT * FROM tb1;&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/figure&gt;
&lt;p&gt;这样的 SQL，即根据 SELECT 的结果创建表并将查询结果插入到新表中。&lt;/p&gt;
&lt;p&gt;语法分析方面，在 CreateTableSqlNode 中加了一个 subselect 字段，用于存储查询语句。然后据此修改语法树。在 CreateTableStmt 的 create 方法中，如果 subselect 不为空，就调用 &lt;code&gt;SelectStmt::create()&lt;/code&gt; 方法创建 SelectStmt。并写一个函数来将 SelectStmt 的查询表达式转换成 FieldMeta 列表，本质上是需要智能解析出要创建的表的列属性，当然，如果 CreateTable 已经指定了列的属性，就不用获取了。&lt;/p&gt;
&lt;p&gt;在查询计划中，同样如果 subselect 不为空，就根据存好的 SelectStmt 创建查询的逻辑计划算子，然后创建物理查询算子存入 CreateTableStmt 中。在 &lt;code&gt;CreateTableExecutor&lt;/code&gt; 中，创建好表之后，打开查询物理算子存入就好了。思想上很简单。&lt;/p&gt;
&lt;h2 id=&#34;create-view&#34;&gt;&lt;a class=&#34;markdownIt-Anchor&#34; href=&#34;#create-view&#34;&gt;&lt;/a&gt; Create-View&lt;/h2&gt;
&lt;h3 id=&#34;需求分析-4&#34;&gt;&lt;a class=&#34;markdownIt-Anchor&#34; href=&#34;#需求分析-4&#34;&gt;&lt;/a&gt; 需求分析&lt;/h3&gt;
&lt;p&gt;需要实现创建视图、插入视图、更新视图。并且需要着重考虑在多表、带 alias 的情况下，视图的插入和更新操作。&lt;/p&gt;
&lt;p&gt;首先，测试了下 MySQL 的视图功能，发现以下几个要注意的点：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;视图名不能和别的视图名、表名重复。（废话）&lt;/li&gt;
&lt;li&gt;创建视图时会检查 SELECT 语句能够正常执行，包括表名、字段名、子查询等。也就是说需要正常走一遍 SELECT 查询计划，但不输出行结果。&lt;/li&gt;
&lt;li&gt;视图和原始表同步更新，更新视图会更新原始表，更新原始表会更新视图。&lt;/li&gt;
&lt;li&gt;带聚合的视图是 not insertable-into 的。[ERROR 1471]&lt;/li&gt;
&lt;li&gt;对于 join view，那么表的字段名不能重复（即使指定定了表名），否则会报错。[ERROR 1060]&lt;/li&gt;
&lt;li&gt;对于 join view，不能同时插入/更新两个表的数据。[ERROR 1394]&lt;/li&gt;
&lt;li&gt;对于 join view 的插入/更新操作，需要指定的字段。[ERROR 1393]&lt;/li&gt;
&lt;li&gt;视图的字段名是可以自定义的（fields list），这种情况下，会覆盖掉结果集的字段名。并且，fields list 的长度必须和结果集列数一致。[ERROR 1058]&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;需要注意的是，在 MYSQL 中，视图是虚拟表的存在，磁盘不会存储视图的任何 SELECT 的结果集，只会简单地存它的定义。&lt;code&gt;INFORMATION_SCHEMA.VIEWS&lt;/code&gt; 中存储了所有的视图信息。这个表的结构如下：&lt;/p&gt;
&lt;figure class=&#34;highlight plaintext&#34;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&#34;gutter&#34;&gt;&lt;pre&gt;&lt;span class=&#34;line&#34;&gt;1&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;2&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;3&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;4&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;5&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;6&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;7&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;8&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;9&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;10&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;11&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;12&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;13&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;14&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;15&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;16&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&#34;code&#34;&gt;&lt;pre&gt;&lt;span class=&#34;line&#34;&gt;mysql&amp;gt; describe views;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;+----------------------+---------------------------------+------+-----+---------+-------+&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;| Field                | Type                            | Null | Key | Default | Extra |&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;+----------------------+---------------------------------+------+-----+---------+-------+&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;| TABLE_CATALOG        | varchar(64)                     | NO   |     | NULL    |       |&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;| TABLE_SCHEMA         | varchar(64)                     | NO   |     | NULL    |       |&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;| TABLE_NAME           | varchar(64)                     | NO   |     | NULL    |       |&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;| VIEW_DEFINITION      | longtext                        | YES  |     | NULL    |       |&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;| CHECK_OPTION         | enum(&amp;#x27;NONE&amp;#x27;,&amp;#x27;LOCAL&amp;#x27;,&amp;#x27;CASCADED&amp;#x27;) | YES  |     | NULL    |       |&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;| IS_UPDATABLE         | enum(&amp;#x27;NO&amp;#x27;,&amp;#x27;YES&amp;#x27;)                | YES  |     | NULL    |       |&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;| DEFINER              | varchar(288)                    | YES  |     | NULL    |       |&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;| SECURITY_TYPE        | varchar(7)                      | YES  |     | NULL    |       |&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;| CHARACTER_SET_CLIENT | varchar(64)                     | NO   |     | NULL    |       |&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;| COLLATION_CONNECTION | varchar(64)                     | NO   |     | NULL    |       |&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;+----------------------+---------------------------------+------+-----+---------+-------+&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;10 rows in set (0.00 sec)&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/figure&gt;
&lt;p&gt;对于我们的实现来说，只需要保留 &lt;code&gt;TABLE_SCHEMA&lt;/code&gt;, &lt;code&gt;TABLE_NAME&lt;/code&gt;, &lt;code&gt;VIEW_DEFINITION&lt;/code&gt;, &lt;code&gt;IS_UPDATABLE&lt;/code&gt; 这几个字段即可。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;NOTE: 在实现后期，发现还需要加一个 &lt;code&gt;ATTRS_NAME&lt;/code&gt; 字段，用于存储用户自定义的视图的字段名。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;NOTE：下文的 &lt;code&gt;视图定义&lt;/code&gt; 指的是 &lt;code&gt;VIEW_DEFINITION&lt;/code&gt; 字段中的内容。即视图的 SELECT 语句。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3 id=&#34;实现-4&#34;&gt;&lt;a class=&#34;markdownIt-Anchor&#34; href=&#34;#实现-4&#34;&gt;&lt;/a&gt; 实现&lt;/h3&gt;
&lt;h4 id=&#34;view-类&#34;&gt;&lt;a class=&#34;markdownIt-Anchor&#34; href=&#34;#view-类&#34;&gt;&lt;/a&gt; View 类&lt;/h4&gt;
&lt;p&gt;为了在执行算子中表示一个视图，我们还需要创建一个 &lt;code&gt;View&lt;/code&gt; 类。为了减小风险和侵入性，在我的设计中，我让 &lt;code&gt;View&lt;/code&gt; 继承了 &lt;code&gt;Table&lt;/code&gt; 类，其实这也说的通，View本质上就是一个临时表，应当具有表的所有属性。&lt;code&gt;View&lt;/code&gt; 类中存储了:&lt;/p&gt;
&lt;figure class=&#34;highlight cpp&#34;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&#34;gutter&#34;&gt;&lt;pre&gt;&lt;span class=&#34;line&#34;&gt;1&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;2&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;3&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;4&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;5&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;6&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;7&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;8&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;9&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;10&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;11&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;12&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;13&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;14&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&#34;code&#34;&gt;&lt;pre&gt;&lt;span class=&#34;line&#34;&gt;std::string view_name_;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;std::string view_definition_;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;std::vector&amp;lt;std::string&amp;gt; attrs_name_;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;type&#34;&gt;bool&lt;/span&gt; is_updatable_;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;type&#34;&gt;int32_t&lt;/span&gt; view_id_;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;std::unique_ptr&amp;lt;PhysicalOperator&amp;gt; operator_;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;comment&#34;&gt;// base table&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;std::vector&amp;lt;Table *&amp;gt; base_tables_;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;comment&#34;&gt;// 维护某个视图的普通字段（不包括聚合等不普通的）属于哪个表&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;std::unordered_map&amp;lt;std::string, std::string&amp;gt; field_base_table_name;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;comment&#34;&gt;// 维护视图的字段名和基表的字段名的映射关系&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;std::unordered_map&amp;lt;std::string, std::string&amp;gt; attr_name_2_base_table_field_name;&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/figure&gt;
&lt;p&gt;前四个就是 View 的基本属性，第五个是表的 ID。。后面几个是在实现插入、更新时的辅助信息，下面再进行阐述。&lt;/p&gt;
&lt;p&gt;在打开数据库时，会初始化 &lt;code&gt;Db&lt;/code&gt; 实例用于表示一个数据库实例，miniob 默认只有一个数据库，因此就只有一个 &lt;code&gt;Db&lt;/code&gt;。在 &lt;code&gt;Db&lt;/code&gt; 初始化时，会加载（Open）所有的表和视图信息。默认实现了 &lt;code&gt;open_all_tables&lt;/code&gt; 方法，我仿照这个方法实现了 &lt;code&gt;open_all_views&lt;/code&gt;，这个方法使用了 &lt;code&gt;RecordFileScanner&lt;/code&gt; 扫一遍 &lt;code&gt;__miniob_views__&lt;/code&gt; 表（如果没有这个表就跳过），然后创建 View 对象加入到 &lt;code&gt;unordered_map&amp;lt;string, View *&amp;gt;  opened_views_&lt;/code&gt; 中即可。view_id 从 &lt;code&gt;114514&lt;/code&gt; 开始，每创建一个 View 递增 1。&lt;/p&gt;
&lt;p&gt;由于继承了 Table，那么也会有 TableMeta 属性。视图的 TableMeta 属性就是 select 的结果集的 fields meta&lt;/p&gt;
&lt;h4 id=&#34;创建视图&#34;&gt;&lt;a class=&#34;markdownIt-Anchor&#34; href=&#34;#创建视图&#34;&gt;&lt;/a&gt; 创建视图&lt;/h4&gt;
&lt;p&gt;&lt;code&gt;CREATE-VIEWS&lt;/code&gt; 的流程相对简单，类似于 &lt;code&gt;CREATE-TABLE-SELECT&lt;/code&gt; 题目。如下：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;code&gt;语法解析&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;创建一个 &lt;code&gt;create_view_stmt&lt;/code&gt; ，在 &lt;code&gt;create_view_stmt&lt;/code&gt; 中创建 SELECT 的 Statement 对象。&lt;/li&gt;
&lt;li&gt;创建 SELECT 的逻辑计划和物理计划，并且把创建好的物理计划指针存入 &lt;code&gt;create_view_stmt&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;create_view_executor&lt;/code&gt; 中执行一遍存入的 SELECT 的物理计划算子，如果没有发生任何报错，则将视图信息插入到 &lt;code&gt;__miniob_views__&lt;/code&gt; 表中（如不存在则创建）。这个表是我自定义的，专门用于存储视图信息。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;在插入成功后，创建视图 &lt;code&gt;View&lt;/code&gt; 对象，动态加入到 &lt;code&gt;Db&lt;/code&gt; 的 opened_views_ 中，这样就不需要重新再从磁盘中读取视图信息了。&lt;/p&gt;
&lt;p&gt;当然了，还需要注意视图的名字不能和视图、实体表重复。&lt;/p&gt;
&lt;p&gt;对于 &lt;code&gt;is_updatable&lt;/code&gt; 字段，如果视图描述中有聚合表达式等不正常的字段，就设为 &lt;code&gt;0&lt;/code&gt;。&lt;/p&gt;
&lt;p&gt;接着，修改了 &lt;code&gt;Db&lt;/code&gt; 的 find_table 方法，如果找不到表，先去找视图。&lt;/p&gt;
&lt;h4 id=&#34;查询视图&#34;&gt;&lt;a class=&#34;markdownIt-Anchor&#34; href=&#34;#查询视图&#34;&gt;&lt;/a&gt; 查询视图&lt;/h4&gt;
&lt;blockquote&gt;
&lt;p&gt;从这里开始，事情开始变得复杂了起来～&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;miniob 中处理 SQL 的阶段都放在一个函数 &lt;code&gt;SqlTaskHandler::handle_sql(SQLStageEvent *sql_event)&lt;/code&gt; 中去实现，阶段包括&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;parse_stage_: 解析 SQL 语句（语法分析）成 &lt;code&gt;ParsedSqlNodes&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;resolve_stage_: 创建 &lt;code&gt;Stmt&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;optimize_stage_: 创建算子 &lt;code&gt;Operator&lt;/code&gt;，生成、优化逻辑计划，生成物理计划&lt;/li&gt;
&lt;li&gt;execute_stage_: 执行算子&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;这个函数的设计是用于处理一条 SQL 语句。而查询视图可不止需要处理一条，需要处理至少两条。一个是查询视图的 SQL，一个是视图定义，&lt;strong&gt;而查询视图的 SQL 中还可能会查询多个视图&lt;/strong&gt;。比如 &lt;code&gt;SELECT * from view_1, view_2;&lt;/code&gt; 就一共需要处理 3 条 SQL 语句。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;一个视图对应一个视图定义，一次视图查询可能对应多个视图。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;可见，查询视图操作需要进行多次 parse_stage_, resolve_stage_，optimize_stage_ 流程。因此我们不得不修改 &lt;code&gt;SqlTaskHandler::handle_sql&lt;/code&gt; 函数。SQLStageEvent 贯穿了整个事件前端，为了方便，我把需要解析的视图定义、视图名字、视图 Stmt 都放在了 SQLStageEvent 中。由于有多个视图，因此这些都用 vector 存储。&lt;/p&gt;
&lt;p&gt;在一次视图查询的解析阶段（parse_stage_）后，我们得到了要查询的视图名字，借此得到所有视图定义，把这些视图定义再进行 parse_stage_，得到 &lt;code&gt;ParsedSqlNodes&lt;/code&gt;。存入 SQLStageEvent 中。&lt;/p&gt;
&lt;p&gt;在 resolve_stage_，我们需要在创建视图查询 Stmt &lt;strong&gt;前&lt;/strong&gt;创建所有的视图定义的 Stmt，因为视图查询的视图也是 Table，需要先初始化其 &lt;code&gt;table_meta&lt;/code&gt;（视图创建好后，是没有初始化它的 TableMeta 的，我设计的是在查询视图的时候才会初始化它的 TableMeta）。而 table meta 的初始化需要各字段的 FieldMeta，而字段信息只能在 Stmt 创建之后得到。&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://cf.s3.soulter.top/2024-oceanbase-database/QQ_1731053332133.png&#34; alt=&#34;&#34; /&gt;&lt;/p&gt;
&lt;p&gt;到了 optimize_stage_，我们需要通过 视图定义 的 Stmt 得到物理算子，然后放入视图的 &lt;code&gt;operator_&lt;/code&gt; 中。&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://cf.s3.soulter.top/2024-oceanbase-database/QQ_1731053556053.png&#34; alt=&#34;&#34; /&gt;&lt;/p&gt;
&lt;p&gt;一切都很简单。&lt;/p&gt;
&lt;p&gt;到了 execute_stage_，我们需要思考一下怎么查询视图。我想到了两个方案。一个是先调用视图定义的算子，把结果集全部取出来，存成一个临时表到磁盘，然后执行查询视图的计划就非常方便了。但是这个方法的一个很大的弊端是需要访问很多次磁盘，在结果集很大的情况下效率非常低。因此，我的第二个方法就是直接把两个计划的算子“拼接起来”。&lt;/p&gt;
&lt;p&gt;在 miniob 中，最底层的算子永远是 &lt;code&gt;TableScanPhysicalOperator&lt;/code&gt;，它调用 Table 实例的 FilePageScanner 来扫描出 Record 再转换成 RowTuple，最顶层的算子永远是 &lt;code&gt;ProjectPhysicalOperator&lt;/code&gt;，它对下层算子的 Tuple 进行投影，转换成 ExpressionTuple。因此，我们的工作就是让 &lt;code&gt;TableScanPhysicalOperator&lt;/code&gt; 对接上 &lt;code&gt;ProjectPhysicalOperator&lt;/code&gt;。带有 Join 算子的场景会不会出问题呢？稍微画个图就知道其实没问题：&lt;/p&gt;
&lt;figure class=&#34;highlight txt&#34;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&#34;gutter&#34;&gt;&lt;pre&gt;&lt;span class=&#34;line&#34;&gt;1&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;2&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;3&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;4&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;5&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&#34;code&#34;&gt;&lt;pre&gt;&lt;span class=&#34;line&#34;&gt;                                 (left)|-&amp;gt;TableScan-&amp;gt;[视图定义的]Project-&amp;gt;...&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;[视图查询的]Project -&amp;gt; Predicate -&amp;gt; Join |&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;                                (right)|-&amp;gt;TableScan-&amp;gt;[视图定义的的]Project-&amp;gt;...&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;// 最简单的带有 Join 的算子执行计划&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/figure&gt;
&lt;p&gt;在 &lt;code&gt;TableScanPhysicalOperator&lt;/code&gt; 中，需要调用 &lt;code&gt;Table::get_record_scanner&lt;/code&gt; 获得 RecordFileScanner 对象，通过 &lt;code&gt;RecordFileScanner::next(Record &amp;amp;record)&lt;/code&gt;，以扫描表内的所有记录。RecordFileScanner 类很复杂，但需要读取表的 Page 文件。而 View 就是个虚拟表，哪来的实体文件？诶 🤓，很简单，这时候之前存的 View 中的 &lt;code&gt;operator_&lt;/code&gt; 就派上用场了，我创建了一个 &lt;code&gt;RecordPhysicalOperatorScanner&lt;/code&gt;，它的 next 方法就是去调用这个 &lt;code&gt;operator_&lt;/code&gt; 物理算子。&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://cf.s3.soulter.top/2024-oceanbase-database/QQ_1731054747452.png&#34; alt=&#34;&#34; /&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://cf.s3.soulter.top/2024-oceanbase-database/QQ_1731054760987.png&#34; alt=&#34;&#34; /&gt;&lt;/p&gt;
&lt;p&gt;得到了 scanner 之后，就是在 &lt;code&gt;TableScanPhysicalOperator::next()&lt;/code&gt; 中去调用这个 scanner 的 next 方法了。如果当前的表是 View，那么就用 &lt;code&gt;RecordPhysicalOperatorScanner&lt;/code&gt; 得到 tuple。需要注意的是，这里的 Tuple 的类型我们是&lt;strong&gt;不知道的&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;我本来打算直接转换成 ValueListTuple 然后共上层调用，但这样导致我在实现视图更新时，上层的 Update 算子无法得到 Record 的 &lt;code&gt;RID&lt;/code&gt; 信息导致无法更新（这里踩了很久的坑），最后，我只能通过 ValueListTuple 来制作 Record 对象，然后通过 Record 对象又创建回 RowTuple。&lt;/p&gt;
&lt;figure class=&#34;highlight cpp&#34;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&#34;gutter&#34;&gt;&lt;pre&gt;&lt;span class=&#34;line&#34;&gt;1&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;2&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;3&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;4&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;5&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;6&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;7&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;8&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&#34;code&#34;&gt;&lt;pre&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;comment&#34;&gt;// while (OB_SUCC(rc = record_scanner_view_.next_tuple())) ...&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;comment&#34;&gt;// 循环体内:&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;keyword&#34;&gt;auto&lt;/span&gt; t_tuple = record_scanner_view_.&lt;span class=&#34;built_in&#34;&gt;current_tuple&lt;/span&gt;();&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;built_in&#34;&gt;LOG_TRACE&lt;/span&gt;(&lt;span class=&#34;string&#34;&gt;&amp;quot;table scan oper got a tuple.&amp;quot;&lt;/span&gt;);&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;ValueListTuple value_list_tuple_ = &lt;span class=&#34;built_in&#34;&gt;ValueListTuple&lt;/span&gt;();&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;ValueListTuple::&lt;span class=&#34;built_in&#34;&gt;make&lt;/span&gt;(*t_tuple, value_list_tuple_);&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;table_-&amp;gt;&lt;span class=&#34;built_in&#34;&gt;make_record&lt;/span&gt;(value_list_tuple_.&lt;span class=&#34;built_in&#34;&gt;cell_num&lt;/span&gt;(), value_list_tuple_.&lt;span class=&#34;built_in&#34;&gt;cells&lt;/span&gt;().&lt;span class=&#34;built_in&#34;&gt;data&lt;/span&gt;(), current_record_); &lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;tuple_.&lt;span class=&#34;built_in&#34;&gt;set_record&lt;/span&gt;(&amp;amp;current_record_); &lt;span class=&#34;comment&#34;&gt;// tuple_ 是 RowTuple&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/figure&gt;
&lt;p&gt;这样的一个好处就是统一了 Table Scan 算子输出的 Tuple 类型。&lt;/p&gt;
&lt;h4 id=&#34;插入视图&#34;&gt;&lt;a class=&#34;markdownIt-Anchor&#34; href=&#34;#插入视图&#34;&gt;&lt;/a&gt; 插入视图&lt;/h4&gt;
&lt;p&gt;插入视图有几大难点：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;如果视图描述有 join，需要知道视图的某个字段属于哪个表。&lt;/li&gt;
&lt;li&gt;如果视图描述中用到了别名，那么视图的字段名和表的字段名可能不一样，需要做映射。&lt;/li&gt;
&lt;li&gt;插入语句也可以指定要插入的列的，比如 &lt;code&gt;insert into view1(id, name) values (1, &#39;a&#39;)&lt;/code&gt; 这种情况。此时需要大改 INSERT Stmt。（但是测试用例好像没有这个开，不过我还是按照这个思路去做实现了）&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;并且，MYSQL 对 JOIN View 的插入是有限制的：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;不能&lt;strong&gt;同时&lt;/strong&gt;插入两个表的数据。&lt;/li&gt;
&lt;li&gt;插入的时候需要&lt;strong&gt;指定字段&lt;/strong&gt;。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;比如，有两个表 &lt;code&gt;tb1&lt;/code&gt; 和 &lt;code&gt;tb2&lt;/code&gt; 各有两个 int 列，视图 &lt;code&gt;v1&lt;/code&gt; 的描述是 &lt;code&gt;SELECT tb1.id, tb2.num FROM tb1, tb2;&lt;/code&gt;。&lt;/p&gt;
&lt;p&gt;那么：&lt;/p&gt;
&lt;figure class=&#34;highlight sql&#34;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&#34;gutter&#34;&gt;&lt;pre&gt;&lt;span class=&#34;line&#34;&gt;1&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;2&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;3&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&#34;code&#34;&gt;&lt;pre&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;keyword&#34;&gt;INSERT INTO&lt;/span&gt; v1 &lt;span class=&#34;keyword&#34;&gt;VALUES&lt;/span&gt; (&lt;span class=&#34;number&#34;&gt;1&lt;/span&gt;, &lt;span class=&#34;number&#34;&gt;2&lt;/span&gt;); &lt;span class=&#34;comment&#34;&gt;-- X&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;keyword&#34;&gt;INSERT INTO&lt;/span&gt; v1(id, num) &lt;span class=&#34;keyword&#34;&gt;VALUES&lt;/span&gt; (&lt;span class=&#34;number&#34;&gt;1&lt;/span&gt;, &lt;span class=&#34;number&#34;&gt;2&lt;/span&gt;); &lt;span class=&#34;comment&#34;&gt;-- X&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;keyword&#34;&gt;INSERT INTO&lt;/span&gt; v1(id) &lt;span class=&#34;keyword&#34;&gt;VALUES&lt;/span&gt; (&lt;span class=&#34;number&#34;&gt;1&lt;/span&gt;); &lt;span class=&#34;comment&#34;&gt;-- 正确&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/figure&gt;
&lt;p&gt;这时候，之前定义的 View 的 &lt;code&gt;field_base_table_name&lt;/code&gt; 和 &lt;code&gt;attr_name_2_base_table_field_name&lt;/code&gt; 就派上用场了。一个是用于判断字段属于哪个表，一个是在指定了视图字段名的情况下，找到对应的表字段名。这两个 map 都很好实现，这里不赘述。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Note: 1. 如果 attrs_name_ 不为空，说明视图的字段名是自定义的，并且长度严格等于 SELECT 的结果集的列数——这个是在创建视图就应该保证的。&lt;br /&gt;
2. 如果 base_tables_ 长度大于 1，说明视图是 join view。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;在 Insert Stmt 中，做一些 Join View 的相关 fast-fail 检测。如果指定了插入的字段，那么存在 value list 长度不等于 view 字段长度的情况，miniob 原实现会导致 crash，这里需要做相关更改。&lt;/p&gt;
&lt;p&gt;在 Insert 物理算子中，如果 attr_name_2_base_table_field_name 不为空，就通过 &lt;code&gt;attr_name_2_base_table_field_name&lt;/code&gt; 找到原字段名，然后就可以找到对应的表。然后根据表的 Field Meta 列表，创建 Value List。这里注意 没指定插入的或者视图中没有但表中有的字段，需要设置为 NULL（如果原表字段设置了 nullable 为 false 就报错）。&lt;/p&gt;
&lt;p&gt;MYSQL 不支持同时插入多表，但我这里额外实现了这个功能。很简单，在最外层循环套一个 base_tables_ 循环，然后对每个 base_table，根据 &lt;code&gt;attr_name_2_base_table_field_name&lt;/code&gt; 和 &lt;code&gt;field_base_table_name&lt;/code&gt; 找到对应的字段，创建 Value List 再进行插入操作即可。似乎不是很难，但很难理解为什么 MYSQL 不支持这个功能。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;实现的过程中，犯了一个错误: &lt;code&gt;field_meta.table_name_ = *field_expr.table_name();&lt;/code&gt;&lt;br /&gt;
这里 table_name() 返回一个 const char *，我本来想解引用 field_expr，然后调用 table_name()，但是由于没加括号，导致 field_meta.table_name_ 取的是这个字符串的首地址，得到的是第一个字符。正确写法是 &lt;code&gt;field_meta.table_name_ = （*field_expr).table_name();&lt;/code&gt; 或者 &lt;code&gt;field_meta.table_name_ = field_expr-&amp;gt;table_name();&lt;/code&gt;。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h4 id=&#34;更新视图&#34;&gt;&lt;a class=&#34;markdownIt-Anchor&#34; href=&#34;#更新视图&#34;&gt;&lt;/a&gt; 更新视图&lt;/h4&gt;
&lt;p&gt;更新视图是整个视图实现中最复杂的部分。除了 Insert 上提到的字段映射的问题，还有一个比较棘手的问题：更新算子需要知道更新的这个 Tuple 的 RID 和 Tuple 所属的表名（不是视图名），然而除了 RowTuple 还持有 RID 之外，所有的 Tuple 都不存储这些信息。事实上，原来的 miniob 的更新算子中断言了它获取到的 Tuple 是 RowTuple（这也是上面在讲 TableScan 时为什么要转换回 RowTuple 的原因）。&lt;/p&gt;
&lt;p&gt;字段转换的问题解决了，逻辑很复杂，但思想很简单，和上面插入视图本质上很像，不赘述了。那后面这个问题怎么解决呢？&lt;/p&gt;
&lt;p&gt;一开始，思考了许久，我决定在 Tuple 上直接加一个 &lt;code&gt;RID rid_&lt;/code&gt; 和 &lt;code&gt;std::string table_name_&lt;/code&gt; 变量，在 TableScan 算子制作 RowTuple 时，更新这两个变量。这样就够使得所有 Tuple 都有这两个信息了。需要额外考虑一点，在高层的算子，比如 Order By、Predict，他们会重新创建 Tuple 会使得这两个信息丢失，因此在这些算子中，需要重新设置这两个信息。（这也是我思考了许久的原因，因为这样会改很多算子，我懒orz，一直在思考如何在改动最小的基础上加这个功能）。跑了一下，诶！怎么更新算子没在表中找到指定 RID 的记录？Debug 之后才发现是在写入 RID 的值时 page_num 和 slot_num 写反了。。修改之后，更新视图就可以正常运行了。&lt;/p&gt;
&lt;p&gt;我本能地测试了一下 Join View，可惜的是，还是出问题了——还记得我们是如何实现视图查询算子和视图定义查询算子的连接的吗？先将视图定义查询算子传递的 Tuple 转换成 ValueListTuple，然后转换成 Record，最后再转换成 RowTuple。&lt;/p&gt;
&lt;p&gt;问题出在这个转换过程，我们损失了所有我们自己加的信息，只保留了算子传递上来的 Tuple 中 cells 中的数据信息，导致 RowTuple 中的 Cells 可能来自不同的表。&lt;/p&gt;
&lt;p&gt;这就有点棘手了。最后（也是没有办法的办法），我给 cell，也就是 Value 类都加上了这两个信息，也就是直接把粒度放到了最小。&lt;/p&gt;
&lt;p&gt;然后在 RowTuple 中，再单独开了 rid_list_ 和 table_name_list_ 在转换成 ValueListTuple 之后，把其中的 cell 中的 RID 和 table_name 都写入到这两个 list 中。这样，Update 算子就能知道这个 RowTuple 中各个 Cell 的 RID 和 table_name 了。&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://cf.s3.soulter.top/2024-oceanbase-database/image.png&#34; alt=&#34;&#34; /&gt;&lt;/p&gt;
&lt;h3 id=&#34;总结&#34;&gt;&lt;a class=&#34;markdownIt-Anchor&#34; href=&#34;#总结&#34;&gt;&lt;/a&gt; 总结&lt;/h3&gt;
&lt;p&gt;在实现视图中，时刻需要注意当前 table 的 table_name 是视图的 table name 还是视图定义中表的 table name，别看这两个相差很远，实际上在写功能的时候很容易弄混淆。此外，还需要注意字段名，对应关系一定要存清楚。明确创建视图的 field_list 和视图定义中的表和表中的 fields 的对应关系。&lt;/p&gt;
&lt;figure class=&#34;highlight plaintext&#34;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&#34;gutter&#34;&gt;&lt;pre&gt;&lt;span class=&#34;line&#34;&gt;1&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;2&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&#34;code&#34;&gt;&lt;pre&gt;&lt;span class=&#34;line&#34;&gt;1. 视图中的 table_meta 中的 field_meta 的列名的来源是：创建视图的field_list &amp;gt; 视图定义的查询字段别名 &amp;gt; 视图定义的查询字段名。其中创建视图的field_list要给就全部给完，不能只给一部分。因此 View 的 attr_list 要么为空，要么其长度等于视图定义的查询字段数。&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;2. 视图中的 table_meta 的 table_name 就是视图的名字 view_name。&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/figure&gt;
&lt;p&gt;此外，记忆犹新的一个测试用例是 &lt;code&gt;create view v1 as select * from tb1 t1, tb1 t2 where t1.id = t2.id;&lt;/code&gt;，特殊在它 join 的表是同一个表。这导致我的代码 join 出来的结果和实际的结果不一样（甚至不是视图的问题），debug 之后意识到我在 select_stmt，也就是上层就把所有的 alias 给替换成了真实的表名，导致底层的 predicate 算子分不清 join 传上来的 tuple 中的 cell 的字段来自于哪个表。这又得让我重新把 table_alias 从上层传到底层。。太搞人心态了。&lt;/p&gt;
&lt;p&gt;最后，在重复提测好几次之后，在 2024 年 11 月 8 日 01:37 分通过了这重量级的一题。wakatime 上显示我在实现这个分支上花了 30 小时。&lt;/p&gt;
</content>
        <category term="2024" />
        <category term="数据库" />
        <category term="计算机系统能力大赛" />
        <updated>2024-11-08T21:31:00.000Z</updated>
    </entry>
    <entry>
        <id>https://blog.soulter.top/posts/introducing-tickstats.html</id>
        <title>Introducing TickStats</title>
        <link rel="alternate" href="https://blog.soulter.top/posts/introducing-tickstats.html"/>
        <content type="html">&lt;h2 id=&#34;简介&#34;&gt;&lt;a class=&#34;markdownIt-Anchor&#34; href=&#34;#简介&#34;&gt;&lt;/a&gt; 简介&lt;/h2&gt;
&lt;p&gt;TickStats 是孵化自 &lt;a href=&#34;https://idoknow.top&#34;&gt;@idoknow&lt;/a&gt; 的一个开源（即将）项目，当前主要由我全程参与设计和功能开发。它旨在提供一个轻量化、易用的指标收集与指标可视化的一体化解决方案。相比于 &lt;code&gt;prometheus&lt;/code&gt;、&lt;code&gt;grafana&lt;/code&gt; 这些服务，TickStats 更加轻量化，能够做到开箱即用，适用于中小型项目等场景。&lt;/p&gt;
&lt;p&gt;Demo: &lt;a href=&#34;https://tickstats.idoknow.top&#34;&gt;https://tickstats.soulter.top&lt;/a&gt;&lt;br /&gt;
Soulter’s TickStats Stat: &lt;a href=&#34;https://tickstats.idoknow.top/app/66049a53&#34;&gt;https://tickstats.soulter.top/app/66049a53&lt;/a&gt;&lt;/p&gt;
&lt;h2 id=&#34;缘起&#34;&gt;&lt;a class=&#34;markdownIt-Anchor&#34; href=&#34;#缘起&#34;&gt;&lt;/a&gt; 缘起&lt;/h2&gt;
&lt;p&gt;可观测性对于开发者来说非常重要。开发者需要知道项目的运行状态、性能指标、用户使用数据等信息，以便及时发现问题并进行优化。一般来说，首选的解决方案是使用 &lt;code&gt;prometheus&lt;/code&gt; 作为指标收集工具，&lt;code&gt;grafana&lt;/code&gt; 作为指标可视化工具。但是，这两个工具的配置和使用都比较复杂，对于一些小型项目来说，可能会显得有些“大材小用”，并且这些服务的运行也会产生服务器开销。&lt;/p&gt;
&lt;p&gt;此外，我们注意到很多的项目都开发了自己的指标收集系统，这些系统的功能都是类似的，但是都是独立开发的，没有统一的标准。这样就导致了很多的重复劳动，而且这些系统的功能也比较简单，没有很好的可视化功能。&lt;/p&gt;
&lt;p&gt;基于上述问题，我们希望设计出一套系统来加以解决。&lt;/p&gt;
&lt;p&gt;TickStats 只暴露了一个数据上报接口，只需要 POST 一个简单的 Json 数据：&lt;/p&gt;
&lt;figure class=&#34;highlight json&#34;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&#34;gutter&#34;&gt;&lt;pre&gt;&lt;span class=&#34;line&#34;&gt;1&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;2&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;3&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;4&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;5&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;6&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;7&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;8&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;9&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&#34;code&#34;&gt;&lt;pre&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;punctuation&#34;&gt;&amp;#123;&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;    &lt;span class=&#34;attr&#34;&gt;&amp;quot;metrics_data&amp;quot;&lt;/span&gt;&lt;span class=&#34;punctuation&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;punctuation&#34;&gt;&amp;#123;&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;        &lt;span class=&#34;attr&#34;&gt;&amp;quot;used_count&amp;quot;&lt;/span&gt;&lt;span class=&#34;punctuation&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;number&#34;&gt;12&lt;/span&gt;&lt;span class=&#34;punctuation&#34;&gt;,&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;        &lt;span class=&#34;attr&#34;&gt;&amp;quot;os_name&amp;quot;&lt;/span&gt;&lt;span class=&#34;punctuation&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;string&#34;&gt;&amp;quot;Mac&amp;quot;&lt;/span&gt;&lt;span class=&#34;punctuation&#34;&gt;,&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;        &lt;span class=&#34;attr&#34;&gt;&amp;quot;cpu_load&amp;quot;&lt;/span&gt;&lt;span class=&#34;punctuation&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;number&#34;&gt;1.23&lt;/span&gt;&lt;span class=&#34;punctuation&#34;&gt;,&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;        &lt;span class=&#34;attr&#34;&gt;&amp;quot;on_user_click_purchase_btn&amp;quot;&lt;/span&gt;&lt;span class=&#34;punctuation&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;number&#34;&gt;1&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;        &lt;span class=&#34;comment&#34;&gt;// everything you want to upload. KeyName: Value&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;    &lt;span class=&#34;punctuation&#34;&gt;&amp;#125;&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;punctuation&#34;&gt;&amp;#125;&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/figure&gt;
&lt;p&gt;即可完成指标收集。无论是 Event-based 还是 Time-based 的指标，都可以通过这个接口上报。&lt;/p&gt;
&lt;p&gt;然后在数据面板添加对应的图表，并绑定 &lt;code&gt;KeyName&lt;/code&gt;，即可完成指标可视化。&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://cf.s3.soulter.top/introducing-tickstats/image.png&#34; alt=&#34;&#34; /&gt;&lt;/p&gt;
&lt;h2 id=&#34;开发中&#34;&gt;&lt;a class=&#34;markdownIt-Anchor&#34; href=&#34;#开发中&#34;&gt;&lt;/a&gt; 开发中&lt;/h2&gt;
&lt;p&gt;目前该项目已经完成第一阶段的开发，已经处于能用的状态了。团队正在着手第二阶段开发和内部稳定性测试，Todo List 如下：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;WebHook: 可自定义的 Hook，如异常指标报警、Weekly Report，可与 Mail、OneBot、HTTP 协议对接。&lt;/li&gt;
&lt;li&gt;更多的图表和数据分析类型：包括但不限于雷达图、柱状图、Gauge 等，以 cover 到更多的场景。&lt;/li&gt;
&lt;li&gt;指标模板: 提供一些常用的指标模板，方便用户一键生成。&lt;/li&gt;
&lt;li&gt;Embed Chart: 可以将图表嵌入到其他网页中。&lt;/li&gt;
&lt;li&gt;Application Description: 为每个 Application 添加描述，方便管理。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;此外，第三阶段的开发计划如下：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;SDK 支持：提供 SDK，方便用户在各种语言中使用 TickStats。&lt;/li&gt;
&lt;li&gt;Paid Plan: 提供更多的服务，如更长的数据预览、更粒度化的 Data Point、更多 Application 等。&lt;/li&gt;
&lt;li&gt;特化网站分析能力：提供网站分析的能力，如用户访问量、用户地理位置、用户设备等。【讨论中】&lt;/li&gt;
&lt;li&gt;插件: [特化网站分析能力] 的一个超集。提供插件机制，用户可以自定义插件，实现更多的功能。【讨论中】&lt;/li&gt;
&lt;/ul&gt;
</content>
        <category term="开源" />
        <category term="数据可视化" />
        <updated>2024-10-25T14:00:00.000Z</updated>
    </entry>
    <entry>
        <id>https://blog.soulter.top/posts/2024-09-after-baoyan.html</id>
        <title>写在保研之后</title>
        <link rel="alternate" href="https://blog.soulter.top/posts/2024-09-after-baoyan.html"/>
        <content type="html">&lt;p&gt;9 月 29 日早上，一条短信发来：&lt;/p&gt;
&lt;p&gt;「XX大学通过推免系统给您发送了待录取通知」&lt;/p&gt;
&lt;p&gt;点下“接受”按钮，全国无数的保研人给这 5 个月的保研准备画上了圆满的句号。&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://cf.s3.soulter.top/2024.09-after-baoyan/bc38c89813b61cf313a0c6d5fc823363.png&#34; alt=&#34;&#34; title=&#34;综合考虑，最终去向是北邮AI院。保研中途放弃了国科大+自动化所、东南软院、华师软院&#34; /&gt;&lt;/p&gt;
&lt;p&gt;回首这五个月，我过的其实并不是那么顺利。&lt;/p&gt;
&lt;h3 id=&#34;心路历程&#34;&gt;&lt;a class=&#34;markdownIt-Anchor&#34; href=&#34;#心路历程&#34;&gt;&lt;/a&gt; 心路历程&lt;/h3&gt;
&lt;p&gt;从今年 4 月开始，我就开始在各大心仪高校官网上翻阅，期望寻找到一个感兴趣方向的导师。很多老师都很友善，及时给了回信，留了微信。特别是复旦大学的老师的回信中写道我的能力很出色，心里暗自窃喜。&lt;/p&gt;
&lt;p&gt;然而，我的“BG”（背景）当时是有很大缺陷的——虽然排名靠前（前5%），但是没有通过六级。&lt;/p&gt;
&lt;p&gt;很快，就到了夏令营，投了十几所心仪的高校，也仅仅只有寥寥一两所入营。最后由于各种原因，入营机会也被我放弃了。记得之前看到的知乎一篇经验贴中博主被高校十几杀之后还笑了出来，没想到反应过来时自己已是画中人。&lt;/p&gt;
&lt;p&gt;这期间我还在中国科学院自动化研究所实习，老师们都很好，给我安慰。&lt;/p&gt;
&lt;p&gt;痛定思痛，哪里不行补哪里。我开始认真准备接下来的六级、雅思。由于放暑假，我成了空旷且凉爽的逸夫楼的常客。每天除了实习和维护项目就是学雅思。说实话，雅思没有想象中那么难。&lt;/p&gt;
&lt;p&gt;也许是受他人影响，记得很清楚，在 8 月 22 日，偶然间看到其他人都有了满意的去向之后，我开始极度焦虑于出国、直接就业、还是继续读研。“早知道挂一科就好了，就不用这么纠结了”、“要黑化了”。&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://cf.s3.soulter.top/2024.09-after-baoyan/QQ_1727588583978.png&#34; alt=&#34;&#34; /&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://cf.s3.soulter.top/2024.09-after-baoyan/QQ_1727594596460.png&#34; alt=&#34;&#34; /&gt;&lt;/p&gt;
&lt;p&gt;天无绝人之路。在第二天，东南大学就给我发了入营通知。我的六级成绩也出来了，542 分——这个分数在国内基本不会被卡。头顶的乌云顿时消散一半。虽然最后的结果是候补，知道结果当天整个晚上没睡好觉，但是联系的导师说机会很大，也给了我继续下去的信心。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;（嗯，机会确实大，从结果来看今年东南软件学院延续了往年的战绩，继续被鸽穿，居然候补到了&lt;strong&gt;我之后的70名&lt;/strong&gt;）&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;之后，本校的一条信息吸引了我：走本硕贯通可以两年毕业。说实话，这几个月的经历已经让我觉得自己不太适合科研了，我的技术栈领域更适合我进行工程开发。读本校也有很多优势：留在北京、两年毕业、科研压力相对较小。并且，反复的翻官网、找导师、发邮件、写推荐信、准备文书已经让我感到厌恶。因此，我开始强烈倾向于保本校。制订好未来的目标之后，心情总算是好了很多。&lt;/p&gt;
&lt;p&gt;然而，在定好目标之后的我又通过了国科大-自动化所的联培面试。听闻自动化所属于计算机领域的 TOP 院校，仅次于清北，我开始有所动摇。但是如果选择了这个，就将意味着之前做的计划全部作废。最终，考虑到个人发展和兴趣，我还是放弃了这个机会，选择了本校。&lt;/p&gt;
&lt;p&gt;9月28日，我率先填下了北科、东南，然后开始等待。之后偶然间看到北邮的招生通知，方向是 LLM、Web3，并且团队氛围很好。由于我对这两个方向都很感兴趣，并且对北邮印象很好，北邮的就业情况也是超越国内大多数 985，于是就给北邮发了邮件，放手一搏。最终，在这个极限的时间点，我通过了面试，收到了北邮的录取通知。&lt;/p&gt;
&lt;p&gt;很多人微信私信问我专业 rank 2+自所实习+多段“大厂”实习经历为什么不去 985 去了一所 211。这我想放到后文再说。To be frank，我完全不后悔于这个选择，相反我很满意，完全对得起我这 5 个月的付出。用朋友的一句话来说——“自己爽就行”。只要我觉得成功，那就是成功。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;况且，北邮也不差吧！社交平台上多少弃9去邮的！！&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3 id=&#34;大学三年&#34;&gt;&lt;a class=&#34;markdownIt-Anchor&#34; href=&#34;#大学三年&#34;&gt;&lt;/a&gt; 大学三年&lt;/h3&gt;
&lt;p&gt;我很荣幸，&lt;strong&gt;在大学三年没有成为硬卷绩点、硬卷加分的一类人&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;占据我这三年时间最多的，当属编程了。&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://cf.s3.soulter.top/2024.09-after-baoyan/QQ_1727590240470.png&#34; alt=&#34;&#34; /&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://cf.s3.soulter.top/2024.09-after-baoyan/QQ_1727594774885.png&#34; alt=&#34;&#34; /&gt;&lt;/p&gt;
&lt;p&gt;学生活动中心二楼的两个角落的桌子、图书馆、806 实验室，一杯咖啡、一台电脑、一只耳机，很多时候我一待就是大半天。修修Blog、写写开源项目的feature、看新技术的文档…无论哪一项都很快活，尤其是看懂某个技术当中以前自己从来没考虑过的设计思想、某个开源项目内部优雅的架构时不自觉的兴奋，都让我觉得很享受。我前前公司的 mentor 说我的学习是“发散性的”，学校的导师说我的学习是“探索型的”，现在想想也对，这让我形成了全栈开发的能力，能够独自解决问题。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;大三之后去图书馆就去的少了，安静的氛围很是压抑，还是学活、KFC、星巴克这些有点吵闹的地方更适合我，以及我在写这篇文章的时候就在斯达巴克斯～&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;我在大一时其实不是计算机专业的。高考之后，我被调剂到了机械工程专业，到了大一下才转到计算机。由于缺失很多计算机的通选课，大二的课程直接拉满，甚至还有几门课和另外的必修课冲突，导致我从大二上开始就很少去线下上课，练就了一身逃课和自学本领。现在想想嘛，还是挺怀念的。经历得越多，就越不会留下缺憾。&lt;/p&gt;
&lt;p&gt;大二下，就业压力席卷而来，我也有幸在搜狐、小红书进行了实习。&lt;/p&gt;
&lt;p&gt;大三还学会了吉他，能够演奏自己喜欢的曲子了。&lt;/p&gt;
&lt;h3 id=&#34;未来如何&#34;&gt;&lt;a class=&#34;markdownIt-Anchor&#34; href=&#34;#未来如何&#34;&gt;&lt;/a&gt; 未来如何&lt;/h3&gt;
&lt;p&gt;如果看过我以前的文章，就会发现我一直在提起这个话题（并且目标时常在变）。这也应该是很多人共有的人生问题吧。&lt;/p&gt;
&lt;p&gt;既然读研了，一定会找准两个方向（研究方向一个，开发方向一个）来深入。三年的经历告诉我广泛的涉猎而不深入容易让人找不到归属感。很幸运的是，从纸面上看，我对我未来的导师的方向全都很感兴趣，无论是多模态LLM，还是Web3。北邮的就业优势、相较985低一些的科研压力（这里画一个问号）也能让我更加专注于我所热爱的技术栈的学习（而不是en卷力扣、背面试八股文）。&lt;/p&gt;
&lt;p&gt;so，上文提到的为什么我这个成绩会选择北邮，就有了答案。我的目标是工程开发、独立软件开发、就业/创业向，科研只是一部分，相对宽松的科研环境更适合我（这里画一个问号）。并且，北邮的就业在全国高校中遥遥领先，大多数企业都会把北邮当中上9看待，我不会为就业产生过多的焦虑。&lt;/p&gt;
&lt;p&gt;除此之外，我会以更积极的姿态投身于 FOSS（Free and open -source Software）领域，给伟大的开源项目提交贡献（不仅是自己的拙作）。向那些优秀的开源大佬、独立开发大佬看齐。我坚信，自由开源软件是互联网的血脉。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;列举几个大佬：&lt;a href=&#34;https://github.com/innei&#34;&gt;@innei&lt;/a&gt;，&lt;a href=&#34;https://github.com/diygod&#34;&gt;@DIYGod&lt;/a&gt;，&lt;a href=&#34;https://github.com/Menci&#34;&gt;@Menci&lt;/a&gt;，&lt;a href=&#34;https://github.com/yanyongyu&#34;&gt;@yanyongyu&lt;/a&gt;，&lt;a href=&#34;https://github.com/Rockchinq&#34;&gt;@Rockchin&lt;/a&gt;，&lt;a href=&#34;https://github.com/NaturalSelect&#34;&gt;@自然选择&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;span class=&#34;black-mask&#34;&gt;然后我会切实提高自己的沟通交流能力和语言能力。从面试、与人打交道的情况来看，这是我目前最大的短板&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;也许为了顺应现实的评价体系和社会秩序，可能又会不得不花费一些不必要的时间，但是又有谁能够完全摆脱这些呢？这也是人生的一部分吧。毕竟都是自己的选择，只要能够找到合适的理由说服自己就可以了。人生如棋，落子无悔。&lt;/p&gt;
&lt;h3 id=&#34;总结&#34;&gt;&lt;a class=&#34;markdownIt-Anchor&#34; href=&#34;#总结&#34;&gt;&lt;/a&gt; 总结&lt;/h3&gt;
&lt;p&gt;也许是以前过得太顺利了，没品尝过失败的滋味（不管是自所的老师还是我的父母都这样说过我），这次保研经历对我最大的帮助就是让我经历过了一次次的低谷，尝到了苦头，我能更加笃定，坚实地履行计划。得之坦然，失之淡然。&lt;/p&gt;
&lt;p&gt;最后，你所__的，就是你的__！并且感谢一路走来帮助过我的朋友们、老师们、绿群。&lt;/p&gt;
&lt;h3 id=&#34;致后面的保研er&#34;&gt;&lt;a class=&#34;markdownIt-Anchor&#34; href=&#34;#致后面的保研er&#34;&gt;&lt;/a&gt; 致后面的保研er&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;如果保研政策没变，绿群圣经是绝对正确的，不要怀疑。928之后鸽子成群远超你的想象。南大、同济、复旦、中山、中科大、西交部分计算机院系都被不同程度鸽穿。不要焦虑，相信圣经。&lt;/li&gt;
&lt;li&gt;如果你不是科研向的，不要en卷课内。公司永远都注重你的实践经历。&lt;/li&gt;
&lt;li&gt;不要为了 title 去了不好的专业/导师，一定要选择适合自己的，综合考虑。&lt;strong&gt;门槛并不等于价值。许多人会自然而然地认为难考的学校就是比容易考的学校好。&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;几次短暂的失败对你是有潜移默化的帮助的，能在这么年轻的时候经历几次波折多好，可以反复咀嚼消化，顺风局多没意思。&lt;/li&gt;
&lt;li&gt;当遇到多个选择摇摆不定时，停下来，问问内心自己真正喜欢什么，然后把它们和自己的计划都用手记录列举下来。你会发现清晰很多。&lt;/li&gt;
&lt;li&gt;注意身体，多喝水少熬夜。&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 id=&#34;2024-保研名场面&#34;&gt;&lt;a class=&#34;markdownIt-Anchor&#34; href=&#34;#2024-保研名场面&#34;&gt;&lt;/a&gt; 2024 保研名场面&lt;/h3&gt;
&lt;p&gt;上面提到了绿裙，就不得不分享今年的保研名场面了。&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://cf.s3.soulter.top/2024.09-after-baoyan/image.png&#34; alt=&#34;&#34; /&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://cf.s3.soulter.top/2024.09-after-baoyan/059c10b80eb8b69642541b6e12f1b3a7.png&#34; alt=&#34;&#34; /&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://cf.s3.soulter.top/2024.09-after-baoyan/e9e818f66451c877ade0b3ce9b70fc96.png&#34; alt=&#34;&#34; /&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://cf.s3.soulter.top/2024.09-after-baoyan/344b052e0af32d01de509a12a8df6a26.png&#34; alt=&#34;&#34; /&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://cf.s3.soulter.top/2024.09-after-baoyan/f128161c70e2f8b957400006b5a860fe.png&#34; alt=&#34;&#34; /&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://cf.s3.soulter.top/2024.09-after-baoyan/f60bfab3195ea3d289454d30b20bce0d_720.png&#34; alt=&#34;&#34; /&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://cf.s3.soulter.top/2024.09-after-baoyan/cef600e21541fd566b0b2e5aaf6d3256.png&#34; alt=&#34;&#34; /&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://cf.s3.soulter.top/2024.09-after-baoyan/480a990adfca793783d4deb134b4748b.jpeg&#34; alt=&#34;&#34; /&gt;&lt;/p&gt;
</content>
        <category term="2024" />
        <category term="保研" />
        <updated>2024-09-30T21:30:00.000Z</updated>
    </entry>
    <entry>
        <id>https://blog.soulter.top/posts/visualize-music.html</id>
        <title>如何从零开始可视化一首音乐？</title>
        <link rel="alternate" href="https://blog.soulter.top/posts/visualize-music.html"/>
        <content type="html">&lt;h1 id=&#34;背景&#34;&gt;&lt;a class=&#34;markdownIt-Anchor&#34; href=&#34;#背景&#34;&gt;&lt;/a&gt; 背景&lt;/h1&gt;
&lt;p&gt;最近换上了 ArchLinux，体验了众多美观的 TUI 应用。其中我认为最酷炫的当属 &lt;code&gt;ncmpcpp&lt;/code&gt; 这个音乐播放器了。&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://cf.s3.soulter.top/visualize-music/ncmpcpp_demo.gif&#34; alt=&#34;&#34; title=&#34;ncmpcpp 的可视化音频效果&#34; /&gt;&lt;/p&gt;
&lt;p&gt;日常办公时把它放在屏幕角落，就会感觉工作环境变得更有趣了。这也让我对它的实现产生了巨大的好奇心，于是我决定探索一下它是如何实现的。&lt;/p&gt;
&lt;p&gt;本文将深入底层，从音频编码开始讲起，探究 &lt;code&gt;wav&lt;/code&gt; 格式的音频文件是如何被解析的，然后再讲解如何通过傅立叶变换将音频信号转换为频谱图，最后再着手实现。&lt;/p&gt;
&lt;h1 id=&#34;音频编码&#34;&gt;&lt;a class=&#34;markdownIt-Anchor&#34; href=&#34;#音频编码&#34;&gt;&lt;/a&gt; 音频编码&lt;/h1&gt;
&lt;h2 id=&#34;声波&#34;&gt;&lt;a class=&#34;markdownIt-Anchor&#34; href=&#34;#声波&#34;&gt;&lt;/a&gt; 声波&lt;/h2&gt;
&lt;p&gt;中学物理教过我们，声音是一种机械波，由物体振动产生，通过介质传播。当波通过介质传递到耳朵时，耳膜会随之振动，我们才能听到声音。&lt;/p&gt;
&lt;p&gt;声波会引起麦克风（注：这里以动圈式麦克风为例）传感器中的振膜振动，振膜与被磁铁包围着住的线圈相连，根据法拉第定律和楞次定律，振膜振动会使磁场中线圈移动而产生感应电流。此时，只需要监测线圈两极的电压即可得到声音的波形图。机械振动就被转换为了电压信号。&lt;/p&gt;
&lt;p&gt;我们现在得到的是连续的模拟信号，还需要将其转换成计算机可识别的离散的数字信号。&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://cf.s3.soulter.top/visualize-music/image.png#full&#34; alt=&#34;&#34; title=&#34;连续的模拟信号。图片来源@cjting&#34; /&gt;&lt;/p&gt;
&lt;p&gt;这时候，我们需要对信号进行模数转换（ADC）。AD 芯片每隔一段时间（几微秒）对波形图打点采样，得到每一个点的电压值，然后量化为二进制数，最后再进行编码，我们就得到了音频波形数据。&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://cf.s3.soulter.top/visualize-music/image-1.png#full&#34; alt=&#34;&#34; title=&#34;采样。图片来源@cjting&#34; /&gt;&lt;/p&gt;
&lt;h2 id=&#34;wav-格式&#34;&gt;&lt;a class=&#34;markdownIt-Anchor&#34; href=&#34;#wav-格式&#34;&gt;&lt;/a&gt; WAV 格式&lt;/h2&gt;
&lt;p&gt;和图像、视频一样，音频编码格式也主要分为两大类：有损压缩和无损压缩格式。&lt;/p&gt;
&lt;p&gt;&lt;code&gt;wav&lt;/code&gt; 就是一种典型的无损压缩格式，它是由微软和 IBM 共同开发的一种音频文件格式。它被称为“波形文件”，它也是相对好理解的一个音频格式。&lt;/p&gt;
&lt;p&gt;一个完整的 &lt;code&gt;wav&lt;/code&gt; 文件必须包含两个区块：&lt;code&gt;Format Chunk&lt;/code&gt; 和 &lt;code&gt;Data Chunk&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;下面是一个包含了以上两个 Chunk 的 WAV 文件的例子：&lt;/p&gt;
&lt;figure class=&#34;highlight plaintext&#34;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&#34;gutter&#34;&gt;&lt;pre&gt;&lt;span class=&#34;line&#34;&gt;1&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;2&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;3&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;4&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;5&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;6&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;7&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;8&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;9&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;10&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;11&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;12&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;13&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&#34;code&#34;&gt;&lt;pre&gt;&lt;span class=&#34;line&#34;&gt; __________________________&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;| RIFF WAVE Chunk	   |&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;|   groupID  = &amp;#x27;RIFF&amp;#x27;      |&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;|   riffType = &amp;#x27;WAVE&amp;#x27;      |&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;|    __________________    |&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;|   | Format Chunk     |   |&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;|   |	ckID = &amp;#x27;fmt &amp;#x27;  |   |&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;|   |__________________|   |&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;|    __________________    |&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;|   | Sound Data Chunk |   |&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;|   |	ckID = &amp;#x27;data&amp;#x27;  |   |&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;|   |__________________|   |&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;|__________________________|&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/figure&gt;
&lt;p&gt;&lt;code&gt;Format Chunk&lt;/code&gt; 包含了重要的描述波形的参数：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;采样率（Sample Rate）：每秒采样模拟信号的次数&lt;/li&gt;
&lt;li&gt;采样宽度（Sample Width）：每个采样点的位数&lt;/li&gt;
&lt;li&gt;声道数量（Audio Channels）&lt;/li&gt;
&lt;li&gt;采样帧总数（Sample Frame）&lt;/li&gt;
&lt;li&gt;压缩方式（Compression Type）&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;code&gt;Data Chunk&lt;/code&gt; 包含了实际的波形数据，这些数据由一连串的采样帧组成，按时间顺序排列。&lt;/p&gt;
&lt;p&gt;一个采样点表示一次采样内声音的振幅值。采样点位数越高，可表示的值的范围就越大，音频的质量就越好，但是文件的大小也就越大。一个采样帧包含了一次采样的所有声道的采样点。比如，我有左右两个声道，每个声道有 16 位的采样点，那么一个采样帧就包含了 32 位的数据。&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://cf.s3.soulter.top/visualize-music/image-2.png&#34; alt=&#34;&#34; /&gt;&lt;/p&gt;
&lt;p&gt;在 Python 中，我们可以使用 &lt;code&gt;wave&lt;/code&gt; 模块来读取 &lt;code&gt;wav&lt;/code&gt; 文件：&lt;/p&gt;
&lt;p&gt;440hz_16bit_5s.wav:&lt;/p&gt;
&lt;p&gt;&lt;audio controls src=&#34;https://cf.s3.soulter.top/visualize-music/440hz_16bit_5s.wav&#34; title=&#34;440hz_16bit_5s&#34;&gt;&lt;/audio&gt;&lt;/p&gt;
&lt;figure class=&#34;highlight python&#34;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&#34;gutter&#34;&gt;&lt;pre&gt;&lt;span class=&#34;line&#34;&gt;1&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;2&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;3&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;4&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;5&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&#34;code&#34;&gt;&lt;pre&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;keyword&#34;&gt;import&lt;/span&gt; wave&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;keyword&#34;&gt;with&lt;/span&gt; wave.&lt;span class=&#34;built_in&#34;&gt;open&lt;/span&gt;(&lt;span class=&#34;string&#34;&gt;&amp;#x27;440hz_16bit_5s.wav&amp;#x27;&lt;/span&gt;, &lt;span class=&#34;string&#34;&gt;&amp;#x27;rb&amp;#x27;&lt;/span&gt;) &lt;span class=&#34;keyword&#34;&gt;as&lt;/span&gt; f:&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;    &lt;span class=&#34;built_in&#34;&gt;print&lt;/span&gt;(f.getparams())&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;    &lt;span class=&#34;built_in&#34;&gt;print&lt;/span&gt;(f.readframes(&lt;span class=&#34;number&#34;&gt;20&lt;/span&gt;))&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/figure&gt;
&lt;p&gt;输出如下内容：&lt;/p&gt;
&lt;figure class=&#34;highlight plaintext&#34;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&#34;gutter&#34;&gt;&lt;pre&gt;&lt;span class=&#34;line&#34;&gt;1&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;2&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&#34;code&#34;&gt;&lt;pre&gt;&lt;span class=&#34;line&#34;&gt;_wave_params(nchannels=1, sampwidth=2, framerate=44100, nframes=220500, comptype=&amp;#x27;NONE&amp;#x27;, compname=&amp;#x27;not compressed&amp;#x27;)&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;b&amp;#x27;\x00\x00\x8d\x05\x13\x0b\x8e\x10\xf9\x15M\x1b\x87 \xa0%\x93*Z/\xf33W8\x82&amp;lt;p@\x1eD\x88G\xa9J\x7fM\x07P?R&amp;#x27;&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/figure&gt;
&lt;p&gt;这段音频有一个声道，每个采样点占 2 byte，采样率为 44100，总共有 220500 个采样帧。220500 / 44100 = 5 秒。&lt;/p&gt;
&lt;p&gt;后面读出的采样帧字符串是一段十六进制转义序列，我们可以使用 &lt;code&gt;struct&lt;/code&gt; 模块来解析：&lt;/p&gt;
&lt;figure class=&#34;highlight python&#34;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&#34;gutter&#34;&gt;&lt;pre&gt;&lt;span class=&#34;line&#34;&gt;1&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;2&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;3&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;4&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;5&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;6&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;7&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;8&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;9&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;10&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;11&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&#34;code&#34;&gt;&lt;pre&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;keyword&#34;&gt;import&lt;/span&gt; wave&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;keyword&#34;&gt;import&lt;/span&gt; struct&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;keyword&#34;&gt;def&lt;/span&gt; &lt;span class=&#34;title function_&#34;&gt;read_wave&lt;/span&gt;(&lt;span class=&#34;params&#34;&gt;filename&lt;/span&gt;):&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;    &lt;span class=&#34;keyword&#34;&gt;with&lt;/span&gt; wave.&lt;span class=&#34;built_in&#34;&gt;open&lt;/span&gt;(filename, &lt;span class=&#34;string&#34;&gt;&amp;#x27;rb&amp;#x27;&lt;/span&gt;) &lt;span class=&#34;keyword&#34;&gt;as&lt;/span&gt; f:&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;        nframes = f.getnframes()&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;        frames = f.readframes(nframes)&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;        &lt;span class=&#34;keyword&#34;&gt;return&lt;/span&gt; struct.unpack(&lt;span class=&#34;string&#34;&gt;f&amp;#x27;&amp;lt;&lt;span class=&#34;subst&#34;&gt;&amp;#123;nframes&amp;#125;&lt;/span&gt;h&amp;#x27;&lt;/span&gt;, frames)&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;data = read_wave(&lt;span class=&#34;string&#34;&gt;&amp;#x27;440hz_16bit_5s.wav&amp;#x27;&lt;/span&gt;)&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;built_in&#34;&gt;print&lt;/span&gt;(data[:&lt;span class=&#34;number&#34;&gt;10&lt;/span&gt;])&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/figure&gt;
&lt;p&gt;在这里我们解析了前 10 个采样帧，&lt;code&gt;h&lt;/code&gt; 表示短整型，&lt;code&gt;&amp;lt;&lt;/code&gt; 表示小端序。WAVE 文件采用小端序来存储数据。&lt;/p&gt;
&lt;p&gt;输出如下：&lt;/p&gt;
&lt;figure class=&#34;highlight plaintext&#34;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&#34;gutter&#34;&gt;&lt;pre&gt;&lt;span class=&#34;line&#34;&gt;1&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&#34;code&#34;&gt;&lt;pre&gt;&lt;span class=&#34;line&#34;&gt;(0, 1421, 2835, 4238, 5625, 6989, 8327, 9632, 10899, 12122)&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/figure&gt;
&lt;p&gt;我们发现这一段数字是逐渐增加的，很符合波形的特点。截取前 0.05 秒的波形，然后根据时间绘制波形图：&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://cf.s3.soulter.top/visualize-music/QQ_1725975340572.png&#34; alt=&#34;&#34; /&gt;&lt;/p&gt;
&lt;p&gt;可以发现这是一个正弦波。横轴为时间，纵轴为振幅。由于这段音频的采样宽度为 16 位，所以振幅的范围是 &lt;span class=&#34;katex&#34;&gt;&lt;span class=&#34;katex-mathml&#34;&gt;&lt;math xmlns=&#34;http://www.w3.org/1998/Math/MathML&#34;&gt;&lt;semantics&gt;&lt;mrow&gt;&lt;mo stretchy=&#34;false&#34;&gt;[&lt;/mo&gt;&lt;mo&gt;−&lt;/mo&gt;&lt;msup&gt;&lt;mn&gt;2&lt;/mn&gt;&lt;mn&gt;15&lt;/mn&gt;&lt;/msup&gt;&lt;mo separator=&#34;true&#34;&gt;,&lt;/mo&gt;&lt;msup&gt;&lt;mn&gt;2&lt;/mn&gt;&lt;mn&gt;15&lt;/mn&gt;&lt;/msup&gt;&lt;mo&gt;−&lt;/mo&gt;&lt;mn&gt;1&lt;/mn&gt;&lt;mo stretchy=&#34;false&#34;&gt;]&lt;/mo&gt;&lt;/mrow&gt;&lt;annotation encoding=&#34;application/x-tex&#34;&gt;[-2^{15}, 2^{15}-1]&lt;/annotation&gt;&lt;/semantics&gt;&lt;/math&gt;&lt;/span&gt;&lt;span class=&#34;katex-html&#34; aria-hidden=&#34;true&#34;&gt;&lt;span class=&#34;base&#34;&gt;&lt;span class=&#34;strut&#34; style=&#34;height:1.064108em;vertical-align:-0.25em;&#34;&gt;&lt;/span&gt;&lt;span class=&#34;mopen&#34;&gt;[&lt;/span&gt;&lt;span class=&#34;mord&#34;&gt;−&lt;/span&gt;&lt;span class=&#34;mord&#34;&gt;&lt;span class=&#34;mord&#34;&gt;2&lt;/span&gt;&lt;span class=&#34;msupsub&#34;&gt;&lt;span class=&#34;vlist-t&#34;&gt;&lt;span class=&#34;vlist-r&#34;&gt;&lt;span class=&#34;vlist&#34; style=&#34;height:0.8141079999999999em;&#34;&gt;&lt;span style=&#34;top:-3.063em;margin-right:0.05em;&#34;&gt;&lt;span class=&#34;pstrut&#34; style=&#34;height:2.7em;&#34;&gt;&lt;/span&gt;&lt;span class=&#34;sizing reset-size6 size3 mtight&#34;&gt;&lt;span class=&#34;mord mtight&#34;&gt;&lt;span class=&#34;mord mtight&#34;&gt;1&lt;/span&gt;&lt;span class=&#34;mord mtight&#34;&gt;5&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;mpunct&#34;&gt;,&lt;/span&gt;&lt;span class=&#34;mspace&#34; style=&#34;margin-right:0.16666666666666666em;&#34;&gt;&lt;/span&gt;&lt;span class=&#34;mord&#34;&gt;&lt;span class=&#34;mord&#34;&gt;2&lt;/span&gt;&lt;span class=&#34;msupsub&#34;&gt;&lt;span class=&#34;vlist-t&#34;&gt;&lt;span class=&#34;vlist-r&#34;&gt;&lt;span class=&#34;vlist&#34; style=&#34;height:0.8141079999999999em;&#34;&gt;&lt;span style=&#34;top:-3.063em;margin-right:0.05em;&#34;&gt;&lt;span class=&#34;pstrut&#34; style=&#34;height:2.7em;&#34;&gt;&lt;/span&gt;&lt;span class=&#34;sizing reset-size6 size3 mtight&#34;&gt;&lt;span class=&#34;mord mtight&#34;&gt;&lt;span class=&#34;mord mtight&#34;&gt;1&lt;/span&gt;&lt;span class=&#34;mord mtight&#34;&gt;5&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;mspace&#34; style=&#34;margin-right:0.2222222222222222em;&#34;&gt;&lt;/span&gt;&lt;span class=&#34;mbin&#34;&gt;−&lt;/span&gt;&lt;span class=&#34;mspace&#34; style=&#34;margin-right:0.2222222222222222em;&#34;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;base&#34;&gt;&lt;span class=&#34;strut&#34; style=&#34;height:1em;vertical-align:-0.25em;&#34;&gt;&lt;/span&gt;&lt;span class=&#34;mord&#34;&gt;1&lt;/span&gt;&lt;span class=&#34;mclose&#34;&gt;]&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;，即 &lt;span class=&#34;katex&#34;&gt;&lt;span class=&#34;katex-mathml&#34;&gt;&lt;math xmlns=&#34;http://www.w3.org/1998/Math/MathML&#34;&gt;&lt;semantics&gt;&lt;mrow&gt;&lt;mo stretchy=&#34;false&#34;&gt;[&lt;/mo&gt;&lt;mo&gt;−&lt;/mo&gt;&lt;mn&gt;32768&lt;/mn&gt;&lt;mo separator=&#34;true&#34;&gt;,&lt;/mo&gt;&lt;mn&gt;32767&lt;/mn&gt;&lt;mo stretchy=&#34;false&#34;&gt;]&lt;/mo&gt;&lt;/mrow&gt;&lt;annotation encoding=&#34;application/x-tex&#34;&gt;[-32768, 32767]&lt;/annotation&gt;&lt;/semantics&gt;&lt;/math&gt;&lt;/span&gt;&lt;span class=&#34;katex-html&#34; aria-hidden=&#34;true&#34;&gt;&lt;span class=&#34;base&#34;&gt;&lt;span class=&#34;strut&#34; style=&#34;height:1em;vertical-align:-0.25em;&#34;&gt;&lt;/span&gt;&lt;span class=&#34;mopen&#34;&gt;[&lt;/span&gt;&lt;span class=&#34;mord&#34;&gt;−&lt;/span&gt;&lt;span class=&#34;mord&#34;&gt;3&lt;/span&gt;&lt;span class=&#34;mord&#34;&gt;2&lt;/span&gt;&lt;span class=&#34;mord&#34;&gt;7&lt;/span&gt;&lt;span class=&#34;mord&#34;&gt;6&lt;/span&gt;&lt;span class=&#34;mord&#34;&gt;8&lt;/span&gt;&lt;span class=&#34;mpunct&#34;&gt;,&lt;/span&gt;&lt;span class=&#34;mspace&#34; style=&#34;margin-right:0.16666666666666666em;&#34;&gt;&lt;/span&gt;&lt;span class=&#34;mord&#34;&gt;3&lt;/span&gt;&lt;span class=&#34;mord&#34;&gt;2&lt;/span&gt;&lt;span class=&#34;mord&#34;&gt;7&lt;/span&gt;&lt;span class=&#34;mord&#34;&gt;6&lt;/span&gt;&lt;span class=&#34;mord&#34;&gt;7&lt;/span&gt;&lt;span class=&#34;mclose&#34;&gt;]&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;。&lt;/p&gt;
&lt;p&gt;到目前为止，我们已经成功地将音频文件解析为了波形数据，并且成功可视化出了声波振幅随时间变化的波形图。不过，我们想象中的 440Hz 的“可视化图像”应该是那种“只有一个峰”的频谱图，和一开始提到的 &lt;code&gt;ncmpcpp&lt;/code&gt; 的效果是一样的。那如何将这种数据转化成频谱图呢？这里，我们需要用到傅立叶变换。&lt;/p&gt;
&lt;h2 id=&#34;快速傅立叶变换fft&#34;&gt;&lt;a class=&#34;markdownIt-Anchor&#34; href=&#34;#快速傅立叶变换fft&#34;&gt;&lt;/a&gt; 快速傅立叶变换（FFT）&lt;/h2&gt;
&lt;p&gt;傅立叶变换是一种将信号从时域转换到频域的方法，它可以将一个信号分解为一系列不同频率的正弦波。快速傅立叶变换（FFT）是一种计算傅立叶变换的高效算法。下面是快速傅立叶变换的 Python 实现：&lt;/p&gt;
&lt;figure class=&#34;highlight python&#34;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&#34;gutter&#34;&gt;&lt;pre&gt;&lt;span class=&#34;line&#34;&gt;1&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;2&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;3&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;4&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;5&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;6&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;7&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;8&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;9&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&#34;code&#34;&gt;&lt;pre&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;keyword&#34;&gt;def&lt;/span&gt; &lt;span class=&#34;title function_&#34;&gt;fft&lt;/span&gt;(&lt;span class=&#34;params&#34;&gt;x&lt;/span&gt;):&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;    N = &lt;span class=&#34;built_in&#34;&gt;len&lt;/span&gt;(x)&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;    &lt;span class=&#34;keyword&#34;&gt;if&lt;/span&gt; N &amp;lt;= &lt;span class=&#34;number&#34;&gt;1&lt;/span&gt;:&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;        &lt;span class=&#34;keyword&#34;&gt;return&lt;/span&gt; x&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;    even = fft(x[&lt;span class=&#34;number&#34;&gt;0&lt;/span&gt;::&lt;span class=&#34;number&#34;&gt;2&lt;/span&gt;])&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;    odd = fft(x[&lt;span class=&#34;number&#34;&gt;1&lt;/span&gt;::&lt;span class=&#34;number&#34;&gt;2&lt;/span&gt;])&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;    T = [cmath.exp(-&lt;span class=&#34;number&#34;&gt;2j&lt;/span&gt; * math.pi * k / N) * odd[k] &lt;span class=&#34;keyword&#34;&gt;for&lt;/span&gt; k &lt;span class=&#34;keyword&#34;&gt;in&lt;/span&gt; &lt;span class=&#34;built_in&#34;&gt;range&lt;/span&gt;(N // &lt;span class=&#34;number&#34;&gt;2&lt;/span&gt;)]&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;    &lt;span class=&#34;keyword&#34;&gt;return&lt;/span&gt; [even[k] + T[k] &lt;span class=&#34;keyword&#34;&gt;for&lt;/span&gt; k &lt;span class=&#34;keyword&#34;&gt;in&lt;/span&gt; &lt;span class=&#34;built_in&#34;&gt;range&lt;/span&gt;(N // &lt;span class=&#34;number&#34;&gt;2&lt;/span&gt;)] + \&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;           [even[k] - T[k] &lt;span class=&#34;keyword&#34;&gt;for&lt;/span&gt; k &lt;span class=&#34;keyword&#34;&gt;in&lt;/span&gt; &lt;span class=&#34;built_in&#34;&gt;range&lt;/span&gt;(N // &lt;span class=&#34;number&#34;&gt;2&lt;/span&gt;)]&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/figure&gt;
&lt;p&gt;它的输入是一系列采样点的振幅值，输出是一系列复数，表示不同频率的正弦波的振幅和相位。让我们把上面的 440Hz 的波形数据输入到这个函数中：&lt;/p&gt;
&lt;figure class=&#34;highlight py&#34;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&#34;gutter&#34;&gt;&lt;pre&gt;&lt;span class=&#34;line&#34;&gt;1&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;2&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;3&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;4&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;5&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;6&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;7&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;8&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&#34;code&#34;&gt;&lt;pre&gt;&lt;span class=&#34;line&#34;&gt;[&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;    (&lt;span class=&#34;number&#34;&gt;503643&lt;/span&gt;+&lt;span class=&#34;number&#34;&gt;0j&lt;/span&gt;),&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;    (-&lt;span class=&#34;number&#34;&gt;68801.16613674666&lt;/span&gt;+&lt;span class=&#34;number&#34;&gt;116234.4683635989j&lt;/span&gt;),&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;    (-&lt;span class=&#34;number&#34;&gt;23890.51738479154&lt;/span&gt;+&lt;span class=&#34;number&#34;&gt;53037.36938852984j&lt;/span&gt;),&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;    (-&lt;span class=&#34;number&#34;&gt;16335.061327176663&lt;/span&gt;+&lt;span class=&#34;number&#34;&gt;34280.395464007364j&lt;/span&gt;),&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;    (-&lt;span class=&#34;number&#34;&gt;13741.719659027507&lt;/span&gt;+&lt;span class=&#34;number&#34;&gt;24979.846914594244j&lt;/span&gt;),&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;    ...&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;]&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/figure&gt;
&lt;h2 id=&#34;可视化频谱&#34;&gt;&lt;a class=&#34;markdownIt-Anchor&#34; href=&#34;#可视化频谱&#34;&gt;&lt;/a&gt; 可视化频谱&lt;/h2&gt;
&lt;p&gt;我们将原始波形数据应用于 FFT，然后将得到的复数值转换为其模长，并进行归一化：&lt;/p&gt;
&lt;figure class=&#34;highlight python&#34;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&#34;gutter&#34;&gt;&lt;pre&gt;&lt;span class=&#34;line&#34;&gt;1&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;2&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;3&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;4&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;5&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;6&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;7&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&#34;code&#34;&gt;&lt;pre&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;keyword&#34;&gt;def&lt;/span&gt; &lt;span class=&#34;title function_&#34;&gt;scale_spectrum&lt;/span&gt;(&lt;span class=&#34;params&#34;&gt;spectrum_complex, max_value: &lt;span class=&#34;built_in&#34;&gt;int&lt;/span&gt; = &lt;span class=&#34;number&#34;&gt;10&lt;/span&gt;&lt;/span&gt;):&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;    &lt;span class=&#34;string&#34;&gt;&amp;#x27;&amp;#x27;&amp;#x27;将频谱复数转换为振幅，并归一化&amp;#x27;&amp;#x27;&amp;#x27;&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;    spectrum = [&lt;span class=&#34;built_in&#34;&gt;abs&lt;/span&gt;(freq) &lt;span class=&#34;keyword&#34;&gt;for&lt;/span&gt; freq &lt;span class=&#34;keyword&#34;&gt;in&lt;/span&gt; spectrum_complex]&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;    max_spectrum = &lt;span class=&#34;built_in&#34;&gt;max&lt;/span&gt;(&lt;span class=&#34;built_in&#34;&gt;max&lt;/span&gt;(spectrum), &lt;span class=&#34;number&#34;&gt;1&lt;/span&gt;) &lt;span class=&#34;comment&#34;&gt;# 防止除以 0 &lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;    &lt;span class=&#34;keyword&#34;&gt;return&lt;/span&gt; [&lt;span class=&#34;built_in&#34;&gt;int&lt;/span&gt;(max_value * freq / max_spectrum) &lt;span class=&#34;keyword&#34;&gt;for&lt;/span&gt; freq &lt;span class=&#34;keyword&#34;&gt;in&lt;/span&gt; spectrum]&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;spectrum = scale_spectrum(fft(data[:&lt;span class=&#34;number&#34;&gt;512&lt;/span&gt;]))[:&lt;span class=&#34;number&#34;&gt;20&lt;/span&gt;]&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/figure&gt;
&lt;p&gt;输出：&lt;/p&gt;
&lt;figure class=&#34;highlight plaintext&#34;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&#34;gutter&#34;&gt;&lt;pre&gt;&lt;span class=&#34;line&#34;&gt;1&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&#34;code&#34;&gt;&lt;pre&gt;&lt;span class=&#34;line&#34;&gt;[0, 0, 0, 0, 0, 9, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/figure&gt;
&lt;p&gt;是不是有感觉了？！我们采用了前 512 个采样帧的频谱数据，并将它们缩放到 0-10 的范围内，然后取前 20 个频率的振幅值。我们可以使用 &lt;code&gt;curses&lt;/code&gt; 模块来绘制频谱图：&lt;/p&gt;
&lt;figure class=&#34;highlight python&#34;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&#34;gutter&#34;&gt;&lt;pre&gt;&lt;span class=&#34;line&#34;&gt;1&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;2&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;3&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;4&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;5&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;6&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;7&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;8&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;9&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;10&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;11&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;12&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;13&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;14&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;15&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;16&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;17&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;18&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;19&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;20&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;21&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;22&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;23&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&#34;code&#34;&gt;&lt;pre&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;keyword&#34;&gt;import&lt;/span&gt; curses, time&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;keyword&#34;&gt;def&lt;/span&gt; &lt;span class=&#34;title function_&#34;&gt;draw_spectrum&lt;/span&gt;(&lt;span class=&#34;params&#34;&gt;stdscr, spectrum&lt;/span&gt;):&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;    stdscr.clear()&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;    &lt;span class=&#34;keyword&#34;&gt;for&lt;/span&gt; i, freq &lt;span class=&#34;keyword&#34;&gt;in&lt;/span&gt; &lt;span class=&#34;built_in&#34;&gt;enumerate&lt;/span&gt;(spectrum):&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;        stdscr.addstr(i, &lt;span class=&#34;number&#34;&gt;0&lt;/span&gt;, &lt;span class=&#34;string&#34;&gt;f&amp;#x27;&lt;span class=&#34;subst&#34;&gt;&amp;#123;i&amp;#125;&lt;/span&gt;: &lt;span class=&#34;subst&#34;&gt;&amp;#123;&lt;span class=&#34;string&#34;&gt;&amp;quot;#&amp;quot;&lt;/span&gt; * freq&amp;#125;&lt;/span&gt;&amp;#x27;&lt;/span&gt;)&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;    stdscr.refresh()&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;comment&#34;&gt;# （这个 main 函数不是最终版本！）&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;keyword&#34;&gt;def&lt;/span&gt; &lt;span class=&#34;title function_&#34;&gt;main&lt;/span&gt;(&lt;span class=&#34;params&#34;&gt;stdscr&lt;/span&gt;):&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;    file_name = &lt;span class=&#34;string&#34;&gt;&amp;#x27;440hz_16bit_5s.wav&amp;#x27;&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;    data, frame_rate = read_wav_file(file_name)&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;    &lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;    frame_size = &lt;span class=&#34;number&#34;&gt;2048&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;    hop_size = &lt;span class=&#34;number&#34;&gt;512&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;    &lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;    &lt;span class=&#34;keyword&#34;&gt;for&lt;/span&gt; i &lt;span class=&#34;keyword&#34;&gt;in&lt;/span&gt; &lt;span class=&#34;built_in&#34;&gt;range&lt;/span&gt;(&lt;span class=&#34;number&#34;&gt;0&lt;/span&gt;, &lt;span class=&#34;built_in&#34;&gt;len&lt;/span&gt;(data) - frame_size, hop_size):&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;        spectrum = scale_spectrum(fft(data[i:i + frame_size]))&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;        draw_spectrum(stdscr, spectrum[:&lt;span class=&#34;number&#34;&gt;40&lt;/span&gt;])&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;        time.sleep(&lt;span class=&#34;number&#34;&gt;0.05&lt;/span&gt;)&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;keyword&#34;&gt;if&lt;/span&gt; __name__ == &lt;span class=&#34;string&#34;&gt;&amp;quot;__main__&amp;quot;&lt;/span&gt;:&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;    curses.wrapper(main)&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/figure&gt;
&lt;p&gt;这里，我们对音频数据进行了分帧处理，每帧 2048 个采样帧，帧移 512 个采样帧。然后我们计算每一帧的频谱，并绘制频谱图。我们可以看到频谱图随时间变化的效果。&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://cf.s3.soulter.top/visualize-music/QQ_1726126824224.png&#34; alt=&#34;&#34; /&gt;&lt;/p&gt;
&lt;p&gt;现在还有一个小问题，就是这个频谱图的播放时间(约 8s) 远远高于了实际音乐的播放时间 5s。这是由于我们在每次渲染时，将睡眠时间固定为了 0.05s。没有考虑实际音乐的播放速度和一次循环所用到的采样帧占总采样帧的比例。并且，计算 FFT 和渲染频谱图也是会消耗时间的。&lt;/p&gt;
&lt;p&gt;这也很好解决，为了使可视化跟上音乐的播放速度，我们需要知道音乐的总长度、每一个可视化帧应该的播放时间、可视化帧运算和渲染花费的时间，然后动态调整睡眠时间：&lt;/p&gt;
&lt;figure class=&#34;highlight py&#34;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&#34;gutter&#34;&gt;&lt;pre&gt;&lt;span class=&#34;line&#34;&gt;1&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;2&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;3&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;4&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;5&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;6&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;7&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;8&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;9&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;10&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;11&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;12&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;13&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;14&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;15&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;16&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;17&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&#34;code&#34;&gt;&lt;pre&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;keyword&#34;&gt;def&lt;/span&gt; &lt;span class=&#34;title function_&#34;&gt;main&lt;/span&gt;(&lt;span class=&#34;params&#34;&gt;stdscr&lt;/span&gt;):&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;    file_name = &lt;span class=&#34;string&#34;&gt;&amp;#x27;440hz_16bit_5s.wav&amp;#x27;&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;    data, frame_rate = read_wav_file(file_name)&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;    &lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;    frame_size = &lt;span class=&#34;number&#34;&gt;2048&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;    hop_size = &lt;span class=&#34;number&#34;&gt;1024&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;    &lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;    frame_duration = hop_size / frame_rate  &lt;span class=&#34;comment&#34;&gt;# 每帧的实际播放时间&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;    total_duration = &lt;span class=&#34;built_in&#34;&gt;len&lt;/span&gt;(data) / frame_rate  &lt;span class=&#34;comment&#34;&gt;# 音频总时长&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;    &lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;    &lt;span class=&#34;keyword&#34;&gt;for&lt;/span&gt; i &lt;span class=&#34;keyword&#34;&gt;in&lt;/span&gt; &lt;span class=&#34;built_in&#34;&gt;range&lt;/span&gt;(&lt;span class=&#34;number&#34;&gt;0&lt;/span&gt;, &lt;span class=&#34;built_in&#34;&gt;len&lt;/span&gt;(data) - frame_size, hop_size):&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;        start_time = time.time()&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;        spectrum = scale_spectrum(fft(data[i:i + frame_size]))&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;        draw_spectrum(stdscr, spectrum[:&lt;span class=&#34;number&#34;&gt;40&lt;/span&gt;], i / frame_rate, total_duration)&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;        elapsed_time = time.time()&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;        &lt;span class=&#34;comment&#34;&gt;# 将可视化帧播放时间剪去运算渲染消耗的时间，来达到自适应。&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;        time.sleep(&lt;span class=&#34;built_in&#34;&gt;max&lt;/span&gt;(&lt;span class=&#34;number&#34;&gt;0&lt;/span&gt;, frame_duration - (elapsed_time - start_time)))&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/figure&gt;
&lt;p&gt;&lt;img src=&#34;https://cf.s3.soulter.top/visualize-music/QQ_1726128923283.png&#34; alt=&#34;&#34; /&gt;&lt;/p&gt;
&lt;p&gt;接下来，我们来优化这个播放器。比如：不同振幅采用不同的颜色、把频谱图横过来、平滑可视化帧之间的振幅。让我们来进行优化&lt;/p&gt;
&lt;p&gt;按照缩放振幅最大值的 0.2、0.4、0.6 倍分割成 4 段，分别采用淡蓝、白、绿、黄颜色区分。&lt;/p&gt;
&lt;p&gt;初始化 curses 的颜色对：&lt;/p&gt;
&lt;figure class=&#34;highlight py&#34;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&#34;gutter&#34;&gt;&lt;pre&gt;&lt;span class=&#34;line&#34;&gt;1&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;2&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;3&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;4&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;5&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;6&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;7&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;8&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;9&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;10&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;11&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;12&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;13&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;14&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;15&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;16&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&#34;code&#34;&gt;&lt;pre&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;keyword&#34;&gt;def&lt;/span&gt; &lt;span class=&#34;title function_&#34;&gt;init_colors&lt;/span&gt;():&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;    &lt;span class=&#34;keyword&#34;&gt;global&lt;/span&gt; colors&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;    curses.start_color()&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;    curses.init_pair(&lt;span class=&#34;number&#34;&gt;1&lt;/span&gt;, curses.COLOR_CYAN, &lt;span class=&#34;number&#34;&gt;1&lt;/span&gt;)&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;    curses.init_pair(&lt;span class=&#34;number&#34;&gt;2&lt;/span&gt;, curses.COLOR_WHITE, &lt;span class=&#34;number&#34;&gt;1&lt;/span&gt;)&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;    curses.init_pair(&lt;span class=&#34;number&#34;&gt;3&lt;/span&gt;, curses.COLOR_GREEN, &lt;span class=&#34;number&#34;&gt;1&lt;/span&gt;)&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;    curses.init_pair(&lt;span class=&#34;number&#34;&gt;4&lt;/span&gt;, curses.COLOR_YELLOW, &lt;span class=&#34;number&#34;&gt;1&lt;/span&gt;)&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;    &lt;span class=&#34;keyword&#34;&gt;for&lt;/span&gt; i &lt;span class=&#34;keyword&#34;&gt;in&lt;/span&gt; &lt;span class=&#34;built_in&#34;&gt;range&lt;/span&gt;(MAX_VALUE+&lt;span class=&#34;number&#34;&gt;1&lt;/span&gt;):&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;        &lt;span class=&#34;keyword&#34;&gt;if&lt;/span&gt; i / MAX_VALUE &amp;lt;= &lt;span class=&#34;number&#34;&gt;0.2&lt;/span&gt;:&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;            colors.append(&lt;span class=&#34;number&#34;&gt;1&lt;/span&gt;)&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;        &lt;span class=&#34;keyword&#34;&gt;elif&lt;/span&gt; i / MAX_VALUE &amp;lt;= &lt;span class=&#34;number&#34;&gt;0.4&lt;/span&gt;:&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;            colors.append(&lt;span class=&#34;number&#34;&gt;2&lt;/span&gt;)&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;        &lt;span class=&#34;keyword&#34;&gt;elif&lt;/span&gt; i / MAX_VALUE &amp;lt;= &lt;span class=&#34;number&#34;&gt;0.6&lt;/span&gt;:&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;            colors.append(&lt;span class=&#34;number&#34;&gt;3&lt;/span&gt;)&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;        &lt;span class=&#34;keyword&#34;&gt;else&lt;/span&gt;:&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;            colors.append(&lt;span class=&#34;number&#34;&gt;4&lt;/span&gt;)&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/figure&gt;
&lt;p&gt;渲染：&lt;/p&gt;
&lt;figure class=&#34;highlight py&#34;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&#34;gutter&#34;&gt;&lt;pre&gt;&lt;span class=&#34;line&#34;&gt;1&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;2&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;3&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;4&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;5&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;6&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;7&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;8&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;9&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;10&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;11&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;12&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;13&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;14&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;15&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&#34;code&#34;&gt;&lt;pre&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;keyword&#34;&gt;def&lt;/span&gt; &lt;span class=&#34;title function_&#34;&gt;draw_spectrum&lt;/span&gt;(&lt;span class=&#34;params&#34;&gt;stdscr, spectrum, curr, total&lt;/span&gt;):&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;    &lt;span class=&#34;keyword&#34;&gt;global&lt;/span&gt; colors&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;    stdscr.clear()&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;    max_y, max_x = stdscr.getmaxyx()&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;    spectrum = spectrum[:max_x]&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;    half_y = max_y // &lt;span class=&#34;number&#34;&gt;2&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;    &lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;    &lt;span class=&#34;comment&#34;&gt;# 水平展示频谱&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;    &lt;span class=&#34;keyword&#34;&gt;for&lt;/span&gt; i, freq &lt;span class=&#34;keyword&#34;&gt;in&lt;/span&gt; &lt;span class=&#34;built_in&#34;&gt;enumerate&lt;/span&gt;(spectrum):&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;        &lt;span class=&#34;keyword&#34;&gt;for&lt;/span&gt; j &lt;span class=&#34;keyword&#34;&gt;in&lt;/span&gt; &lt;span class=&#34;built_in&#34;&gt;range&lt;/span&gt;(freq):&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;            stdscr.addstr(half_y - j, i, &lt;span class=&#34;string&#34;&gt;&amp;quot;█&amp;quot;&lt;/span&gt;, curses.color_pair(colors[j]))&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;            stdscr.addstr(half_y + j, i, &lt;span class=&#34;string&#34;&gt;&amp;quot;█&amp;quot;&lt;/span&gt;, curses.color_pair(colors[j]))&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;    &lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;    stdscr.addstr(max_y - &lt;span class=&#34;number&#34;&gt;1&lt;/span&gt;, &lt;span class=&#34;number&#34;&gt;0&lt;/span&gt;, &lt;span class=&#34;string&#34;&gt;f&amp;#x27;&lt;span class=&#34;subst&#34;&gt;&amp;#123;curr:&lt;span class=&#34;number&#34;&gt;.2&lt;/span&gt;f&amp;#125;&lt;/span&gt; / &lt;span class=&#34;subst&#34;&gt;&amp;#123;total:&lt;span class=&#34;number&#34;&gt;.2&lt;/span&gt;f&amp;#125;&lt;/span&gt; s&amp;#x27;&lt;/span&gt;, curses.color_pair(&lt;span class=&#34;number&#34;&gt;0&lt;/span&gt;))&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;    stdscr.refresh()&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/figure&gt;
&lt;p&gt;然后，我们采用 EMA（移动指数平均）来实现平滑数据。&lt;/p&gt;
&lt;p&gt;EMA 的计算公式为：&lt;/p&gt;
&lt;p class=&#39;katex-block&#39;&gt;&lt;span class=&#34;katex-display&#34;&gt;&lt;span class=&#34;katex&#34;&gt;&lt;span class=&#34;katex-mathml&#34;&gt;&lt;math xmlns=&#34;http://www.w3.org/1998/Math/MathML&#34; display=&#34;block&#34;&gt;&lt;semantics&gt;&lt;mrow&gt;&lt;mi&gt;E&lt;/mi&gt;&lt;mi&gt;M&lt;/mi&gt;&lt;mi&gt;A&lt;/mi&gt;&lt;mo stretchy=&#34;false&#34;&gt;(&lt;/mo&gt;&lt;mi&gt;t&lt;/mi&gt;&lt;mo stretchy=&#34;false&#34;&gt;)&lt;/mo&gt;&lt;mo&gt;=&lt;/mo&gt;&lt;mo stretchy=&#34;false&#34;&gt;(&lt;/mo&gt;&lt;mn&gt;1&lt;/mn&gt;&lt;mo&gt;−&lt;/mo&gt;&lt;mi&gt;α&lt;/mi&gt;&lt;mo stretchy=&#34;false&#34;&gt;)&lt;/mo&gt;&lt;mo&gt;⋅&lt;/mo&gt;&lt;mi&gt;E&lt;/mi&gt;&lt;mi&gt;M&lt;/mi&gt;&lt;mi&gt;A&lt;/mi&gt;&lt;mo stretchy=&#34;false&#34;&gt;(&lt;/mo&gt;&lt;mi&gt;t&lt;/mi&gt;&lt;mo&gt;−&lt;/mo&gt;&lt;mn&gt;1&lt;/mn&gt;&lt;mo stretchy=&#34;false&#34;&gt;)&lt;/mo&gt;&lt;mo&gt;+&lt;/mo&gt;&lt;mi&gt;α&lt;/mi&gt;&lt;mo&gt;⋅&lt;/mo&gt;&lt;mi&gt;x&lt;/mi&gt;&lt;mo stretchy=&#34;false&#34;&gt;(&lt;/mo&gt;&lt;mi&gt;t&lt;/mi&gt;&lt;mo stretchy=&#34;false&#34;&gt;)&lt;/mo&gt;&lt;/mrow&gt;&lt;annotation encoding=&#34;application/x-tex&#34;&gt;EMA(t) = (1 - \alpha) \cdot EMA(t-1) + \alpha \cdot x(t)
&lt;/annotation&gt;&lt;/semantics&gt;&lt;/math&gt;&lt;/span&gt;&lt;span class=&#34;katex-html&#34; aria-hidden=&#34;true&#34;&gt;&lt;span class=&#34;base&#34;&gt;&lt;span class=&#34;strut&#34; style=&#34;height:1em;vertical-align:-0.25em;&#34;&gt;&lt;/span&gt;&lt;span class=&#34;mord mathnormal&#34; style=&#34;margin-right:0.05764em;&#34;&gt;E&lt;/span&gt;&lt;span class=&#34;mord mathnormal&#34; style=&#34;margin-right:0.10903em;&#34;&gt;M&lt;/span&gt;&lt;span class=&#34;mord mathnormal&#34;&gt;A&lt;/span&gt;&lt;span class=&#34;mopen&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;mord mathnormal&#34;&gt;t&lt;/span&gt;&lt;span class=&#34;mclose&#34;&gt;)&lt;/span&gt;&lt;span class=&#34;mspace&#34; style=&#34;margin-right:0.2777777777777778em;&#34;&gt;&lt;/span&gt;&lt;span class=&#34;mrel&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;mspace&#34; style=&#34;margin-right:0.2777777777777778em;&#34;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;base&#34;&gt;&lt;span class=&#34;strut&#34; style=&#34;height:1em;vertical-align:-0.25em;&#34;&gt;&lt;/span&gt;&lt;span class=&#34;mopen&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;mord&#34;&gt;1&lt;/span&gt;&lt;span class=&#34;mspace&#34; style=&#34;margin-right:0.2222222222222222em;&#34;&gt;&lt;/span&gt;&lt;span class=&#34;mbin&#34;&gt;−&lt;/span&gt;&lt;span class=&#34;mspace&#34; style=&#34;margin-right:0.2222222222222222em;&#34;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;base&#34;&gt;&lt;span class=&#34;strut&#34; style=&#34;height:1em;vertical-align:-0.25em;&#34;&gt;&lt;/span&gt;&lt;span class=&#34;mord mathnormal&#34; style=&#34;margin-right:0.0037em;&#34;&gt;α&lt;/span&gt;&lt;span class=&#34;mclose&#34;&gt;)&lt;/span&gt;&lt;span class=&#34;mspace&#34; style=&#34;margin-right:0.2222222222222222em;&#34;&gt;&lt;/span&gt;&lt;span class=&#34;mbin&#34;&gt;⋅&lt;/span&gt;&lt;span class=&#34;mspace&#34; style=&#34;margin-right:0.2222222222222222em;&#34;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;base&#34;&gt;&lt;span class=&#34;strut&#34; style=&#34;height:1em;vertical-align:-0.25em;&#34;&gt;&lt;/span&gt;&lt;span class=&#34;mord mathnormal&#34; style=&#34;margin-right:0.05764em;&#34;&gt;E&lt;/span&gt;&lt;span class=&#34;mord mathnormal&#34; style=&#34;margin-right:0.10903em;&#34;&gt;M&lt;/span&gt;&lt;span class=&#34;mord mathnormal&#34;&gt;A&lt;/span&gt;&lt;span class=&#34;mopen&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;mord mathnormal&#34;&gt;t&lt;/span&gt;&lt;span class=&#34;mspace&#34; style=&#34;margin-right:0.2222222222222222em;&#34;&gt;&lt;/span&gt;&lt;span class=&#34;mbin&#34;&gt;−&lt;/span&gt;&lt;span class=&#34;mspace&#34; style=&#34;margin-right:0.2222222222222222em;&#34;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;base&#34;&gt;&lt;span class=&#34;strut&#34; style=&#34;height:1em;vertical-align:-0.25em;&#34;&gt;&lt;/span&gt;&lt;span class=&#34;mord&#34;&gt;1&lt;/span&gt;&lt;span class=&#34;mclose&#34;&gt;)&lt;/span&gt;&lt;span class=&#34;mspace&#34; style=&#34;margin-right:0.2222222222222222em;&#34;&gt;&lt;/span&gt;&lt;span class=&#34;mbin&#34;&gt;+&lt;/span&gt;&lt;span class=&#34;mspace&#34; style=&#34;margin-right:0.2222222222222222em;&#34;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;base&#34;&gt;&lt;span class=&#34;strut&#34; style=&#34;height:0.44445em;vertical-align:0em;&#34;&gt;&lt;/span&gt;&lt;span class=&#34;mord mathnormal&#34; style=&#34;margin-right:0.0037em;&#34;&gt;α&lt;/span&gt;&lt;span class=&#34;mspace&#34; style=&#34;margin-right:0.2222222222222222em;&#34;&gt;&lt;/span&gt;&lt;span class=&#34;mbin&#34;&gt;⋅&lt;/span&gt;&lt;span class=&#34;mspace&#34; style=&#34;margin-right:0.2222222222222222em;&#34;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;base&#34;&gt;&lt;span class=&#34;strut&#34; style=&#34;height:1em;vertical-align:-0.25em;&#34;&gt;&lt;/span&gt;&lt;span class=&#34;mord mathnormal&#34;&gt;x&lt;/span&gt;&lt;span class=&#34;mopen&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;mord mathnormal&#34;&gt;t&lt;/span&gt;&lt;span class=&#34;mclose&#34;&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;其中 &lt;span class=&#34;katex&#34;&gt;&lt;span class=&#34;katex-mathml&#34;&gt;&lt;math xmlns=&#34;http://www.w3.org/1998/Math/MathML&#34;&gt;&lt;semantics&gt;&lt;mrow&gt;&lt;mi&gt;α&lt;/mi&gt;&lt;/mrow&gt;&lt;annotation encoding=&#34;application/x-tex&#34;&gt;\alpha&lt;/annotation&gt;&lt;/semantics&gt;&lt;/math&gt;&lt;/span&gt;&lt;span class=&#34;katex-html&#34; aria-hidden=&#34;true&#34;&gt;&lt;span class=&#34;base&#34;&gt;&lt;span class=&#34;strut&#34; style=&#34;height:0.43056em;vertical-align:0em;&#34;&gt;&lt;/span&gt;&lt;span class=&#34;mord mathnormal&#34; style=&#34;margin-right:0.0037em;&#34;&gt;α&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt; 为平滑系数，&lt;span class=&#34;katex&#34;&gt;&lt;span class=&#34;katex-mathml&#34;&gt;&lt;math xmlns=&#34;http://www.w3.org/1998/Math/MathML&#34;&gt;&lt;semantics&gt;&lt;mrow&gt;&lt;mi&gt;x&lt;/mi&gt;&lt;mo stretchy=&#34;false&#34;&gt;(&lt;/mo&gt;&lt;mi&gt;t&lt;/mi&gt;&lt;mo stretchy=&#34;false&#34;&gt;)&lt;/mo&gt;&lt;/mrow&gt;&lt;annotation encoding=&#34;application/x-tex&#34;&gt;x(t)&lt;/annotation&gt;&lt;/semantics&gt;&lt;/math&gt;&lt;/span&gt;&lt;span class=&#34;katex-html&#34; aria-hidden=&#34;true&#34;&gt;&lt;span class=&#34;base&#34;&gt;&lt;span class=&#34;strut&#34; style=&#34;height:1em;vertical-align:-0.25em;&#34;&gt;&lt;/span&gt;&lt;span class=&#34;mord mathnormal&#34;&gt;x&lt;/span&gt;&lt;span class=&#34;mopen&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;mord mathnormal&#34;&gt;t&lt;/span&gt;&lt;span class=&#34;mclose&#34;&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt; 为当前值，&lt;span class=&#34;katex&#34;&gt;&lt;span class=&#34;katex-mathml&#34;&gt;&lt;math xmlns=&#34;http://www.w3.org/1998/Math/MathML&#34;&gt;&lt;semantics&gt;&lt;mrow&gt;&lt;mi&gt;E&lt;/mi&gt;&lt;mi&gt;M&lt;/mi&gt;&lt;mi&gt;A&lt;/mi&gt;&lt;mo stretchy=&#34;false&#34;&gt;(&lt;/mo&gt;&lt;mi&gt;t&lt;/mi&gt;&lt;mo&gt;−&lt;/mo&gt;&lt;mn&gt;1&lt;/mn&gt;&lt;mo stretchy=&#34;false&#34;&gt;)&lt;/mo&gt;&lt;/mrow&gt;&lt;annotation encoding=&#34;application/x-tex&#34;&gt;EMA(t-1)&lt;/annotation&gt;&lt;/semantics&gt;&lt;/math&gt;&lt;/span&gt;&lt;span class=&#34;katex-html&#34; aria-hidden=&#34;true&#34;&gt;&lt;span class=&#34;base&#34;&gt;&lt;span class=&#34;strut&#34; style=&#34;height:1em;vertical-align:-0.25em;&#34;&gt;&lt;/span&gt;&lt;span class=&#34;mord mathnormal&#34; style=&#34;margin-right:0.05764em;&#34;&gt;E&lt;/span&gt;&lt;span class=&#34;mord mathnormal&#34; style=&#34;margin-right:0.10903em;&#34;&gt;M&lt;/span&gt;&lt;span class=&#34;mord mathnormal&#34;&gt;A&lt;/span&gt;&lt;span class=&#34;mopen&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;mord mathnormal&#34;&gt;t&lt;/span&gt;&lt;span class=&#34;mspace&#34; style=&#34;margin-right:0.2222222222222222em;&#34;&gt;&lt;/span&gt;&lt;span class=&#34;mbin&#34;&gt;−&lt;/span&gt;&lt;span class=&#34;mspace&#34; style=&#34;margin-right:0.2222222222222222em;&#34;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;base&#34;&gt;&lt;span class=&#34;strut&#34; style=&#34;height:1em;vertical-align:-0.25em;&#34;&gt;&lt;/span&gt;&lt;span class=&#34;mord&#34;&gt;1&lt;/span&gt;&lt;span class=&#34;mclose&#34;&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt; 为上一次的平滑值。&lt;/p&gt;
&lt;p&gt;我们只需要记录上一次的平滑后的频谱值，然后在每一帧的频谱值上应用 EMA 即可。&lt;/p&gt;
&lt;figure class=&#34;highlight py&#34;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&#34;gutter&#34;&gt;&lt;pre&gt;&lt;span class=&#34;line&#34;&gt;1&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;2&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;3&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;4&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;5&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;6&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;7&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;8&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;9&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;10&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;11&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;12&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;13&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;14&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;15&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;16&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;17&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;18&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;19&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;20&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;21&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;22&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;23&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;24&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;25&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;26&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;27&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;28&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;29&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;30&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;31&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;32&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&#34;code&#34;&gt;&lt;pre&gt;&lt;span class=&#34;line&#34;&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;keyword&#34;&gt;def&lt;/span&gt; &lt;span class=&#34;title function_&#34;&gt;main&lt;/span&gt;(&lt;span class=&#34;params&#34;&gt;stdscr&lt;/span&gt;):&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;    init_colors()&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;    file_name = &lt;span class=&#34;string&#34;&gt;&amp;#x27;haruhikage.wav&amp;#x27;&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;    data, frame_rate = read_wav_file(file_name)&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;    &lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;    frame_size = &lt;span class=&#34;number&#34;&gt;4096&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;    hop_size = &lt;span class=&#34;number&#34;&gt;2048&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;    &lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;    frame_duration = hop_size / frame_rate  &lt;span class=&#34;comment&#34;&gt;# 每帧的实际播放时间&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;    total_duration = &lt;span class=&#34;built_in&#34;&gt;len&lt;/span&gt;(data) / frame_rate  &lt;span class=&#34;comment&#34;&gt;# 音频总时长&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;    &lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;    audio_thread = threading.Thread(target=play_audio, args=(file_name,))&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;    audio_thread.start()&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;    &lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;    previous_spectrum = &lt;span class=&#34;literal&#34;&gt;None&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;    ema_alpha = &lt;span class=&#34;number&#34;&gt;0.5&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;    &lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;    &lt;span class=&#34;keyword&#34;&gt;for&lt;/span&gt; i &lt;span class=&#34;keyword&#34;&gt;in&lt;/span&gt; &lt;span class=&#34;built_in&#34;&gt;range&lt;/span&gt;(&lt;span class=&#34;number&#34;&gt;0&lt;/span&gt;, &lt;span class=&#34;built_in&#34;&gt;len&lt;/span&gt;(data) - frame_size, hop_size):&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;        start_time = time.time()&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;        spectrum = scale_spectrum(fft(data[i:i + frame_size]))&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;        &lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;        &lt;span class=&#34;keyword&#34;&gt;if&lt;/span&gt; previous_spectrum &lt;span class=&#34;keyword&#34;&gt;is&lt;/span&gt; &lt;span class=&#34;keyword&#34;&gt;not&lt;/span&gt; &lt;span class=&#34;literal&#34;&gt;None&lt;/span&gt;:&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;            &lt;span class=&#34;comment&#34;&gt;# 应用 EMA&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;            spectrum = [&lt;span class=&#34;built_in&#34;&gt;int&lt;/span&gt;((&lt;span class=&#34;number&#34;&gt;1&lt;/span&gt; - ema_alpha) * prev + ema_alpha * curr) &lt;span class=&#34;keyword&#34;&gt;for&lt;/span&gt; prev, curr &lt;span class=&#34;keyword&#34;&gt;in&lt;/span&gt; &lt;span class=&#34;built_in&#34;&gt;zip&lt;/span&gt;(previous_spectrum, spectrum)]&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;        &lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;        draw_spectrum(stdscr, spectrum, i / frame_rate, total_duration)&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;        previous_spectrum = spectrum&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;        elapsed_time = time.time()&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;        time.sleep(&lt;span class=&#34;built_in&#34;&gt;max&lt;/span&gt;(&lt;span class=&#34;number&#34;&gt;0&lt;/span&gt;, frame_duration - (elapsed_time - start_time)))&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;        &lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;    audio_thread.join()&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/figure&gt;
&lt;p&gt;&lt;img src=&#34;https://cf.s3.soulter.top/visualize-music/QQ_1726145469076.png&#34; alt=&#34;&#34; /&gt;&lt;/p&gt;
&lt;p&gt;挺像回事了hhh。最后，我们再对整个代码进行一轮优化。&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;第零，既然这是一个可以可视化音频的工具，为什么不能同时播放音乐呢？于是我用了 &lt;code&gt;simpleaudio&lt;/code&gt; 模块，在子线程中同步播放音乐。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;第一，上面的代码没有考虑窗口自适应，我们需要在每一次循环中都取一次窗口大小 &lt;code&gt;max_x&lt;/code&gt; 和 &lt;code&gt;max_y&lt;/code&gt;，当窗口发生变化时重新计算 &lt;code&gt;colors&lt;/code&gt; 列表。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;第二，现在的计算方式是一次性将 4096 个采样帧都进行 EMA 计算，实际我们只需要将窗口的 &lt;code&gt;max_x&lt;/code&gt; 个采样帧进行 EMA 计算和存储即可。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;第三，我通过和实际音乐播放的时间比对发现，整个渲染部分还是会有延迟（基本上每秒会有 0.003 秒的延迟），如果任由其累加，到了后面，实际的音频播放会和可视化频谱有很大的错位。我直接计算了实际播放时间和可视化帧时间的差，将其作为 &lt;code&gt;delay&lt;/code&gt;，然后在 time.sleep() 时直接减去这个 &lt;code&gt;delay&lt;/code&gt;，这样就能在单次帧渲染时把 delay 给消除。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;第四，测试发现很多振幅低的频率在频谱图中几乎不可见，这是因为我们采用的是线性归一化，高振幅的频率会压缩低振幅的频率。我们可以换成平方根归一化，这样可以减少高低频率振幅的差距。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;第五，我发现渲染帧率太高了，而帧之间的振幅变化又太快，导致频谱图看起来很闪烁。于是我设置了一个固定的帧率 &lt;code&gt;fps&lt;/code&gt; 和频谱数据缓冲区，其大小为 &lt;code&gt;frame_duration / (1/fps)&lt;/code&gt;，频谱数据先放入缓冲区，当缓冲区满时全部取出，并求平均值，然后再进行 EMA 计算和渲染。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;后续发现其实调大 &lt;code&gt;hop_size&lt;/code&gt; 就行。想复杂了 = =&lt;/p&gt;
&lt;/blockquote&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;优化后的完整代码如下：&lt;/p&gt;
&lt;figure class=&#34;highlight python&#34;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&#34;gutter&#34;&gt;&lt;pre&gt;&lt;span class=&#34;line&#34;&gt;1&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;2&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;3&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;4&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;5&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;6&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;7&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;8&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;9&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;10&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;11&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;12&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;13&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;14&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;15&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;16&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;17&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;18&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;19&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;20&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;21&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;22&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;23&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;24&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;25&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;26&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;27&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;28&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;29&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;30&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;31&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;32&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;33&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;34&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;35&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;36&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;37&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;38&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;39&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;40&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;41&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;42&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;43&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;44&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;45&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;46&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;47&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;48&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;49&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;50&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;51&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;52&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;53&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;54&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;55&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;56&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;57&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;58&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;59&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;60&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;61&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;62&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;63&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;64&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;65&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;66&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;67&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;68&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;69&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;70&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;71&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;72&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;73&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;74&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;75&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;76&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;77&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;78&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;79&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;80&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;81&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;82&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;83&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;84&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;85&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;86&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;87&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;88&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;89&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;90&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;91&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;92&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;93&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;94&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;95&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;96&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;97&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;98&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;99&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;100&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;101&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;102&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;103&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;104&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;105&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;106&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;107&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;108&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;109&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;110&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;111&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;112&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;113&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;114&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;115&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;116&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;117&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;118&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;119&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;120&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;121&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;122&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;123&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;124&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;125&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;126&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;127&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;128&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;129&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;130&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;131&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;132&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;133&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;134&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;135&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;136&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;137&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;138&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;139&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;140&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;141&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;142&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;143&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;144&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;145&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;146&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;147&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;148&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;149&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;150&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;151&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;152&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;153&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;154&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&#34;code&#34;&gt;&lt;pre&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;keyword&#34;&gt;import&lt;/span&gt; wave&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;keyword&#34;&gt;import&lt;/span&gt; struct&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;keyword&#34;&gt;import&lt;/span&gt; math&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;keyword&#34;&gt;import&lt;/span&gt; cmath&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;keyword&#34;&gt;import&lt;/span&gt; curses&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;keyword&#34;&gt;import&lt;/span&gt; time&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;keyword&#34;&gt;import&lt;/span&gt; threading&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;keyword&#34;&gt;import&lt;/span&gt; simpleaudio &lt;span class=&#34;keyword&#34;&gt;as&lt;/span&gt; sa&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;keyword&#34;&gt;def&lt;/span&gt; &lt;span class=&#34;title function_&#34;&gt;read_wav_file&lt;/span&gt;(&lt;span class=&#34;params&#34;&gt;file_name&lt;/span&gt;):&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;    &lt;span class=&#34;keyword&#34;&gt;with&lt;/span&gt; wave.&lt;span class=&#34;built_in&#34;&gt;open&lt;/span&gt;(file_name, &lt;span class=&#34;string&#34;&gt;&amp;#x27;rb&amp;#x27;&lt;/span&gt;) &lt;span class=&#34;keyword&#34;&gt;as&lt;/span&gt; wf:&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;        num_channels = wf.getnchannels()&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;        sample_width = wf.getsampwidth()&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;        frame_rate = wf.getframerate()&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;        num_frames = wf.getnframes()&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;        &lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;        raw_data = wf.readframes(num_frames)&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;        total_samples = num_frames * num_channels&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;        fmt = &lt;span class=&#34;string&#34;&gt;f&amp;quot;&amp;lt;&lt;span class=&#34;subst&#34;&gt;&amp;#123;total_samples&amp;#125;&lt;/span&gt;h&amp;quot;&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;        data = struct.unpack(fmt, raw_data)&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;        data = [data[i] &lt;span class=&#34;keyword&#34;&gt;for&lt;/span&gt; i &lt;span class=&#34;keyword&#34;&gt;in&lt;/span&gt; &lt;span class=&#34;built_in&#34;&gt;range&lt;/span&gt;(&lt;span class=&#34;number&#34;&gt;0&lt;/span&gt;, &lt;span class=&#34;built_in&#34;&gt;len&lt;/span&gt;(data), num_channels)]  &lt;span class=&#34;comment&#34;&gt;# 只取一个声道&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;    &lt;span class=&#34;keyword&#34;&gt;return&lt;/span&gt; data, frame_rate&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;keyword&#34;&gt;def&lt;/span&gt; &lt;span class=&#34;title function_&#34;&gt;fft&lt;/span&gt;(&lt;span class=&#34;params&#34;&gt;x&lt;/span&gt;):&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;    N = &lt;span class=&#34;built_in&#34;&gt;len&lt;/span&gt;(x)&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;    &lt;span class=&#34;keyword&#34;&gt;if&lt;/span&gt; N &amp;lt;= &lt;span class=&#34;number&#34;&gt;1&lt;/span&gt;:&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;        &lt;span class=&#34;keyword&#34;&gt;return&lt;/span&gt; x&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;    even = fft(x[&lt;span class=&#34;number&#34;&gt;0&lt;/span&gt;::&lt;span class=&#34;number&#34;&gt;2&lt;/span&gt;])&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;    odd = fft(x[&lt;span class=&#34;number&#34;&gt;1&lt;/span&gt;::&lt;span class=&#34;number&#34;&gt;2&lt;/span&gt;])&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;    T = [cmath.exp(-&lt;span class=&#34;number&#34;&gt;2j&lt;/span&gt; * math.pi * k / N) * odd[k] &lt;span class=&#34;keyword&#34;&gt;for&lt;/span&gt; k &lt;span class=&#34;keyword&#34;&gt;in&lt;/span&gt; &lt;span class=&#34;built_in&#34;&gt;range&lt;/span&gt;(N // &lt;span class=&#34;number&#34;&gt;2&lt;/span&gt;)]&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;    &lt;span class=&#34;keyword&#34;&gt;return&lt;/span&gt; [even[k] + T[k] &lt;span class=&#34;keyword&#34;&gt;for&lt;/span&gt; k &lt;span class=&#34;keyword&#34;&gt;in&lt;/span&gt; &lt;span class=&#34;built_in&#34;&gt;range&lt;/span&gt;(N // &lt;span class=&#34;number&#34;&gt;2&lt;/span&gt;)] + \&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;           [even[k] - T[k] &lt;span class=&#34;keyword&#34;&gt;for&lt;/span&gt; k &lt;span class=&#34;keyword&#34;&gt;in&lt;/span&gt; &lt;span class=&#34;built_in&#34;&gt;range&lt;/span&gt;(N // &lt;span class=&#34;number&#34;&gt;2&lt;/span&gt;)]&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;keyword&#34;&gt;def&lt;/span&gt; &lt;span class=&#34;title function_&#34;&gt;scale_spectrum&lt;/span&gt;(&lt;span class=&#34;params&#34;&gt;spectrum_complex, max_y&lt;/span&gt;):&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;    &lt;span class=&#34;string&#34;&gt;&amp;#x27;&amp;#x27;&amp;#x27;将频谱复数转换为振幅，并归一化&amp;#x27;&amp;#x27;&amp;#x27;&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;    max_y = &lt;span class=&#34;built_in&#34;&gt;int&lt;/span&gt;((max_y // &lt;span class=&#34;number&#34;&gt;2&lt;/span&gt;) *  &lt;span class=&#34;number&#34;&gt;0.8&lt;/span&gt;)&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;    spectrum = [&lt;span class=&#34;built_in&#34;&gt;abs&lt;/span&gt;(freq) &lt;span class=&#34;keyword&#34;&gt;for&lt;/span&gt; freq &lt;span class=&#34;keyword&#34;&gt;in&lt;/span&gt; spectrum_complex]&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;    max_spectrum = &lt;span class=&#34;built_in&#34;&gt;max&lt;/span&gt;(&lt;span class=&#34;built_in&#34;&gt;max&lt;/span&gt;(spectrum), &lt;span class=&#34;number&#34;&gt;1&lt;/span&gt;)&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;    &lt;span class=&#34;comment&#34;&gt;# return [int(max_y * freq / max_spectrum) for freq in spectrum]&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;    &lt;span class=&#34;keyword&#34;&gt;return&lt;/span&gt; [&lt;span class=&#34;built_in&#34;&gt;int&lt;/span&gt;(max_y * math.sqrt(freq) / math.sqrt(max_spectrum)) &lt;span class=&#34;keyword&#34;&gt;for&lt;/span&gt; freq &lt;span class=&#34;keyword&#34;&gt;in&lt;/span&gt; spectrum]&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;keyword&#34;&gt;def&lt;/span&gt; &lt;span class=&#34;title function_&#34;&gt;draw_spectrum&lt;/span&gt;(&lt;span class=&#34;params&#34;&gt;stdscr, spectrum: &lt;span class=&#34;built_in&#34;&gt;list&lt;/span&gt;[&lt;span class=&#34;built_in&#34;&gt;int&lt;/span&gt;], max_y: &lt;span class=&#34;built_in&#34;&gt;int&lt;/span&gt;&lt;/span&gt;):&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;    &lt;span class=&#34;keyword&#34;&gt;global&lt;/span&gt; colors&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;    stdscr.clear()&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;    half_y = max_y // &lt;span class=&#34;number&#34;&gt;2&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;    &lt;span class=&#34;keyword&#34;&gt;for&lt;/span&gt; i, freq &lt;span class=&#34;keyword&#34;&gt;in&lt;/span&gt; &lt;span class=&#34;built_in&#34;&gt;enumerate&lt;/span&gt;(spectrum):&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;        &lt;span class=&#34;keyword&#34;&gt;for&lt;/span&gt; j &lt;span class=&#34;keyword&#34;&gt;in&lt;/span&gt; &lt;span class=&#34;built_in&#34;&gt;range&lt;/span&gt;(freq):&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;            stdscr.addstr(half_y - j, i, &lt;span class=&#34;string&#34;&gt;&amp;quot;█&amp;quot;&lt;/span&gt;, curses.color_pair(colors[j]))&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;            stdscr.addstr(half_y + j, i, &lt;span class=&#34;string&#34;&gt;&amp;quot;█&amp;quot;&lt;/span&gt;, curses.color_pair(colors[j]))&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;    &lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;keyword&#34;&gt;def&lt;/span&gt; &lt;span class=&#34;title function_&#34;&gt;play_audio&lt;/span&gt;(&lt;span class=&#34;params&#34;&gt;file_name&lt;/span&gt;):&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;    wave_obj = sa.WaveObject.from_wave_file(file_name)&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;    play_obj = wave_obj.play()&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;    play_obj.wait_done()&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;    &lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;keyword&#34;&gt;def&lt;/span&gt; &lt;span class=&#34;title function_&#34;&gt;init_colors&lt;/span&gt;():&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;    curses.start_color()&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;    curses.init_pair(&lt;span class=&#34;number&#34;&gt;1&lt;/span&gt;, curses.COLOR_CYAN, &lt;span class=&#34;number&#34;&gt;1&lt;/span&gt;)&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;    curses.init_pair(&lt;span class=&#34;number&#34;&gt;2&lt;/span&gt;, curses.COLOR_WHITE, &lt;span class=&#34;number&#34;&gt;1&lt;/span&gt;)&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;    curses.init_pair(&lt;span class=&#34;number&#34;&gt;3&lt;/span&gt;, curses.COLOR_GREEN, &lt;span class=&#34;number&#34;&gt;1&lt;/span&gt;)&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;    curses.init_pair(&lt;span class=&#34;number&#34;&gt;4&lt;/span&gt;, curses.COLOR_YELLOW, &lt;span class=&#34;number&#34;&gt;1&lt;/span&gt;)&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;    &lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;keyword&#34;&gt;def&lt;/span&gt; &lt;span class=&#34;title function_&#34;&gt;adjust_colors&lt;/span&gt;(&lt;span class=&#34;params&#34;&gt;max_y&lt;/span&gt;):&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;    &lt;span class=&#34;keyword&#34;&gt;global&lt;/span&gt; colors&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;    max_y = max_y // &lt;span class=&#34;number&#34;&gt;2&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;    &lt;span class=&#34;keyword&#34;&gt;for&lt;/span&gt; i &lt;span class=&#34;keyword&#34;&gt;in&lt;/span&gt; &lt;span class=&#34;built_in&#34;&gt;range&lt;/span&gt;(max_y+&lt;span class=&#34;number&#34;&gt;1&lt;/span&gt;):&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;        &lt;span class=&#34;keyword&#34;&gt;if&lt;/span&gt; i / max_y &amp;lt;= &lt;span class=&#34;number&#34;&gt;0.2&lt;/span&gt;:&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;            colors.append(&lt;span class=&#34;number&#34;&gt;1&lt;/span&gt;)&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;        &lt;span class=&#34;keyword&#34;&gt;elif&lt;/span&gt; i / max_y &amp;lt;= &lt;span class=&#34;number&#34;&gt;0.4&lt;/span&gt;:&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;            colors.append(&lt;span class=&#34;number&#34;&gt;2&lt;/span&gt;)&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;        &lt;span class=&#34;keyword&#34;&gt;elif&lt;/span&gt; i / max_y &amp;lt;= &lt;span class=&#34;number&#34;&gt;0.6&lt;/span&gt;:&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;            colors.append(&lt;span class=&#34;number&#34;&gt;3&lt;/span&gt;)&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;        &lt;span class=&#34;keyword&#34;&gt;else&lt;/span&gt;:&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;            colors.append(&lt;span class=&#34;number&#34;&gt;4&lt;/span&gt;)&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;            &lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;keyword&#34;&gt;def&lt;/span&gt; &lt;span class=&#34;title function_&#34;&gt;ema&lt;/span&gt;(&lt;span class=&#34;params&#34;&gt;alpha, prev, curr&lt;/span&gt;):&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;    new = []&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;    &lt;span class=&#34;keyword&#34;&gt;for&lt;/span&gt; i &lt;span class=&#34;keyword&#34;&gt;in&lt;/span&gt; &lt;span class=&#34;built_in&#34;&gt;range&lt;/span&gt;(&lt;span class=&#34;built_in&#34;&gt;len&lt;/span&gt;(curr)):&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;        &lt;span class=&#34;keyword&#34;&gt;if&lt;/span&gt; i &amp;gt;= &lt;span class=&#34;built_in&#34;&gt;len&lt;/span&gt;(prev):&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;            new.append(&lt;span class=&#34;built_in&#34;&gt;int&lt;/span&gt;(curr[i])) &lt;span class=&#34;comment&#34;&gt;# 自适应窗口大小可能会导致 prev 长度小于 curr&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;        &lt;span class=&#34;keyword&#34;&gt;else&lt;/span&gt;:&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;            new.append(&lt;span class=&#34;built_in&#34;&gt;int&lt;/span&gt;((&lt;span class=&#34;number&#34;&gt;1&lt;/span&gt; - alpha) * prev[i] + alpha * curr[i]))&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;    &lt;span class=&#34;keyword&#34;&gt;return&lt;/span&gt; new&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;keyword&#34;&gt;def&lt;/span&gt; &lt;span class=&#34;title function_&#34;&gt;main&lt;/span&gt;(&lt;span class=&#34;params&#34;&gt;stdscr&lt;/span&gt;):&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;    init_colors()&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;    file_name = &lt;span class=&#34;string&#34;&gt;&amp;#x27;haruhikage.wav&amp;#x27;&lt;/span&gt;    &lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;    frame_size = &lt;span class=&#34;number&#34;&gt;2048&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;    hop_size = &lt;span class=&#34;number&#34;&gt;1024&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;    ema_alpha = &lt;span class=&#34;number&#34;&gt;0.4&lt;/span&gt; &lt;span class=&#34;comment&#34;&gt;# EMA 的 alpha&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;    fps = &lt;span class=&#34;number&#34;&gt;40&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;    data, frame_rate = read_wav_file(file_name)&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;    frame_duration = hop_size / frame_rate  &lt;span class=&#34;comment&#34;&gt;# 每帧的实际播放时间&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;    total_duration = &lt;span class=&#34;built_in&#34;&gt;len&lt;/span&gt;(data) / frame_rate  &lt;span class=&#34;comment&#34;&gt;# 音频总时长&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;    &lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;    audio_thread = threading.Thread(target=play_audio, args=(file_name,))&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;    audio_thread.start()&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;    play_start_time = time.time()&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;    &lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;    previous_spectrum = &lt;span class=&#34;literal&#34;&gt;None&lt;/span&gt; &lt;span class=&#34;comment&#34;&gt;# 上一次的频谱&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;    last_max_y, _ = stdscr.getmaxyx() &lt;span class=&#34;comment&#34;&gt;# 上一次的窗口大小&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;    adjust_colors(last_max_y)&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;    &lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;    cached_list = []&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;    ideal_frame_time = &lt;span class=&#34;number&#34;&gt;1&lt;/span&gt; / fps&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;    cached_size = &lt;span class=&#34;built_in&#34;&gt;max&lt;/span&gt;(ideal_frame_time // frame_duration, &lt;span class=&#34;number&#34;&gt;1&lt;/span&gt;)&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;    &lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;    &lt;span class=&#34;keyword&#34;&gt;for&lt;/span&gt; i &lt;span class=&#34;keyword&#34;&gt;in&lt;/span&gt; &lt;span class=&#34;built_in&#34;&gt;range&lt;/span&gt;(&lt;span class=&#34;number&#34;&gt;0&lt;/span&gt;, &lt;span class=&#34;built_in&#34;&gt;len&lt;/span&gt;(data) - frame_size, hop_size):&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;        start_time = time.time()&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;        &lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;        &lt;span class=&#34;comment&#34;&gt;# 自适应窗口大小&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;        max_y, max_x = stdscr.getmaxyx()&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;        &lt;span class=&#34;keyword&#34;&gt;if&lt;/span&gt; max_y != last_max_y:&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;            adjust_colors(max_y)&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;            last_max_y = max_y&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;        &lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;        spectrum = scale_spectrum(fft(data[i:i + frame_size])[:max_x], max_y)&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;        &lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;        &lt;span class=&#34;keyword&#34;&gt;if&lt;/span&gt; &lt;span class=&#34;built_in&#34;&gt;len&lt;/span&gt;(cached_list) &amp;gt;= cached_size:&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;            &lt;span class=&#34;comment&#34;&gt;# 求平均&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;            spectrum = [&lt;span class=&#34;built_in&#34;&gt;sum&lt;/span&gt;(x) // &lt;span class=&#34;built_in&#34;&gt;len&lt;/span&gt;(x) &lt;span class=&#34;keyword&#34;&gt;for&lt;/span&gt; x &lt;span class=&#34;keyword&#34;&gt;in&lt;/span&gt; &lt;span class=&#34;built_in&#34;&gt;zip&lt;/span&gt;(*cached_list)]&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;            cached_list = []&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;        &lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;            &lt;span class=&#34;comment&#34;&gt;# 应用 EMA&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;            &lt;span class=&#34;keyword&#34;&gt;if&lt;/span&gt; previous_spectrum &lt;span class=&#34;keyword&#34;&gt;is&lt;/span&gt; &lt;span class=&#34;keyword&#34;&gt;not&lt;/span&gt; &lt;span class=&#34;literal&#34;&gt;None&lt;/span&gt;:&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;                spectrum = ema(ema_alpha, previous_spectrum, spectrum)&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;            &lt;span class=&#34;comment&#34;&gt;# 渲染频谱&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;            draw_spectrum(stdscr, spectrum, max_y)&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;            &lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;            previous_spectrum = spectrum&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;        &lt;span class=&#34;keyword&#34;&gt;else&lt;/span&gt;:&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;            cached_list.append(spectrum)&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;        &lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;        elapsed_time = time.time()&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;        &lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;        _render_time = elapsed_time - start_time&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;        _frame_time = i / frame_rate&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;        _actual_frame_time = elapsed_time - play_start_time&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;        _delay = _actual_frame_time - _frame_time&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;        stdscr.addstr(max_y - &lt;span class=&#34;number&#34;&gt;1&lt;/span&gt;, &lt;span class=&#34;number&#34;&gt;0&lt;/span&gt;, &lt;span class=&#34;string&#34;&gt;f&amp;#x27;(&lt;span class=&#34;subst&#34;&gt;&amp;#123;_actual_frame_time:&lt;span class=&#34;number&#34;&gt;.2&lt;/span&gt;f&amp;#125;&lt;/span&gt;s) &lt;span class=&#34;subst&#34;&gt;&amp;#123;_frame_time:&lt;span class=&#34;number&#34;&gt;.2&lt;/span&gt;f&amp;#125;&lt;/span&gt; / &lt;span class=&#34;subst&#34;&gt;&amp;#123;total_duration:&lt;span class=&#34;number&#34;&gt;.2&lt;/span&gt;f&amp;#125;&lt;/span&gt; s delay: &lt;span class=&#34;subst&#34;&gt;&amp;#123;(_delay):&lt;span class=&#34;number&#34;&gt;.5&lt;/span&gt;f&amp;#125;&lt;/span&gt;s render: &lt;span class=&#34;subst&#34;&gt;&amp;#123;_render_time:&lt;span class=&#34;number&#34;&gt;.5&lt;/span&gt;f&amp;#125;&lt;/span&gt;s frame: &lt;span class=&#34;subst&#34;&gt;&amp;#123;frame_duration:&lt;span class=&#34;number&#34;&gt;.5&lt;/span&gt;f&amp;#125;&lt;/span&gt;s&amp;#x27;&lt;/span&gt;, curses.color_pair(&lt;span class=&#34;number&#34;&gt;0&lt;/span&gt;))&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;        stdscr.refresh()&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;        &lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;        time.sleep(&lt;span class=&#34;built_in&#34;&gt;max&lt;/span&gt;(&lt;span class=&#34;number&#34;&gt;0&lt;/span&gt;, frame_duration - _render_time - _delay))&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;        &lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;    audio_thread.join()&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;keyword&#34;&gt;if&lt;/span&gt; __name__ == &lt;span class=&#34;string&#34;&gt;&amp;quot;__main__&amp;quot;&lt;/span&gt;:&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;    colors = []&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;    curses.wrapper(main)&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/figure&gt;
&lt;p&gt;我们用它来播放一下来自 CHYCHIC 的 &lt;code&gt;haruhikage.wav&lt;/code&gt;：&lt;/p&gt;
&lt;p&gt;（该demo不是最终版本！下面还有两个优化！！）&lt;/p&gt;
&lt;p&gt;&lt;video controls src=&#34;https://cf.s3.soulter.top/visualize-music/demo_cry.mp4&#34; title=&#34;Title&#34; style=&#34;width: 100%&#34;&gt;&lt;/video&gt;&lt;/p&gt;
&lt;p&gt;相比于 &lt;code&gt;ncmpcpp&lt;/code&gt; 的效果，还有很多地方需要优化，比如多声道支持、更平滑的频谱图。&lt;/p&gt;
&lt;p&gt;（下面是 Update，已经更新了多声道支持）&lt;/p&gt;
&lt;h2 id=&#34;更丝滑的频谱图&#34;&gt;&lt;a class=&#34;markdownIt-Anchor&#34; href=&#34;#更丝滑的频谱图&#34;&gt;&lt;/a&gt; 更丝滑的频谱图&lt;/h2&gt;
&lt;p&gt;查阅了相关资料，突然发现我们可以设置两个 EMA Alpha，一个是当 prev_spectrum 小于 curr_spectrum 时的 alpha1，一个是当 prev_spectrum 大于 curr_spectrum 时的 alpha2。前者可以让频谱图更快地上升，后者可以让频谱图更慢地下降。这样就可以让频谱图更加丝滑了。&lt;/p&gt;
&lt;p&gt;主函数：&lt;/p&gt;
&lt;figure class=&#34;highlight py&#34;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&#34;gutter&#34;&gt;&lt;pre&gt;&lt;span class=&#34;line&#34;&gt;1&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;2&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;3&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;4&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;5&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;6&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;7&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;8&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;9&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;10&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;11&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&#34;code&#34;&gt;&lt;pre&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;keyword&#34;&gt;def&lt;/span&gt; &lt;span class=&#34;title function_&#34;&gt;main&lt;/span&gt;(&lt;span class=&#34;params&#34;&gt;stdscr&lt;/span&gt;):&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;    init_colors()&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;    file_name = args.parse_args().file&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;    frame_size = &lt;span class=&#34;number&#34;&gt;2048&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;    hop_size = &lt;span class=&#34;number&#34;&gt;1600&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;    &lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;    &lt;span class=&#34;comment&#34;&gt;# The EMA alpha. Means the weight of the **previous** spectrum.&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;    ema_alpha_down = &lt;span class=&#34;number&#34;&gt;0.93&lt;/span&gt; &lt;span class=&#34;comment&#34;&gt;# when previous value &amp;gt; current value&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;    ema_alpha_up = &lt;span class=&#34;number&#34;&gt;0.2&lt;/span&gt; &lt;span class=&#34;comment&#34;&gt;# when previous value &amp;lt;= current value&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;    &lt;span class=&#34;comment&#34;&gt;# ...&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/figure&gt;
&lt;p&gt;EMA 函数：&lt;/p&gt;
&lt;figure class=&#34;highlight py&#34;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&#34;gutter&#34;&gt;&lt;pre&gt;&lt;span class=&#34;line&#34;&gt;1&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;2&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;3&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;4&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;5&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;6&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;7&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;8&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;9&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;10&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;11&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;12&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;13&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&#34;code&#34;&gt;&lt;pre&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;keyword&#34;&gt;def&lt;/span&gt; &lt;span class=&#34;title function_&#34;&gt;ema&lt;/span&gt;(&lt;span class=&#34;params&#34;&gt;alpha_down: &lt;span class=&#34;built_in&#34;&gt;float&lt;/span&gt;, alpha_up: &lt;span class=&#34;built_in&#34;&gt;float&lt;/span&gt;, prev: &lt;span class=&#34;built_in&#34;&gt;list&lt;/span&gt;, curr: &lt;span class=&#34;built_in&#34;&gt;list&lt;/span&gt;&lt;/span&gt;):&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;    &lt;span class=&#34;string&#34;&gt;&amp;#x27;&amp;#x27;&amp;#x27;Exponential Moving Average&amp;#x27;&amp;#x27;&amp;#x27;&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;    new = []&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;    &lt;span class=&#34;keyword&#34;&gt;for&lt;/span&gt; i &lt;span class=&#34;keyword&#34;&gt;in&lt;/span&gt; &lt;span class=&#34;built_in&#34;&gt;range&lt;/span&gt;(&lt;span class=&#34;built_in&#34;&gt;len&lt;/span&gt;(curr)):&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;        &lt;span class=&#34;keyword&#34;&gt;if&lt;/span&gt; i &amp;gt;= &lt;span class=&#34;built_in&#34;&gt;len&lt;/span&gt;(prev):&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;            &lt;span class=&#34;comment&#34;&gt;# dynamically adjust the size of terminal window may cause the length of prev and curr to be different&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;            new.append(&lt;span class=&#34;built_in&#34;&gt;int&lt;/span&gt;(curr[i])) &lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;        &lt;span class=&#34;keyword&#34;&gt;else&lt;/span&gt;:&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;            &lt;span class=&#34;keyword&#34;&gt;if&lt;/span&gt; prev[i] &amp;gt; curr[i]:&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;                new.append(&lt;span class=&#34;built_in&#34;&gt;int&lt;/span&gt;(alpha_down * prev[i] + (&lt;span class=&#34;number&#34;&gt;1&lt;/span&gt; - alpha_down) * curr[i]))&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;            &lt;span class=&#34;keyword&#34;&gt;else&lt;/span&gt;:&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;                new.append(&lt;span class=&#34;built_in&#34;&gt;int&lt;/span&gt;(alpha_up * prev[i] + (&lt;span class=&#34;number&#34;&gt;1&lt;/span&gt; - alpha_up) * curr[i]))&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;    &lt;span class=&#34;keyword&#34;&gt;return&lt;/span&gt; new&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/figure&gt;
&lt;p&gt;效果如下：&lt;/p&gt;
&lt;p&gt;&lt;video controls src=&#34;https://cf.s3.soulter.top/visualize-music/demo2.mp4&#34; title=&#34;demo2&#34; style=&#34;width: 100%&#34;&gt;&lt;/video&gt;&lt;/p&gt;
&lt;p&gt;可以发现，在鼓点来时，频谱图上升得很快，而在鼓点过后，频谱图下降得很慢。这样就显得更加丝滑！&lt;/p&gt;
&lt;h2 id=&#34;更更丝滑的频谱图&#34;&gt;&lt;a class=&#34;markdownIt-Anchor&#34; href=&#34;#更更丝滑的频谱图&#34;&gt;&lt;/a&gt; 更更丝滑的频谱图&lt;/h2&gt;
&lt;p&gt;在前面的实现中，我们默认绘图的字符是 “█”，然而，它在 unicode 中叫作 “Full block”。它还有 7 个兄弟：&lt;/p&gt;
&lt;figure class=&#34;highlight py&#34;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&#34;gutter&#34;&gt;&lt;pre&gt;&lt;span class=&#34;line&#34;&gt;1&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&#34;code&#34;&gt;&lt;pre&gt;&lt;span class=&#34;line&#34;&gt;blocks = [&lt;span class=&#34;string&#34;&gt;&amp;#x27;▁&amp;#x27;&lt;/span&gt;, &lt;span class=&#34;string&#34;&gt;&amp;#x27;▂&amp;#x27;&lt;/span&gt;, &lt;span class=&#34;string&#34;&gt;&amp;#x27;▃&amp;#x27;&lt;/span&gt;, &lt;span class=&#34;string&#34;&gt;&amp;#x27;▄&amp;#x27;&lt;/span&gt;, &lt;span class=&#34;string&#34;&gt;&amp;#x27;▅&amp;#x27;&lt;/span&gt;, &lt;span class=&#34;string&#34;&gt;&amp;#x27;▆&amp;#x27;&lt;/span&gt;, &lt;span class=&#34;string&#34;&gt;&amp;#x27;▇&amp;#x27;&lt;/span&gt;, &lt;span class=&#34;string&#34;&gt;&amp;#x27;█&amp;#x27;&lt;/span&gt;]&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/figure&gt;
&lt;p&gt;我们可以借此实现更加丝滑的频谱图。&lt;/p&gt;
&lt;p&gt;首先，scale 的最大值可以直接乘以 &lt;code&gt;len(blocks)&lt;/code&gt;：&lt;/p&gt;
&lt;figure class=&#34;highlight py&#34;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&#34;gutter&#34;&gt;&lt;pre&gt;&lt;span class=&#34;line&#34;&gt;1&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&#34;code&#34;&gt;&lt;pre&gt;&lt;span class=&#34;line&#34;&gt;last_scale_max_val = &lt;span class=&#34;built_in&#34;&gt;int&lt;/span&gt;((max_y * &lt;span class=&#34;built_in&#34;&gt;len&lt;/span&gt;(blocks) // &lt;span class=&#34;number&#34;&gt;2&lt;/span&gt;) * &lt;span class=&#34;number&#34;&gt;0.8&lt;/span&gt;)&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/figure&gt;
&lt;p&gt;然后我们的振幅范围就在 [0, last_scale_max_val] 之间。&lt;/p&gt;
&lt;p&gt;在绘图时，前 &lt;code&gt;val // len(blocks)&lt;/code&gt; 个字符使用 &lt;code&gt;Full Block&lt;/code&gt;，最后一个字符使用 &lt;code&gt;blocks[val % len(blocks)]&lt;/code&gt;。&lt;/p&gt;
&lt;p&gt;我们稍加修改 &lt;code&gt;draw_spectrum&lt;/code&gt; 函数：&lt;/p&gt;
&lt;figure class=&#34;highlight py&#34;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&#34;gutter&#34;&gt;&lt;pre&gt;&lt;span class=&#34;line&#34;&gt;1&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;2&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;3&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;4&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;5&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;6&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;7&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;8&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;9&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;10&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;11&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;12&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;13&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;14&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;15&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;16&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;17&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;18&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;19&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;20&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;21&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;22&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;23&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;24&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;25&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;26&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;27&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;28&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&#34;code&#34;&gt;&lt;pre&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;keyword&#34;&gt;def&lt;/span&gt; &lt;span class=&#34;title function_&#34;&gt;draw_spectrum&lt;/span&gt;(&lt;span class=&#34;params&#34;&gt;stdscr, spectrum: &lt;span class=&#34;built_in&#34;&gt;list&lt;/span&gt;[&lt;span class=&#34;built_in&#34;&gt;list&lt;/span&gt;[&lt;span class=&#34;built_in&#34;&gt;int&lt;/span&gt;]], max_y: &lt;span class=&#34;built_in&#34;&gt;int&lt;/span&gt;&lt;/span&gt;):&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;    &lt;span class=&#34;keyword&#34;&gt;global&lt;/span&gt; colors&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;    stdscr.clear()&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;    half_y = max_y // &lt;span class=&#34;number&#34;&gt;2&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;    &lt;span class=&#34;keyword&#34;&gt;for&lt;/span&gt; i, freq &lt;span class=&#34;keyword&#34;&gt;in&lt;/span&gt; &lt;span class=&#34;built_in&#34;&gt;enumerate&lt;/span&gt;(spectrum[&lt;span class=&#34;number&#34;&gt;0&lt;/span&gt;]):&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;        full_1 = &lt;span class=&#34;built_in&#34;&gt;min&lt;/span&gt;(freq // &lt;span class=&#34;built_in&#34;&gt;len&lt;/span&gt;(blocks), &lt;span class=&#34;built_in&#34;&gt;len&lt;/span&gt;(colors))&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;        full_2 = &lt;span class=&#34;built_in&#34;&gt;min&lt;/span&gt;(spectrum[&lt;span class=&#34;number&#34;&gt;1&lt;/span&gt;][i] // &lt;span class=&#34;built_in&#34;&gt;len&lt;/span&gt;(blocks), &lt;span class=&#34;built_in&#34;&gt;len&lt;/span&gt;(colors))&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;        left_1 = freq % &lt;span class=&#34;built_in&#34;&gt;len&lt;/span&gt;(blocks)&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;        left_2 = spectrum[&lt;span class=&#34;number&#34;&gt;1&lt;/span&gt;][i] % &lt;span class=&#34;built_in&#34;&gt;len&lt;/span&gt;(blocks)&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;        &lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;        &lt;span class=&#34;comment&#34;&gt;# draw the full blocks&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;        &lt;span class=&#34;keyword&#34;&gt;for&lt;/span&gt; j &lt;span class=&#34;keyword&#34;&gt;in&lt;/span&gt; &lt;span class=&#34;built_in&#34;&gt;range&lt;/span&gt;(full_1):&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;            stdscr.addstr(half_y - j, i, blocks[-&lt;span class=&#34;number&#34;&gt;1&lt;/span&gt;], curses.color_pair(colors[j]))&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;            &lt;span class=&#34;keyword&#34;&gt;if&lt;/span&gt; &lt;span class=&#34;built_in&#34;&gt;len&lt;/span&gt;(spectrum) == &lt;span class=&#34;number&#34;&gt;1&lt;/span&gt;:&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;                &lt;span class=&#34;comment&#34;&gt;# if the audio file only has one channel, draw the same spectrum for the other channel&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;                stdscr.addstr(half_y + j, i, blocks[-&lt;span class=&#34;number&#34;&gt;1&lt;/span&gt;], curses.color_pair(colors[j]))&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;                &lt;span class=&#34;keyword&#34;&gt;continue&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;        &lt;span class=&#34;keyword&#34;&gt;for&lt;/span&gt; j &lt;span class=&#34;keyword&#34;&gt;in&lt;/span&gt; &lt;span class=&#34;built_in&#34;&gt;range&lt;/span&gt;(full_2):&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;            stdscr.addstr(half_y + j, i, blocks[-&lt;span class=&#34;number&#34;&gt;1&lt;/span&gt;], curses.color_pair(colors[j]))&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;        &lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;        &lt;span class=&#34;comment&#34;&gt;# draw the left blocks&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;        &lt;span class=&#34;keyword&#34;&gt;if&lt;/span&gt; left_1 &amp;gt; &lt;span class=&#34;number&#34;&gt;0&lt;/span&gt;:&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;            stdscr.addstr(half_y - full_1, i, blocks[left_1], curses.color_pair(colors[full_1-&lt;span class=&#34;number&#34;&gt;1&lt;/span&gt;]))&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;            &lt;span class=&#34;keyword&#34;&gt;if&lt;/span&gt; &lt;span class=&#34;built_in&#34;&gt;len&lt;/span&gt;(spectrum) == &lt;span class=&#34;number&#34;&gt;1&lt;/span&gt;:&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;                stdscr.addstr(half_y + full_1, i, blocks[left_1], curses.color_pair(colors[full_1-&lt;span class=&#34;number&#34;&gt;1&lt;/span&gt;]))&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;        &lt;span class=&#34;keyword&#34;&gt;if&lt;/span&gt; left_2 &amp;gt; &lt;span class=&#34;number&#34;&gt;0&lt;/span&gt;:&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;            stdscr.addstr(half_y + full_2, i, blocks[left_2], curses.color_pair(colors[full_2-&lt;span class=&#34;number&#34;&gt;1&lt;/span&gt;]))&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/figure&gt;
&lt;p&gt;&lt;video controls src=&#34;https://cf.s3.soulter.top/visualize-music/demo2.5.mp4&#34; title=&#34;demo2.5&#34; style=&#34;width: 100%&#34;&gt;&lt;/video&gt;&lt;/p&gt;
&lt;p&gt;OHHH! 看样子更加丝滑了！… 怎么下面的频谱图变得断断续续了？&lt;/p&gt;
&lt;p&gt;因为我们的 blocks 使用的 unicode 字符的填充是从下往上的，因此就会导致在绘制下半部分频谱图时，末端使用的字符方向不对，导致 full block 和其他 block 之间有间隙。怎么解决呢？似乎 unicode 字符没有从上往下填充的字符。&lt;/p&gt;
&lt;p&gt;一个好办法是创造一个 &lt;code&gt;colors_reverse&lt;/code&gt;，其颜色配置和 &lt;code&gt;colors&lt;/code&gt; 相反，比如 colors 中一个 color 的前景是淡蓝色，背景是黑色，那么 colors_reverse 中这个 color 的前景就是黑色，背景就是淡蓝色。然后我们在绘制频谱图时，将下半部分的频谱图字符颜色设置为 &lt;code&gt;colors_reverse&lt;/code&gt; 中的某个。&lt;/p&gt;
&lt;figure class=&#34;highlight py&#34;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&#34;gutter&#34;&gt;&lt;pre&gt;&lt;span class=&#34;line&#34;&gt;1&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;2&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;3&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;4&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;5&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;6&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;7&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;8&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;9&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;10&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;11&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;12&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&#34;code&#34;&gt;&lt;pre&gt;&lt;span class=&#34;line&#34;&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;keyword&#34;&gt;def&lt;/span&gt; &lt;span class=&#34;title function_&#34;&gt;draw_spectrum&lt;/span&gt;(&lt;span class=&#34;params&#34;&gt;stdscr, spectrum: &lt;span class=&#34;built_in&#34;&gt;list&lt;/span&gt;[&lt;span class=&#34;built_in&#34;&gt;list&lt;/span&gt;[&lt;span class=&#34;built_in&#34;&gt;int&lt;/span&gt;]], max_y: &lt;span class=&#34;built_in&#34;&gt;int&lt;/span&gt;&lt;/span&gt;):&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;    &lt;span class=&#34;comment&#34;&gt;# ...&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;    &lt;span class=&#34;keyword&#34;&gt;for&lt;/span&gt; i, freq &lt;span class=&#34;keyword&#34;&gt;in&lt;/span&gt; &lt;span class=&#34;built_in&#34;&gt;enumerate&lt;/span&gt;(spectrum[&lt;span class=&#34;number&#34;&gt;0&lt;/span&gt;]):&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;        &lt;span class=&#34;comment&#34;&gt;# ...&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;        &lt;span class=&#34;comment&#34;&gt;# draw the left blocks&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;        &lt;span class=&#34;keyword&#34;&gt;if&lt;/span&gt; left_1 &amp;gt; &lt;span class=&#34;number&#34;&gt;0&lt;/span&gt;:&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;            stdscr.addstr(half_y - full_1, i, blocks[left_1], curses.color_pair(colors[full_1-&lt;span class=&#34;number&#34;&gt;1&lt;/span&gt;]))&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;            &lt;span class=&#34;keyword&#34;&gt;if&lt;/span&gt; &lt;span class=&#34;built_in&#34;&gt;len&lt;/span&gt;(spectrum) == &lt;span class=&#34;number&#34;&gt;1&lt;/span&gt;:&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;                stdscr.addstr(half_y + full_1, i, blocks[left_1], curses.color_pair(colors_reverse[full_1-&lt;span class=&#34;number&#34;&gt;1&lt;/span&gt;]))&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;        &lt;span class=&#34;keyword&#34;&gt;if&lt;/span&gt; left_2 &amp;gt; &lt;span class=&#34;number&#34;&gt;0&lt;/span&gt;:&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;            stdscr.addstr(half_y + full_2, i, blocks[left_2], curses.color_pair(colors_reverse[full_2-&lt;span class=&#34;number&#34;&gt;1&lt;/span&gt;]))&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/figure&gt;
&lt;p&gt;效果如下：&lt;/p&gt;
&lt;p&gt;&lt;video controls src=&#34;https://cf.s3.soulter.top/visualize-music/demo3.mp4&#34; title=&#34;demo3&#34; style=&#34;width: 100%&#34;&gt;&lt;/video&gt;&lt;/p&gt;
&lt;p&gt;快赶上 &lt;code&gt;ncmpcpp&lt;/code&gt; 的效果了！&lt;/p&gt;
&lt;p&gt;后面有时间再继续优化喵。&lt;/p&gt;
&lt;h2 id=&#34;附直观理解傅立叶变换&#34;&gt;&lt;a class=&#34;markdownIt-Anchor&#34; href=&#34;#附直观理解傅立叶变换&#34;&gt;&lt;/a&gt; 附：直观理解傅立叶变换&lt;/h2&gt;
&lt;p&gt;上文讲到傅立叶变换的基本思想是将一个信号分解为一系列正弦波的叠加。&lt;/p&gt;
&lt;p&gt;拿方波举个例子，下图是一个 5Hz 的方波和一个 5Hz 的正弦波&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://cf.s3.soulter.top/visualize-music/QQ_1725976721919.png&#34; alt=&#34;&#34; /&gt;&lt;/p&gt;
&lt;p&gt;是不是差距蛮大的？那如果我们将 2 个正弦波叠加在一起呢？&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://cf.s3.soulter.top/visualize-music/QQ_1725976743585.png&#34; alt=&#34;&#34; /&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://cf.s3.soulter.top/visualize-music/QQ_1725976828380.png&#34; alt=&#34;&#34; /&gt;&lt;/p&gt;
&lt;p&gt;我们会发现，更像方波了。随着我们叠加的正弦波越来越多，我们的波形就会越来越接近方波。&lt;/p&gt;
&lt;p&gt;我们日常听的音乐就是由大量不同频率的正弦波叠加而成的，而傅立叶变换就能够将其分解为一系列不同频率的波。&lt;/p&gt;
&lt;p&gt;为了直观理解傅立叶变换，我参考了 &lt;a href=&#34;https://www.youtube.com/watch?v=spUNpyF58BY&#34;&gt;@3b1b 的视频&lt;/a&gt; 里的方法。&lt;/p&gt;
&lt;p&gt;还是以 5Hz 的正弦波为例。&lt;/p&gt;
&lt;p&gt;440hz_16bit_5s.wav:&lt;/p&gt;
&lt;p&gt;&lt;audio controls src=&#34;https://cf.s3.soulter.top/visualize-music/440hz_16bit_5s.wav&#34; title=&#34;440hz_16bit_5s&#34;&gt;&lt;/audio&gt;&lt;/p&gt;
&lt;p&gt;我们在复平面上从实数轴正半轴开始逆时针转动一个向量：在任意时刻下，向量的模长等于正弦波的振幅，向量的移动速度设置为 &lt;code&gt;1 Hz&lt;/code&gt;，以此绘制得到一个图：&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://cf.s3.soulter.top/visualize-music/QQ_1725978427291.png&#34; alt=&#34;&#34; /&gt;&lt;/p&gt;
&lt;p&gt;它的质心用红点标了出来。&lt;/p&gt;
&lt;p&gt;当向量的移动速度设置为 &lt;code&gt;4.2 Hz&lt;/code&gt; 时：&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://cf.s3.soulter.top/visualize-music/QQ_1726146180844.png&#34; alt=&#34;&#34; /&gt;&lt;/p&gt;
&lt;p&gt;当向量的移动速度设置为 &lt;code&gt;4.3 Hz&lt;/code&gt; 时：&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://cf.s3.soulter.top/visualize-music/QQ_1726146113953.png&#34; alt=&#34;&#34; /&gt;&lt;/p&gt;
&lt;p&gt;当向量的移动速度设置为 &lt;code&gt;5 Hz&lt;/code&gt; 时：&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://cf.s3.soulter.top/visualize-music/QQ_1725979910845.png&#34; alt=&#34;&#34; /&gt;&lt;/p&gt;
&lt;p&gt;有没有发现什么？我们绘制出质心与原点的距离随向量的移动速度变化的图（我们称为“缠绕图像”）：&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://cf.s3.soulter.top/visualize-music/QQ_1725979995976.png&#34; alt=&#34;&#34; /&gt;&lt;/p&gt;
&lt;p&gt;在接近 &lt;code&gt;5 Hz&lt;/code&gt; 时，值最大！！&lt;/p&gt;
&lt;p&gt;同样，我们将 5Hz + 15Hz 的正弦波叠加在一起，再次绘制这个图：&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://cf.s3.soulter.top/visualize-music/QQ_1725980156817.png&#34; alt=&#34;&#34; /&gt;&lt;/p&gt;
&lt;p&gt;极大值点同样出现在 5Hz 和 15Hz 处。&lt;/p&gt;
&lt;p&gt;发生了什么？让我们从公式的角度出发，来解释这个现象。&lt;/p&gt;
&lt;p&gt;由欧拉公式，我们知道，一个圆周运动可以表示为：&lt;/p&gt;
&lt;p class=&#39;katex-block&#39;&gt;&lt;span class=&#34;katex-display&#34;&gt;&lt;span class=&#34;katex&#34;&gt;&lt;span class=&#34;katex-mathml&#34;&gt;&lt;math xmlns=&#34;http://www.w3.org/1998/Math/MathML&#34; display=&#34;block&#34;&gt;&lt;semantics&gt;&lt;mrow&gt;&lt;msup&gt;&lt;mi&gt;e&lt;/mi&gt;&lt;mrow&gt;&lt;mn&gt;2&lt;/mn&gt;&lt;mi&gt;π&lt;/mi&gt;&lt;mi&gt;i&lt;/mi&gt;&lt;mi&gt;f&lt;/mi&gt;&lt;mi&gt;t&lt;/mi&gt;&lt;/mrow&gt;&lt;/msup&gt;&lt;/mrow&gt;&lt;annotation encoding=&#34;application/x-tex&#34;&gt;e^{2\pi i f t}
&lt;/annotation&gt;&lt;/semantics&gt;&lt;/math&gt;&lt;/span&gt;&lt;span class=&#34;katex-html&#34; aria-hidden=&#34;true&#34;&gt;&lt;span class=&#34;base&#34;&gt;&lt;span class=&#34;strut&#34; style=&#34;height:0.8991079999999999em;vertical-align:0em;&#34;&gt;&lt;/span&gt;&lt;span class=&#34;mord&#34;&gt;&lt;span class=&#34;mord mathnormal&#34;&gt;e&lt;/span&gt;&lt;span class=&#34;msupsub&#34;&gt;&lt;span class=&#34;vlist-t&#34;&gt;&lt;span class=&#34;vlist-r&#34;&gt;&lt;span class=&#34;vlist&#34; style=&#34;height:0.8991079999999999em;&#34;&gt;&lt;span style=&#34;top:-3.1130000000000004em;margin-right:0.05em;&#34;&gt;&lt;span class=&#34;pstrut&#34; style=&#34;height:2.7em;&#34;&gt;&lt;/span&gt;&lt;span class=&#34;sizing reset-size6 size3 mtight&#34;&gt;&lt;span class=&#34;mord mtight&#34;&gt;&lt;span class=&#34;mord mtight&#34;&gt;2&lt;/span&gt;&lt;span class=&#34;mord mathnormal mtight&#34; style=&#34;margin-right:0.03588em;&#34;&gt;π&lt;/span&gt;&lt;span class=&#34;mord mathnormal mtight&#34;&gt;i&lt;/span&gt;&lt;span class=&#34;mord mathnormal mtight&#34; style=&#34;margin-right:0.10764em;&#34;&gt;f&lt;/span&gt;&lt;span class=&#34;mord mathnormal mtight&#34;&gt;t&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;其中 &lt;span class=&#34;katex&#34;&gt;&lt;span class=&#34;katex-mathml&#34;&gt;&lt;math xmlns=&#34;http://www.w3.org/1998/Math/MathML&#34;&gt;&lt;semantics&gt;&lt;mrow&gt;&lt;mi&gt;f&lt;/mi&gt;&lt;/mrow&gt;&lt;annotation encoding=&#34;application/x-tex&#34;&gt;f&lt;/annotation&gt;&lt;/semantics&gt;&lt;/math&gt;&lt;/span&gt;&lt;span class=&#34;katex-html&#34; aria-hidden=&#34;true&#34;&gt;&lt;span class=&#34;base&#34;&gt;&lt;span class=&#34;strut&#34; style=&#34;height:0.8888799999999999em;vertical-align:-0.19444em;&#34;&gt;&lt;/span&gt;&lt;span class=&#34;mord mathnormal&#34; style=&#34;margin-right:0.10764em;&#34;&gt;f&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt; 为频率，&lt;span class=&#34;katex&#34;&gt;&lt;span class=&#34;katex-mathml&#34;&gt;&lt;math xmlns=&#34;http://www.w3.org/1998/Math/MathML&#34;&gt;&lt;semantics&gt;&lt;mrow&gt;&lt;mi&gt;t&lt;/mi&gt;&lt;/mrow&gt;&lt;annotation encoding=&#34;application/x-tex&#34;&gt;t&lt;/annotation&gt;&lt;/semantics&gt;&lt;/math&gt;&lt;/span&gt;&lt;span class=&#34;katex-html&#34; aria-hidden=&#34;true&#34;&gt;&lt;span class=&#34;base&#34;&gt;&lt;span class=&#34;strut&#34; style=&#34;height:0.61508em;vertical-align:0em;&#34;&gt;&lt;/span&gt;&lt;span class=&#34;mord mathnormal&#34;&gt;t&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt; 为时间。&lt;/p&gt;
&lt;p&gt;上述像花一样的缠绕图像可以表示为：&lt;/p&gt;
&lt;p class=&#39;katex-block&#39;&gt;&lt;span class=&#34;katex-display&#34;&gt;&lt;span class=&#34;katex&#34;&gt;&lt;span class=&#34;katex-mathml&#34;&gt;&lt;math xmlns=&#34;http://www.w3.org/1998/Math/MathML&#34; display=&#34;block&#34;&gt;&lt;semantics&gt;&lt;mrow&gt;&lt;mi&gt;g&lt;/mi&gt;&lt;mo stretchy=&#34;false&#34;&gt;(&lt;/mo&gt;&lt;mi&gt;t&lt;/mi&gt;&lt;mo stretchy=&#34;false&#34;&gt;)&lt;/mo&gt;&lt;msup&gt;&lt;mi&gt;e&lt;/mi&gt;&lt;mrow&gt;&lt;mn&gt;2&lt;/mn&gt;&lt;mi&gt;π&lt;/mi&gt;&lt;mi&gt;i&lt;/mi&gt;&lt;mi&gt;t&lt;/mi&gt;&lt;/mrow&gt;&lt;/msup&gt;&lt;/mrow&gt;&lt;annotation encoding=&#34;application/x-tex&#34;&gt;g(t) e^{2\pi i t} 
&lt;/annotation&gt;&lt;/semantics&gt;&lt;/math&gt;&lt;/span&gt;&lt;span class=&#34;katex-html&#34; aria-hidden=&#34;true&#34;&gt;&lt;span class=&#34;base&#34;&gt;&lt;span class=&#34;strut&#34; style=&#34;height:1.1246639999999999em;vertical-align:-0.25em;&#34;&gt;&lt;/span&gt;&lt;span class=&#34;mord mathnormal&#34; style=&#34;margin-right:0.03588em;&#34;&gt;g&lt;/span&gt;&lt;span class=&#34;mopen&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;mord mathnormal&#34;&gt;t&lt;/span&gt;&lt;span class=&#34;mclose&#34;&gt;)&lt;/span&gt;&lt;span class=&#34;mord&#34;&gt;&lt;span class=&#34;mord mathnormal&#34;&gt;e&lt;/span&gt;&lt;span class=&#34;msupsub&#34;&gt;&lt;span class=&#34;vlist-t&#34;&gt;&lt;span class=&#34;vlist-r&#34;&gt;&lt;span class=&#34;vlist&#34; style=&#34;height:0.8746639999999999em;&#34;&gt;&lt;span style=&#34;top:-3.113em;margin-right:0.05em;&#34;&gt;&lt;span class=&#34;pstrut&#34; style=&#34;height:2.7em;&#34;&gt;&lt;/span&gt;&lt;span class=&#34;sizing reset-size6 size3 mtight&#34;&gt;&lt;span class=&#34;mord mtight&#34;&gt;&lt;span class=&#34;mord mtight&#34;&gt;2&lt;/span&gt;&lt;span class=&#34;mord mathnormal mtight&#34; style=&#34;margin-right:0.03588em;&#34;&gt;π&lt;/span&gt;&lt;span class=&#34;mord mathnormal mtight&#34;&gt;i&lt;/span&gt;&lt;span class=&#34;mord mathnormal mtight&#34;&gt;t&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;其中，g(t) 为正弦函数：$ g(t) = \sin(2\pi 5 t) $，也就是一个振幅随时间变化的方程。&lt;/p&gt;
&lt;p&gt;我们需要描述的是其质心在复平面上的坐标的模随时间变化的方程。因此随机取 &lt;span class=&#34;katex&#34;&gt;&lt;span class=&#34;katex-mathml&#34;&gt;&lt;math xmlns=&#34;http://www.w3.org/1998/Math/MathML&#34;&gt;&lt;semantics&gt;&lt;mrow&gt;&lt;mi&gt;N&lt;/mi&gt;&lt;/mrow&gt;&lt;annotation encoding=&#34;application/x-tex&#34;&gt;N&lt;/annotation&gt;&lt;/semantics&gt;&lt;/math&gt;&lt;/span&gt;&lt;span class=&#34;katex-html&#34; aria-hidden=&#34;true&#34;&gt;&lt;span class=&#34;base&#34;&gt;&lt;span class=&#34;strut&#34; style=&#34;height:0.68333em;vertical-align:0em;&#34;&gt;&lt;/span&gt;&lt;span class=&#34;mord mathnormal&#34; style=&#34;margin-right:0.10903em;&#34;&gt;N&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt; 个时间点，计算其质心的坐标，然后取其模长的平均值。&lt;/p&gt;
&lt;p class=&#39;katex-block&#39;&gt;&lt;span class=&#34;katex-display&#34;&gt;&lt;span class=&#34;katex&#34;&gt;&lt;span class=&#34;katex-mathml&#34;&gt;&lt;math xmlns=&#34;http://www.w3.org/1998/Math/MathML&#34; display=&#34;block&#34;&gt;&lt;semantics&gt;&lt;mrow&gt;&lt;mfrac&gt;&lt;mn&gt;1&lt;/mn&gt;&lt;mi&gt;N&lt;/mi&gt;&lt;/mfrac&gt;&lt;munderover&gt;&lt;mo&gt;∑&lt;/mo&gt;&lt;mrow&gt;&lt;mi&gt;n&lt;/mi&gt;&lt;mo&gt;=&lt;/mo&gt;&lt;mn&gt;1&lt;/mn&gt;&lt;/mrow&gt;&lt;mi&gt;N&lt;/mi&gt;&lt;/munderover&gt;&lt;mi mathvariant=&#34;normal&#34;&gt;∣&lt;/mi&gt;&lt;mi&gt;g&lt;/mi&gt;&lt;mo stretchy=&#34;false&#34;&gt;(&lt;/mo&gt;&lt;msub&gt;&lt;mi&gt;t&lt;/mi&gt;&lt;mi&gt;n&lt;/mi&gt;&lt;/msub&gt;&lt;mo stretchy=&#34;false&#34;&gt;)&lt;/mo&gt;&lt;msup&gt;&lt;mi&gt;e&lt;/mi&gt;&lt;mrow&gt;&lt;mn&gt;2&lt;/mn&gt;&lt;mi&gt;π&lt;/mi&gt;&lt;mi&gt;i&lt;/mi&gt;&lt;msub&gt;&lt;mi&gt;t&lt;/mi&gt;&lt;mi&gt;n&lt;/mi&gt;&lt;/msub&gt;&lt;/mrow&gt;&lt;/msup&gt;&lt;mi mathvariant=&#34;normal&#34;&gt;∣&lt;/mi&gt;&lt;/mrow&gt;&lt;annotation encoding=&#34;application/x-tex&#34;&gt;\frac{1}{N} \sum_{n=1}^{N} |g(t_n) e^{2\pi i t_n}| 
&lt;/annotation&gt;&lt;/semantics&gt;&lt;/math&gt;&lt;/span&gt;&lt;span class=&#34;katex-html&#34; aria-hidden=&#34;true&#34;&gt;&lt;span class=&#34;base&#34;&gt;&lt;span class=&#34;strut&#34; style=&#34;height:3.0954490000000003em;vertical-align:-1.267113em;&#34;&gt;&lt;/span&gt;&lt;span class=&#34;mord&#34;&gt;&lt;span class=&#34;mopen nulldelimiter&#34;&gt;&lt;/span&gt;&lt;span class=&#34;mfrac&#34;&gt;&lt;span class=&#34;vlist-t vlist-t2&#34;&gt;&lt;span class=&#34;vlist-r&#34;&gt;&lt;span class=&#34;vlist&#34; style=&#34;height:1.32144em;&#34;&gt;&lt;span style=&#34;top:-2.314em;&#34;&gt;&lt;span class=&#34;pstrut&#34; style=&#34;height:3em;&#34;&gt;&lt;/span&gt;&lt;span class=&#34;mord&#34;&gt;&lt;span class=&#34;mord mathnormal&#34; style=&#34;margin-right:0.10903em;&#34;&gt;N&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;top:-3.23em;&#34;&gt;&lt;span class=&#34;pstrut&#34; style=&#34;height:3em;&#34;&gt;&lt;/span&gt;&lt;span class=&#34;frac-line&#34; style=&#34;border-bottom-width:0.04em;&#34;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;top:-3.677em;&#34;&gt;&lt;span class=&#34;pstrut&#34; style=&#34;height:3em;&#34;&gt;&lt;/span&gt;&lt;span class=&#34;mord&#34;&gt;&lt;span class=&#34;mord&#34;&gt;1&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;vlist-s&#34;&gt;​&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;vlist-r&#34;&gt;&lt;span class=&#34;vlist&#34; style=&#34;height:0.686em;&#34;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;mclose nulldelimiter&#34;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;mspace&#34; style=&#34;margin-right:0.16666666666666666em;&#34;&gt;&lt;/span&gt;&lt;span class=&#34;mop op-limits&#34;&gt;&lt;span class=&#34;vlist-t vlist-t2&#34;&gt;&lt;span class=&#34;vlist-r&#34;&gt;&lt;span class=&#34;vlist&#34; style=&#34;height:1.8283360000000002em;&#34;&gt;&lt;span style=&#34;top:-1.882887em;margin-left:0em;&#34;&gt;&lt;span class=&#34;pstrut&#34; style=&#34;height:3.05em;&#34;&gt;&lt;/span&gt;&lt;span class=&#34;sizing reset-size6 size3 mtight&#34;&gt;&lt;span class=&#34;mord mtight&#34;&gt;&lt;span class=&#34;mord mathnormal mtight&#34;&gt;n&lt;/span&gt;&lt;span class=&#34;mrel mtight&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;mord mtight&#34;&gt;1&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;top:-3.050005em;&#34;&gt;&lt;span class=&#34;pstrut&#34; style=&#34;height:3.05em;&#34;&gt;&lt;/span&gt;&lt;span&gt;&lt;span class=&#34;mop op-symbol large-op&#34;&gt;∑&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;top:-4.3000050000000005em;margin-left:0em;&#34;&gt;&lt;span class=&#34;pstrut&#34; style=&#34;height:3.05em;&#34;&gt;&lt;/span&gt;&lt;span class=&#34;sizing reset-size6 size3 mtight&#34;&gt;&lt;span class=&#34;mord mtight&#34;&gt;&lt;span class=&#34;mord mathnormal mtight&#34; style=&#34;margin-right:0.10903em;&#34;&gt;N&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;vlist-s&#34;&gt;​&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;vlist-r&#34;&gt;&lt;span class=&#34;vlist&#34; style=&#34;height:1.267113em;&#34;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;mspace&#34; style=&#34;margin-right:0.16666666666666666em;&#34;&gt;&lt;/span&gt;&lt;span class=&#34;mord&#34;&gt;∣&lt;/span&gt;&lt;span class=&#34;mord mathnormal&#34; style=&#34;margin-right:0.03588em;&#34;&gt;g&lt;/span&gt;&lt;span class=&#34;mopen&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;mord&#34;&gt;&lt;span class=&#34;mord mathnormal&#34;&gt;t&lt;/span&gt;&lt;span class=&#34;msupsub&#34;&gt;&lt;span class=&#34;vlist-t vlist-t2&#34;&gt;&lt;span class=&#34;vlist-r&#34;&gt;&lt;span class=&#34;vlist&#34; style=&#34;height:0.151392em;&#34;&gt;&lt;span style=&#34;top:-2.5500000000000003em;margin-left:0em;margin-right:0.05em;&#34;&gt;&lt;span class=&#34;pstrut&#34; style=&#34;height:2.7em;&#34;&gt;&lt;/span&gt;&lt;span class=&#34;sizing reset-size6 size3 mtight&#34;&gt;&lt;span class=&#34;mord mathnormal mtight&#34;&gt;n&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;vlist-s&#34;&gt;​&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;vlist-r&#34;&gt;&lt;span class=&#34;vlist&#34; style=&#34;height:0.15em;&#34;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;mclose&#34;&gt;)&lt;/span&gt;&lt;span class=&#34;mord&#34;&gt;&lt;span class=&#34;mord mathnormal&#34;&gt;e&lt;/span&gt;&lt;span class=&#34;msupsub&#34;&gt;&lt;span class=&#34;vlist-t&#34;&gt;&lt;span class=&#34;vlist-r&#34;&gt;&lt;span class=&#34;vlist&#34; style=&#34;height:0.8746639999999999em;&#34;&gt;&lt;span style=&#34;top:-3.113em;margin-right:0.05em;&#34;&gt;&lt;span class=&#34;pstrut&#34; style=&#34;height:2.7em;&#34;&gt;&lt;/span&gt;&lt;span class=&#34;sizing reset-size6 size3 mtight&#34;&gt;&lt;span class=&#34;mord mtight&#34;&gt;&lt;span class=&#34;mord mtight&#34;&gt;2&lt;/span&gt;&lt;span class=&#34;mord mathnormal mtight&#34; style=&#34;margin-right:0.03588em;&#34;&gt;π&lt;/span&gt;&lt;span class=&#34;mord mathnormal mtight&#34;&gt;i&lt;/span&gt;&lt;span class=&#34;mord mtight&#34;&gt;&lt;span class=&#34;mord mathnormal mtight&#34;&gt;t&lt;/span&gt;&lt;span class=&#34;msupsub&#34;&gt;&lt;span class=&#34;vlist-t vlist-t2&#34;&gt;&lt;span class=&#34;vlist-r&#34;&gt;&lt;span class=&#34;vlist&#34; style=&#34;height:0.16454285714285719em;&#34;&gt;&lt;span style=&#34;top:-2.357em;margin-left:0em;margin-right:0.07142857142857144em;&#34;&gt;&lt;span class=&#34;pstrut&#34; style=&#34;height:2.5em;&#34;&gt;&lt;/span&gt;&lt;span class=&#34;sizing reset-size3 size1 mtight&#34;&gt;&lt;span class=&#34;mord mathnormal mtight&#34;&gt;n&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;vlist-s&#34;&gt;​&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;vlist-r&#34;&gt;&lt;span class=&#34;vlist&#34; style=&#34;height:0.143em;&#34;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;mord&#34;&gt;∣&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;类似这样，我们对函数作积分，就得到了傅立叶变换：&lt;/p&gt;
&lt;p class=&#39;katex-block&#39;&gt;&lt;span class=&#34;katex-display&#34;&gt;&lt;span class=&#34;katex&#34;&gt;&lt;span class=&#34;katex-mathml&#34;&gt;&lt;math xmlns=&#34;http://www.w3.org/1998/Math/MathML&#34; display=&#34;block&#34;&gt;&lt;semantics&gt;&lt;mrow&gt;&lt;msubsup&gt;&lt;mo&gt;∫&lt;/mo&gt;&lt;msub&gt;&lt;mi&gt;t&lt;/mi&gt;&lt;mn&gt;1&lt;/mn&gt;&lt;/msub&gt;&lt;msub&gt;&lt;mi&gt;t&lt;/mi&gt;&lt;mn&gt;2&lt;/mn&gt;&lt;/msub&gt;&lt;/msubsup&gt;&lt;mi mathvariant=&#34;normal&#34;&gt;∣&lt;/mi&gt;&lt;mi&gt;g&lt;/mi&gt;&lt;mo stretchy=&#34;false&#34;&gt;(&lt;/mo&gt;&lt;mi&gt;t&lt;/mi&gt;&lt;mo stretchy=&#34;false&#34;&gt;)&lt;/mo&gt;&lt;msup&gt;&lt;mi&gt;e&lt;/mi&gt;&lt;mrow&gt;&lt;mn&gt;2&lt;/mn&gt;&lt;mi&gt;π&lt;/mi&gt;&lt;mi&gt;i&lt;/mi&gt;&lt;mi&gt;t&lt;/mi&gt;&lt;/mrow&gt;&lt;/msup&gt;&lt;mi mathvariant=&#34;normal&#34;&gt;∣&lt;/mi&gt;&lt;mi&gt;d&lt;/mi&gt;&lt;mi&gt;t&lt;/mi&gt;&lt;/mrow&gt;&lt;annotation encoding=&#34;application/x-tex&#34;&gt;\int_{t_1}^{t_2} |g(t) e^{2\pi i t}| dt 
&lt;/annotation&gt;&lt;/semantics&gt;&lt;/math&gt;&lt;/span&gt;&lt;span class=&#34;katex-html&#34; aria-hidden=&#34;true&#34;&gt;&lt;span class=&#34;base&#34;&gt;&lt;span class=&#34;strut&#34; style=&#34;height:2.5555060000000003em;vertical-align:-1.01205em;&#34;&gt;&lt;/span&gt;&lt;span class=&#34;mop&#34;&gt;&lt;span class=&#34;mop op-symbol large-op&#34; style=&#34;margin-right:0.44445em;position:relative;top:-0.0011249999999999316em;&#34;&gt;∫&lt;/span&gt;&lt;span class=&#34;msupsub&#34;&gt;&lt;span class=&#34;vlist-t vlist-t2&#34;&gt;&lt;span class=&#34;vlist-r&#34;&gt;&lt;span class=&#34;vlist&#34; style=&#34;height:1.5434560000000004em;&#34;&gt;&lt;span style=&#34;top:-1.7880500000000004em;margin-left:-0.44445em;margin-right:0.05em;&#34;&gt;&lt;span class=&#34;pstrut&#34; style=&#34;height:2.7em;&#34;&gt;&lt;/span&gt;&lt;span class=&#34;sizing reset-size6 size3 mtight&#34;&gt;&lt;span class=&#34;mord mtight&#34;&gt;&lt;span class=&#34;mord mtight&#34;&gt;&lt;span class=&#34;mord mathnormal mtight&#34;&gt;t&lt;/span&gt;&lt;span class=&#34;msupsub&#34;&gt;&lt;span class=&#34;vlist-t vlist-t2&#34;&gt;&lt;span class=&#34;vlist-r&#34;&gt;&lt;span class=&#34;vlist&#34; style=&#34;height:0.31731428571428577em;&#34;&gt;&lt;span style=&#34;top:-2.357em;margin-left:0em;margin-right:0.07142857142857144em;&#34;&gt;&lt;span class=&#34;pstrut&#34; style=&#34;height:2.5em;&#34;&gt;&lt;/span&gt;&lt;span class=&#34;sizing reset-size3 size1 mtight&#34;&gt;&lt;span class=&#34;mord mtight&#34;&gt;1&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;vlist-s&#34;&gt;​&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;vlist-r&#34;&gt;&lt;span class=&#34;vlist&#34; style=&#34;height:0.143em;&#34;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;top:-3.8129000000000004em;margin-right:0.05em;&#34;&gt;&lt;span class=&#34;pstrut&#34; style=&#34;height:2.7em;&#34;&gt;&lt;/span&gt;&lt;span class=&#34;sizing reset-size6 size3 mtight&#34;&gt;&lt;span class=&#34;mord mtight&#34;&gt;&lt;span class=&#34;mord mtight&#34;&gt;&lt;span class=&#34;mord mathnormal mtight&#34;&gt;t&lt;/span&gt;&lt;span class=&#34;msupsub&#34;&gt;&lt;span class=&#34;vlist-t vlist-t2&#34;&gt;&lt;span class=&#34;vlist-r&#34;&gt;&lt;span class=&#34;vlist&#34; style=&#34;height:0.31731428571428577em;&#34;&gt;&lt;span style=&#34;top:-2.357em;margin-left:0em;margin-right:0.07142857142857144em;&#34;&gt;&lt;span class=&#34;pstrut&#34; style=&#34;height:2.5em;&#34;&gt;&lt;/span&gt;&lt;span class=&#34;sizing reset-size3 size1 mtight&#34;&gt;&lt;span class=&#34;mord mtight&#34;&gt;2&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;vlist-s&#34;&gt;​&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;vlist-r&#34;&gt;&lt;span class=&#34;vlist&#34; style=&#34;height:0.143em;&#34;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;vlist-s&#34;&gt;​&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;vlist-r&#34;&gt;&lt;span class=&#34;vlist&#34; style=&#34;height:1.01205em;&#34;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;mspace&#34; style=&#34;margin-right:0.16666666666666666em;&#34;&gt;&lt;/span&gt;&lt;span class=&#34;mord&#34;&gt;∣&lt;/span&gt;&lt;span class=&#34;mord mathnormal&#34; style=&#34;margin-right:0.03588em;&#34;&gt;g&lt;/span&gt;&lt;span class=&#34;mopen&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;mord mathnormal&#34;&gt;t&lt;/span&gt;&lt;span class=&#34;mclose&#34;&gt;)&lt;/span&gt;&lt;span class=&#34;mord&#34;&gt;&lt;span class=&#34;mord mathnormal&#34;&gt;e&lt;/span&gt;&lt;span class=&#34;msupsub&#34;&gt;&lt;span class=&#34;vlist-t&#34;&gt;&lt;span class=&#34;vlist-r&#34;&gt;&lt;span class=&#34;vlist&#34; style=&#34;height:0.8746639999999999em;&#34;&gt;&lt;span style=&#34;top:-3.113em;margin-right:0.05em;&#34;&gt;&lt;span class=&#34;pstrut&#34; style=&#34;height:2.7em;&#34;&gt;&lt;/span&gt;&lt;span class=&#34;sizing reset-size6 size3 mtight&#34;&gt;&lt;span class=&#34;mord mtight&#34;&gt;&lt;span class=&#34;mord mtight&#34;&gt;2&lt;/span&gt;&lt;span class=&#34;mord mathnormal mtight&#34; style=&#34;margin-right:0.03588em;&#34;&gt;π&lt;/span&gt;&lt;span class=&#34;mord mathnormal mtight&#34;&gt;i&lt;/span&gt;&lt;span class=&#34;mord mathnormal mtight&#34;&gt;t&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;mord&#34;&gt;∣&lt;/span&gt;&lt;span class=&#34;mord mathnormal&#34;&gt;d&lt;/span&gt;&lt;span class=&#34;mord mathnormal&#34;&gt;t&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;其值表示的是在 &lt;span class=&#34;katex&#34;&gt;&lt;span class=&#34;katex-mathml&#34;&gt;&lt;math xmlns=&#34;http://www.w3.org/1998/Math/MathML&#34;&gt;&lt;semantics&gt;&lt;mrow&gt;&lt;msub&gt;&lt;mi&gt;t&lt;/mi&gt;&lt;mn&gt;1&lt;/mn&gt;&lt;/msub&gt;&lt;/mrow&gt;&lt;annotation encoding=&#34;application/x-tex&#34;&gt;t_1&lt;/annotation&gt;&lt;/semantics&gt;&lt;/math&gt;&lt;/span&gt;&lt;span class=&#34;katex-html&#34; aria-hidden=&#34;true&#34;&gt;&lt;span class=&#34;base&#34;&gt;&lt;span class=&#34;strut&#34; style=&#34;height:0.76508em;vertical-align:-0.15em;&#34;&gt;&lt;/span&gt;&lt;span class=&#34;mord&#34;&gt;&lt;span class=&#34;mord mathnormal&#34;&gt;t&lt;/span&gt;&lt;span class=&#34;msupsub&#34;&gt;&lt;span class=&#34;vlist-t vlist-t2&#34;&gt;&lt;span class=&#34;vlist-r&#34;&gt;&lt;span class=&#34;vlist&#34; style=&#34;height:0.30110799999999993em;&#34;&gt;&lt;span style=&#34;top:-2.5500000000000003em;margin-left:0em;margin-right:0.05em;&#34;&gt;&lt;span class=&#34;pstrut&#34; style=&#34;height:2.7em;&#34;&gt;&lt;/span&gt;&lt;span class=&#34;sizing reset-size6 size3 mtight&#34;&gt;&lt;span class=&#34;mord mtight&#34;&gt;1&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;vlist-s&#34;&gt;​&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;vlist-r&#34;&gt;&lt;span class=&#34;vlist&#34; style=&#34;height:0.15em;&#34;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt; 到 &lt;span class=&#34;katex&#34;&gt;&lt;span class=&#34;katex-mathml&#34;&gt;&lt;math xmlns=&#34;http://www.w3.org/1998/Math/MathML&#34;&gt;&lt;semantics&gt;&lt;mrow&gt;&lt;msub&gt;&lt;mi&gt;t&lt;/mi&gt;&lt;mn&gt;2&lt;/mn&gt;&lt;/msub&gt;&lt;/mrow&gt;&lt;annotation encoding=&#34;application/x-tex&#34;&gt;t_2&lt;/annotation&gt;&lt;/semantics&gt;&lt;/math&gt;&lt;/span&gt;&lt;span class=&#34;katex-html&#34; aria-hidden=&#34;true&#34;&gt;&lt;span class=&#34;base&#34;&gt;&lt;span class=&#34;strut&#34; style=&#34;height:0.76508em;vertical-align:-0.15em;&#34;&gt;&lt;/span&gt;&lt;span class=&#34;mord&#34;&gt;&lt;span class=&#34;mord mathnormal&#34;&gt;t&lt;/span&gt;&lt;span class=&#34;msupsub&#34;&gt;&lt;span class=&#34;vlist-t vlist-t2&#34;&gt;&lt;span class=&#34;vlist-r&#34;&gt;&lt;span class=&#34;vlist&#34; style=&#34;height:0.30110799999999993em;&#34;&gt;&lt;span style=&#34;top:-2.5500000000000003em;margin-left:0em;margin-right:0.05em;&#34;&gt;&lt;span class=&#34;pstrut&#34; style=&#34;height:2.7em;&#34;&gt;&lt;/span&gt;&lt;span class=&#34;sizing reset-size6 size3 mtight&#34;&gt;&lt;span class=&#34;mord mtight&#34;&gt;2&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;vlist-s&#34;&gt;​&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;vlist-r&#34;&gt;&lt;span class=&#34;vlist&#34; style=&#34;height:0.15em;&#34;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt; 时刻下，频率为 &lt;span class=&#34;katex&#34;&gt;&lt;span class=&#34;katex-mathml&#34;&gt;&lt;math xmlns=&#34;http://www.w3.org/1998/Math/MathML&#34;&gt;&lt;semantics&gt;&lt;mrow&gt;&lt;mi&gt;f&lt;/mi&gt;&lt;/mrow&gt;&lt;annotation encoding=&#34;application/x-tex&#34;&gt;f&lt;/annotation&gt;&lt;/semantics&gt;&lt;/math&gt;&lt;/span&gt;&lt;span class=&#34;katex-html&#34; aria-hidden=&#34;true&#34;&gt;&lt;span class=&#34;base&#34;&gt;&lt;span class=&#34;strut&#34; style=&#34;height:0.8888799999999999em;vertical-align:-0.19444em;&#34;&gt;&lt;/span&gt;&lt;span class=&#34;mord mathnormal&#34; style=&#34;margin-right:0.10764em;&#34;&gt;f&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt; 的声波的相对振幅。如果某一个频率的波的强度很大，那么值也会越大。&lt;/p&gt;
&lt;p&gt;看文字不如直接看 &lt;a href=&#34;https://www.youtube.com/watch?v=spUNpyF58BY&#34;&gt;@3b1b 的视频&lt;/a&gt;。&lt;/p&gt;
&lt;h2 id=&#34;总结&#34;&gt;&lt;a class=&#34;markdownIt-Anchor&#34; href=&#34;#总结&#34;&gt;&lt;/a&gt; 总结&lt;/h2&gt;
&lt;p&gt;本文介绍了 &lt;code&gt;wav&lt;/code&gt; 的编码结构和傅立叶变换在音频可视化中的应用，并手搓了一个 TUI 的支持频谱可视化的音频播放器。最后，我们通过直观的方式理解了傅立叶变换的基本原理。&lt;/p&gt;
&lt;p&gt;代码开源在了 &lt;a href=&#34;https://github.com/soulter/wav-spectrum-visualizer&#34;&gt;GitHub&lt;/a&gt;，可以去玩玩。&lt;/p&gt;
&lt;h2 id=&#34;参考文献&#34;&gt;&lt;a class=&#34;markdownIt-Anchor&#34; href=&#34;#参考文献&#34;&gt;&lt;/a&gt; 参考文献&lt;/h2&gt;
&lt;p&gt;[1] WAVE File Format. &lt;a href=&#34;https://web.archive.org/web/20140221054954/http://home.roadrunner.com/~jgglatt/tech/wave.htm&#34;&gt;https://web.archive.org/web/20140221054954/http://home.roadrunner.com/~jgglatt/tech/wave.htm&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;[2] But what is the Fourier Transform? A visual introduction. &lt;a href=&#34;https://www.youtube.com/watch?v=spUNpyF58BY&#34;&gt;https://www.youtube.com/watch?v=spUNpyF58BY&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;[3] 为什么要进行傅立叶变换. &lt;a href=&#34;https://ibillxia.github.io/blog/2013/04/04/why-do-Fourier-transformation/&#34;&gt;https://ibillxia.github.io/blog/2013/04/04/why-do-Fourier-transformation/&lt;/a&gt;&lt;/p&gt;
</content>
        <category term="2024" />
        <category term="音频编码" />
        <updated>2024-09-09T23:53:00.000Z</updated>
    </entry>
    <entry>
        <id>https://blog.soulter.top/posts/arch-linux-hyprland.html</id>
        <title>ArchLinux + Hyprland 体验报告</title>
        <link rel="alternate" href="https://blog.soulter.top/posts/arch-linux-hyprland.html"/>
        <content type="html">&lt;p&gt;起源于在 Bilibili 上看到的一个&lt;a href=&#34;https://www.bilibili.com/video/BV11i4y1a7US&#34;&gt;关于 Hyprland 使用的视频&lt;/a&gt;，作为 Windows、MacOS、Gnome 桌面用惯了的人，在看到视频中展示的新奇的 “平铺式” 桌面交互体验、大量的动画效果时，我对它产生了浓厚的兴趣，于是打算动手装一个 Hyprland 桌面。&lt;/p&gt;
&lt;p&gt;Hyprland 官方声明其对 ArchLinux、NixOS 的支持最好。久仰 ArchLinux 大名，我对它的部署充满了好奇心（雾），所以又先安装了 ArchLinux。&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://cf.s3.soulter.top/arch/desktop.png#full&#34; alt=&#34;&#34; title=&#34;My desktop&#34; /&gt;&lt;/p&gt;
&lt;h2 id=&#34;0x01-在-linux-下制作-pe-引导盘&#34;&gt;&lt;a class=&#34;markdownIt-Anchor&#34; href=&#34;#0x01-在-linux-下制作-pe-引导盘&#34;&gt;&lt;/a&gt; 0x01. 在 Linux 下制作 PE 引导盘&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;使用 Ubuntu 系发行版的用户请自行在前面加 sudo。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;插入 U 盘。&lt;/p&gt;
&lt;figure class=&#34;highlight bash&#34;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&#34;gutter&#34;&gt;&lt;pre&gt;&lt;span class=&#34;line&#34;&gt;1&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&#34;code&#34;&gt;&lt;pre&gt;&lt;span class=&#34;line&#34;&gt;fdisk -l&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/figure&gt;
&lt;p&gt;查看 U 盘对应的设备名称：&lt;/p&gt;
&lt;figure class=&#34;highlight bash&#34;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&#34;gutter&#34;&gt;&lt;pre&gt;&lt;span class=&#34;line&#34;&gt;1&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;2&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;3&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;4&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;5&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;6&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;7&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&#34;code&#34;&gt;&lt;pre&gt;&lt;span class=&#34;line&#34;&gt;Disk /dev/sdd：57.3 GiB，61530439680 字节，120176640 个扇区&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;Disk model:  SanDisk 3.2Gen1&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;单元：扇区 / 1 * 512 = 512 字节&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;扇区大小(逻辑/物理)：512 字节 / 512 字节&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;I/O 大小(最小/最佳)：512 字节 / 512 字节&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;磁盘标签类型：dos&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;磁盘标识符：0x00000000 &lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/figure&gt;
&lt;blockquote&gt;
&lt;p&gt;注：磁盘标识符不应该为 &lt;code&gt;0x00000000&lt;/code&gt;，我后来才发现我的 U 盘坏了…&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;找到挂载点：&lt;/p&gt;
&lt;figure class=&#34;highlight bash&#34;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&#34;gutter&#34;&gt;&lt;pre&gt;&lt;span class=&#34;line&#34;&gt;1&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&#34;code&#34;&gt;&lt;pre&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;built_in&#34;&gt;df&lt;/span&gt; -h&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/figure&gt;
&lt;p&gt;卸载 U 盘：&lt;/p&gt;
&lt;figure class=&#34;highlight bash&#34;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&#34;gutter&#34;&gt;&lt;pre&gt;&lt;span class=&#34;line&#34;&gt;1&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&#34;code&#34;&gt;&lt;pre&gt;&lt;span class=&#34;line&#34;&gt;umount /dev/&amp;lt;设备名称&amp;gt;&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/figure&gt;
&lt;p&gt;格式化 U 盘：&lt;/p&gt;
&lt;figure class=&#34;highlight bash&#34;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&#34;gutter&#34;&gt;&lt;pre&gt;&lt;span class=&#34;line&#34;&gt;1&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&#34;code&#34;&gt;&lt;pre&gt;&lt;span class=&#34;line&#34;&gt;mkfs.fat /dev/&amp;lt;设备名称&amp;gt; -I&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/figure&gt;
&lt;p&gt;然后前往 ArchLinux 下载对应的 .ISO 镜像文件。&lt;/p&gt;
&lt;figure class=&#34;highlight bash&#34;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&#34;gutter&#34;&gt;&lt;pre&gt;&lt;span class=&#34;line&#34;&gt;1&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&#34;code&#34;&gt;&lt;pre&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;built_in&#34;&gt;sudo&lt;/span&gt; &lt;span class=&#34;built_in&#34;&gt;dd&lt;/span&gt; &lt;span class=&#34;keyword&#34;&gt;if&lt;/span&gt;=archlinux-2024.08.01-x86_64.iso of=/dev/&amp;lt;设备名称&amp;gt;&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/figure&gt;
&lt;p&gt;写入 ISO 镜像文件到 U 盘，最终显示如下：&lt;/p&gt;
&lt;figure class=&#34;highlight bash&#34;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&#34;gutter&#34;&gt;&lt;pre&gt;&lt;span class=&#34;line&#34;&gt;1&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;2&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;3&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;4&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&#34;code&#34;&gt;&lt;pre&gt;&lt;span class=&#34;line&#34;&gt;(base) soulter@806dev:~/下载$ &lt;span class=&#34;built_in&#34;&gt;sudo&lt;/span&gt; &lt;span class=&#34;built_in&#34;&gt;dd&lt;/span&gt; &lt;span class=&#34;keyword&#34;&gt;if&lt;/span&gt;=archlinux-2024.08.01-x86_64.iso of=/dev/sdf&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;记录了2310208+0 的读入&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;记录了2310208+0 的写出&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;1182826496字节（1.2 GB，1.1 GiB）已复制，231.334 s，5.1 MB/s&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/figure&gt;
&lt;h2 id=&#34;0x02-boot-archlinux&#34;&gt;&lt;a class=&#34;markdownIt-Anchor&#34; href=&#34;#0x02-boot-archlinux&#34;&gt;&lt;/a&gt; 0x02. Boot ArchLinux&lt;/h2&gt;
&lt;p&gt;进入 BIOS，找到启动顺序设置，然后将 U 盘放在第一位即可。BIOS 会按照启动顺序依次检查每个设备是否有引导记录，如果有则启动。&lt;/p&gt;
&lt;h2 id=&#34;0x03-install-archlinux&#34;&gt;&lt;a class=&#34;markdownIt-Anchor&#34; href=&#34;#0x03-install-archlinux&#34;&gt;&lt;/a&gt; 0x03. Install ArchLinux&lt;/h2&gt;
&lt;p&gt;参考 ArchLinux Wiki。主要分为以下几步：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;注：请参考 ArchLinux Wiki，本文仅提供一个简单的流程。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;ol&gt;
&lt;li&gt;检查网络联通性（更新 pacman 源需要互联网接入）
&lt;ol&gt;
&lt;li&gt;&lt;code&gt;ping blog.lwl.lol&lt;/code&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;li&gt;设置时间、时区
&lt;ol&gt;
&lt;li&gt;&lt;code&gt;timedatectl set-timezone Asia/Shanghai&lt;/code&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;li&gt;分区（EFI、bootable）
&lt;ol&gt;
&lt;li&gt;&lt;code&gt;fdisk -l&lt;/code&gt; 查看要分区的 disk。&lt;/li&gt;
&lt;li&gt;使用 &lt;code&gt;fdisk /dev/sdX&lt;/code&gt; 进入 disk 来分区&lt;/li&gt;
&lt;li&gt;分别创建两个分区：EFI, Linux FileSystem。EFI 分区大小 &lt;code&gt;512M&lt;/code&gt;，Linux FileSystem 分区大小为剩余空间。&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;li&gt;给创建好的两个分区挂载文件系统
&lt;ol&gt;
&lt;li&gt;&lt;code&gt;mkfs.fat -F32 /dev/sdX1&lt;/code&gt; （EFI 分区）&lt;/li&gt;
&lt;li&gt;&lt;code&gt;mkfs.ext4 /dev/sdX2&lt;/code&gt; （Linux FileSystem 分区）&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;li&gt;pacman 源更新
&lt;ol&gt;
&lt;li&gt;&lt;code&gt;pacman -Syy&lt;/code&gt; 更新 pacman 源&lt;/li&gt;
&lt;li&gt;&lt;code&gt;pacman -S reflector&lt;/code&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;li&gt;使用 &lt;code&gt;pacstrap&lt;/code&gt; 安装 ArchLinux。
&lt;ol&gt;
&lt;li&gt;&lt;code&gt;mount /dev/sdX2 /mnt&lt;/code&gt; 此步骤会将 Linux FileSystem 分区挂载到 /mnt 目录下。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;pacstrap /mnt base linux linux-firmware nano&lt;/code&gt; 此步骤会安装基本的 ArchLinux 系统。&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;li&gt;使用 &lt;code&gt;arch-chroot /mnt&lt;/code&gt; 进入到安装好 Arch 的 Disk。
&lt;ol&gt;
&lt;li&gt;更新 &lt;code&gt;/etc/hostname&lt;/code&gt;, &lt;code&gt;/etc/hosts&lt;/code&gt; 设置主机名和 hosts。&lt;/li&gt;
&lt;li&gt;安装/启动 dhcp 服务。&lt;code&gt;yay -S dhcpcd &amp;amp;&amp;amp; systemctl enable dhcpcd&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;passwd&lt;/code&gt; 设置密码&lt;/li&gt;
&lt;li&gt;安装 Bootloader（GRUB）
&lt;ol&gt;
&lt;li&gt;在 non-UEFI 上和在 UEFI 设备上安装方式不同。&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;li&gt;&lt;code&gt;grub-mkconfig -o /boot/grub/grub.cfg&lt;/code&gt; 生成 grub 配置文件。&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;li&gt;拔掉 U 盘，reboot～&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;ArchLinux 默认是不带桌面环境的，因此重启之后你会发现只有一个 tty，如果想使用桌面环境，需要自行安装。&lt;/p&gt;
&lt;h2 id=&#34;0x04-安装和使用-hyprland&#34;&gt;&lt;a class=&#34;markdownIt-Anchor&#34; href=&#34;#0x04-安装和使用-hyprland&#34;&gt;&lt;/a&gt; 0x04. 安装和使用 Hyprland&lt;/h2&gt;
&lt;p&gt;提到了 Hyprland，就不得不提一下 Wayland。Wayland 是一个计算机图形显示协议，旨在替代 X 协议。由于使用 Wayland 协议的显示服务器也可作为混成窗口管理器，因此被称为混成器。Wayland 本身并不提供图形环境，因此还需要安装混成器。混成器有很多，大致可以分为平铺式和堆叠式两类。Hyprland 就是一个独立的使用 C++ 编写的平铺式 Wayland 混成器。&lt;/p&gt;
&lt;p&gt;如果你想&lt;strong&gt;自己设计桌面&lt;/strong&gt;，可以直接使用 &lt;code&gt;yay&lt;/code&gt; 安装 hyprland。&lt;/p&gt;
&lt;figure class=&#34;highlight bash&#34;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&#34;gutter&#34;&gt;&lt;pre&gt;&lt;span class=&#34;line&#34;&gt;1&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&#34;code&#34;&gt;&lt;pre&gt;&lt;span class=&#34;line&#34;&gt;yay -S hyprland&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/figure&gt;
&lt;p&gt;安装完成之后，输入 &lt;code&gt;Hyprland&lt;/code&gt; 即可进入 hyprland 桌面。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;注意：请不要使用 &lt;code&gt;root&lt;/code&gt; 账户或者 &lt;code&gt;sudo&lt;/code&gt; 启动 hyprland。否则，你需要加一个参数: &lt;code&gt;--i-am-really-stupid&lt;/code&gt;。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;网络上有很多提供好的 hyprland 设计配置称为 &lt;code&gt;hyprland dotfiles&lt;/code&gt;，你可以直接使用他们的配置。&lt;/p&gt;
&lt;p&gt;一开始我打算自己设计桌面，但是惊讶地发现发现 hyprland 自由度是在太高了，以至于在安装成功之后打开的图形界面是黑屏——壁纸、状态栏等都需要自己安装相应的工具（hyprpaper, waybar 等等）。捣鼓了一会之后觉得太费时（最主要还是效果不好XD），所以最终我还是使用了 &lt;a href=&#34;https://github.com/end-4/dots-hyprland&#34;&gt;end-4/dots-hyprland&lt;/a&gt; 的配置。这也是文章一开始提到的 Bilibili 视频中使用的配置。&lt;/p&gt;
&lt;p&gt;下面针对这个配置后的 ArchLinux + Hyprland 进行一些简单的个人体验。&lt;/p&gt;
&lt;h2 id=&#34;0x05-pacman-yay-are-all-you-need&#34;&gt;&lt;a class=&#34;markdownIt-Anchor&#34; href=&#34;#0x05-pacman-yay-are-all-you-need&#34;&gt;&lt;/a&gt; 0x05. PACMAN + YAY are all you need&lt;/h2&gt;
&lt;p&gt;ArchLinux 的自带软件包管理器是 &lt;code&gt;pacman&lt;/code&gt;。事实证明，simple is the best。pacman 的本地数据库甚至只是包名的目录树，打包脚本也是 bash script。简单的包管理造就了方便的软件包生态。不管软件包来自官方 Arch 库还是用户自己创建，pacman 都能方便地管理。&lt;/p&gt;
&lt;p&gt;ArchLinux 的 AUR（Arch User Repository）是一个非常强大的软件源，它是为用户而建、由用户主导的，每个人都可以往里面发自己创建的软件包。从这里就可以看出，AUR 的包生态是非常丰富的。大到常用的软件 VSCode、IDEA、QQ，小到纳西妲鼠标光标、&lt;a href=&#34;https://aur.archlinux.org/packages/i-use-arch-btw-git&#34;&gt;i-use-arch-btw 编程语言&lt;/a&gt;，都可以在 AUR 中找到。&lt;/p&gt;
&lt;p&gt;如果想搜索软件包，只需要在 &lt;a href=&#34;https://aur.archlinux.org/&#34;&gt;https://aur.archlinux.org/&lt;/a&gt; 搜索即可。或者 &lt;code&gt;yay -Ssq &amp;lt;keyword&amp;gt;&lt;/code&gt;，也会列出相关的软件包。&lt;/p&gt;
&lt;p&gt;可以说，要是在 ArchLinux 上找不到你想要的软件包，那么其他发行版上就更不可能有了。&lt;/p&gt;
&lt;p&gt;这意味着，你可以直接以一行安装指令安装你想要的软件，而不需要查看软件包官网上冗长的安装和编译教程（Debian！），让你尝到先苦后甜的滋味 😃 。&lt;/p&gt;
&lt;p&gt;需要帮助？大多数软件包都会提供一个 ArchWiki 页面，请自己查阅 ArchWiki，或者在 Arch Community 上提问。&lt;/p&gt;
&lt;h2 id=&#34;0x06-no-鼠标-anymore&#34;&gt;&lt;a class=&#34;markdownIt-Anchor&#34; href=&#34;#0x06-no-鼠标-anymore&#34;&gt;&lt;/a&gt; 0x06. No 鼠标 Anymore！&lt;/h2&gt;
&lt;p&gt;“平铺式”，顾名思义，就是将窗口平铺在桌面上而不重叠，这种方式的好处是可以最大化地利用屏幕空间，在需要同时使用多个程序的时候非常方便，免去了鼠标频繁移动到任务栏和点击的麻烦。&lt;/p&gt;
&lt;p&gt;Hyprland 中，触发指令通常是 &lt;code&gt;SUPER&lt;/code&gt; 键 + XXX 的形式，例如 &lt;code&gt;SUPER + T&lt;/code&gt; 打开终端。这里的 &lt;code&gt;SUPER&lt;/code&gt; 键默认是 &lt;code&gt;Windows&lt;/code&gt; 徽标键。当然了，这是可以配置的。&lt;/p&gt;
&lt;p&gt;使用 &lt;code&gt;SUPER + Ctrl + /&lt;/code&gt; 会打开类似 MacOS 的聚焦搜索，你可以通过键盘输入来搜索程序，然后回车打开。打开的窗口的位置取决于光标的位置。&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://cf.s3.soulter.top/arch/focus_search.png&#34; alt=&#34;&#34; /&gt;&lt;/p&gt;
&lt;p&gt;在 Hyprland 中，光标下的窗口会被自动激活，也就是说，当鼠标移动到某个窗口上时，这个窗口就会被激活，而不需要点击。是不是这样还不够方便？你可以直接使用 &lt;code&gt;SUPER + 方向键&lt;/code&gt; 将光标直接移动到相邻的窗口中央。&lt;/p&gt;
&lt;p&gt;如何调整窗口呢？当鼠标移动到一个窗口内时，使用 &lt;code&gt;SUPER+;&lt;/code&gt; 即可上下/左右调整该窗口的尺寸，使用 &lt;code&gt;SUPER+Shift+方向键&lt;/code&gt; 可以将窗口和相邻的窗口交换位置。&lt;/p&gt;
&lt;p&gt;悬浮一个窗口&lt;strong&gt;也是受支持的&lt;/strong&gt;。只需要按下 &lt;code&gt;SUPER + Alt + SPACE&lt;/code&gt; 即可。你可以通过鼠标拖动窗口来调整窗口的大小，按下 &lt;code&gt;SUPER&lt;/code&gt; 和鼠标左键可以拖动窗口。&lt;/p&gt;
&lt;p&gt;此外，和 Windows 和 MacOS 类似，Hyprland 也支持多个桌面，可以通过 &lt;code&gt;SUPER + 1/2/3/4/5/6/7/8/9&lt;/code&gt; 来切换。&lt;/p&gt;
&lt;p&gt;这么多的快捷键使得鼠标的使用频率大大降低，右手可以更专注于键盘上。在一些场景下，比如码字+查资料，甚至可以完全不用鼠标来操作电脑。&lt;/p&gt;
&lt;p&gt;对于我来说，我使用这台安装了 Hyprland 的电脑一般就是用来写写代码，浏览网页，因此这种设计非常适合我。&lt;/p&gt;
&lt;h2 id=&#34;0x07-高自由度的配置&#34;&gt;&lt;a class=&#34;markdownIt-Anchor&#34; href=&#34;#0x07-高自由度的配置&#34;&gt;&lt;/a&gt; 0x07. 高自由度的配置&lt;/h2&gt;
&lt;p&gt;Hyprland 和其他工具的配置文件都统一到了 &lt;code&gt;~/.config/&lt;/code&gt; 目录下，并且可配置性非常强，可以很方便地个性化你的桌面。例如按键绑定、壁纸、圆角、窗口透明度、模糊效果、动画效果等等。&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://cf.s3.soulter.top/arch/config.png&#34; alt=&#34;alt text&#34; /&gt;&lt;/p&gt;
&lt;p&gt;我们 btw 群体安装 Arch 的一大乐趣就是自己捣鼓桌面（也没人用它来做服务器吧），因此可以在网上看到各式各样的桌面样式。&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://cf.s3.soulter.top/arch/image-1.png&#34; alt=&#34;&#34; /&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://cf.s3.soulter.top/arch/image-2.png&#34; alt=&#34;&#34; title=&#34;Reddit: u/PahasaraDv&#34; /&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://cf.s3.soulter.top/arch/image-3.png&#34; alt=&#34;&#34; title=&#34;Reddit: u/infarni&#34; /&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://cf.s3.soulter.top/arch/image-4.png&#34; alt=&#34;&#34; title=&#34;Reddit: u/mbregg&#34; /&gt;&lt;/p&gt;
&lt;h2 id=&#34;0x08-tui-️&#34;&gt;&lt;a class=&#34;markdownIt-Anchor&#34; href=&#34;#0x08-tui-️&#34;&gt;&lt;/a&gt; 0x08. TUI ❤️&lt;/h2&gt;
&lt;p&gt;把这个有点说成是 Hyprland 的特色似乎有点站不住脚，因为 TUI 只是一个和 GUI 相对的设计理念，基于它们的软件可以运行于任何桌面环境，但是我觉得 Hyprland 为 TUI 软件提供了一个很好的展示平台，极大提高了他们的使用频率。TUI 软件的界面一般只包含有效的文本信息（当然，一些协议像是 iterm2 协议也支持在终端中显示图像）而没有多余的装饰。对于那些对图形界面审美疲劳的人来说，TUI 软件是一个很好的选择。&lt;/p&gt;
&lt;p&gt;我安装了 musicfox + ncmpcpp + mpd 来播放网易云音乐。musicfox 是基于 TUI 的第三方网易云音乐客户端，支持登录网易云账号、歌词显示、歌单播放。ncmpcpp 是一个 mpd 客户端，我主要用它显示音乐波形，mpd 是一个音乐播放器守护进程。简单配置之后，效果极其优雅 🤤。&lt;/p&gt;
&lt;p&gt;还有很多 TUI 软件，比如 btop，一个类似 htop 的系统监视器，可以查看 CPU、内存、磁盘、网络等信息。&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://cf.s3.soulter.top/arch/desktop.png&#34; alt=&#34;&#34; /&gt;&lt;/p&gt;
&lt;p&gt;还有 neofetch (x)&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://cf.s3.soulter.top/arch/acd989c9d4f2a4e58c0342ddf17e4657.png&#34; alt=&#34;&#34; /&gt;&lt;/p&gt;
&lt;h2 id=&#34;0x09-支持远程桌面&#34;&gt;&lt;a class=&#34;markdownIt-Anchor&#34; href=&#34;#0x09-支持远程桌面&#34;&gt;&lt;/a&gt; 0x09. 支持远程桌面&lt;/h2&gt;
&lt;p&gt;同 X 协议一样，Wayland 协议也支持众多远程桌面协议，如 VNC、RDP。我尝试了使用 &lt;code&gt;wayvnc&lt;/code&gt; 来实现远程桌面。&lt;/p&gt;
&lt;p&gt;首先安装 &lt;code&gt;wayvnc&lt;/code&gt;：&lt;/p&gt;
&lt;figure class=&#34;highlight bash&#34;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&#34;gutter&#34;&gt;&lt;pre&gt;&lt;span class=&#34;line&#34;&gt;1&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&#34;code&#34;&gt;&lt;pre&gt;&lt;span class=&#34;line&#34;&gt;yay -S wayvnc&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/figure&gt;
&lt;p&gt;然后使用 &lt;code&gt;hyprctl monitors&lt;/code&gt; 查看你的显示器信息，将 &lt;code&gt;Monitor&lt;/code&gt; 后的 ID 记录下来。&lt;/p&gt;
&lt;p&gt;然后新开一个 terminal，输入 &lt;code&gt;wayvnc 0.0.0.0 5900&lt;/code&gt; 来启动 VNC 服务，此时 wayvnc 会堵塞当前 terminal。需要再新开一个 terminal，输入 &lt;code&gt;wayvncctl output-set &amp;lt;Monitor ID&amp;gt; 0&lt;/code&gt; 来将 VNC 服务绑定到指定的显示器上。&lt;/p&gt;
&lt;p&gt;Wayland 是支持多 monitor 的，如果你不想远程控制现有的桌面而想单开一个桌面，你可以创建一个无头 monitor：&lt;/p&gt;
&lt;figure class=&#34;highlight bash&#34;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&#34;gutter&#34;&gt;&lt;pre&gt;&lt;span class=&#34;line&#34;&gt;1&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&#34;code&#34;&gt;&lt;pre&gt;&lt;span class=&#34;line&#34;&gt;hyprctl output create headless &lt;span class=&#34;built_in&#34;&gt;test&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/figure&gt;
&lt;p&gt;然后前往 hyprland 的 config 中设置这个 monitor 的分辨率、位置等信息，比如&lt;/p&gt;
&lt;figure class=&#34;highlight bash&#34;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&#34;gutter&#34;&gt;&lt;pre&gt;&lt;span class=&#34;line&#34;&gt;1&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&#34;code&#34;&gt;&lt;pre&gt;&lt;span class=&#34;line&#34;&gt;monitor=&lt;span class=&#34;built_in&#34;&gt;test&lt;/span&gt;,1920x1080,auto,1&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/figure&gt;
&lt;p&gt;然后将 VNC 服务绑定到这个 monitor 上，再启动 VNC 服务即可。（顺序不能错）&lt;/p&gt;
&lt;h2 id=&#34;0x0a-缺点&#34;&gt;&lt;a class=&#34;markdownIt-Anchor&#34; href=&#34;#0x0a-缺点&#34;&gt;&lt;/a&gt; 0x0A. 缺点？&lt;/h2&gt;
&lt;p&gt;平铺式窗口布局确实有很多好处，但是这仅建立在你有一个大屏幕的基础上。如果屏幕太小，那么平铺式布局可能会显得有些拥挤。&lt;/p&gt;
&lt;p&gt;此外，在我的使用过程中，发现在系统 Suspend 之后再重新唤醒，会导致花屏的情况，使得我不得不直接强行重启电脑，非常不方便。当然了，可以通过修改 &lt;code&gt;.config/hypr/hypridle.conf&lt;/code&gt; 来禁用一段时间后 Suspend 的功能。&lt;/p&gt;
&lt;h2 id=&#34;0x0b-总结&#34;&gt;&lt;a class=&#34;markdownIt-Anchor&#34; href=&#34;#0x0b-总结&#34;&gt;&lt;/a&gt; 0x0B. 总结&lt;/h2&gt;
&lt;p&gt;i use arch btw.&lt;/p&gt;
&lt;p&gt;这句话已经成为著名的 Linux 圈的梗了，甚至官方也在玩梗（x）：&lt;a href=&#34;https://wiki.archlinux.org/title/Frequently_asked_questions&#34;&gt;https://wiki.archlinux.org/title/Frequently_asked_questions&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://cf.s3.soulter.top/arch/2cc557d21520be9762694093b0b8972f.png&#34; alt=&#34;&#34; /&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://cf.s3.soulter.top/arch/d7e63fc3b76cd1691f583ac51dae58bc_720.png&#34; alt=&#34;&#34; /&gt;&lt;/p&gt;
</content>
        <category term="2024" />
        <category term="ArchLinux" />
        <category term="Hyprland" />
        <updated>2024-08-30T11:48:00.000Z</updated>
    </entry>
    <entry>
        <id>https://blog.soulter.top/posts/2024-8-monthly-record.html</id>
        <title>8 月 / 荒原与星空</title>
        <link rel="alternate" href="https://blog.soulter.top/posts/2024-8-monthly-record.html"/>
        <content type="html">&lt;div class=&#34;hbe hbe-container&#34; id=&#34;hexo-blog-encrypt&#34; data-wpm=&#34;Oh, this is an invalid password. Check and try again, please.&#34; data-whm=&#34;OOPS, these decrypted content may changed, but you can still have a look.&#34;&gt;
  &lt;script id=&#34;hbeData&#34; type=&#34;hbeData&#34; data-hmacdigest=&#34;37b0ff838b17b198f496566377de26f907da85d9085e8ece9eaaaf7554fb12c0&#34;&gt;57a93903e798fb80b7d87b9f0d7fccf3d782b9c740a0b363a91395603a62cf584ed5aca24fb6af2b7e1eb9069fae3fcb0c1c8a77b0697400a5ad27cc796a0c198270665e3c021b70d51e3ef8bb7f00fe10920fc72475c09c733c1af03241b0adb62ed5767bc16a395377f896a750ee4c3c6f48f2a906518df8ec0bc16ffad3a8d3a42c8be1485b94882012ccbc48d030d7babc716626fbc24f924ce5c48fff6e7c185bb06c0ff9df754a55693ce41afe60bb174c52b2af0a5cba25f451744397391fad9787c59c9531d3a03ed5b9935ae6304c3cfcb2da4dfd82e2250dea02d0c3c952978d81f72121535334178f27d76bec54383063dc2c4eaac0971479f6f571790c5d4e874ce443f4d66f196a8f980c9020caa906e82347645098a724828ff2d00e68d27c21fb2227f4877077fcc0145cfec8ce33028e9d2c6f9331248bbb1e748828c92f9206e31e710980fc9d3679205e3645342008d02aa3fdbfa84686065be0480fc237e67eaf6290bc2f06958d11447442c9b06c5c5c4fad0985fae42aee448ee93479acc14aac8cc2cafd2005630e0dfb98ea32b372df5e14f80f2194581c8629aa28e72949b4695e8589a35da93ecf2bc176b832b8bdeafca11b71ffa784a280c1e54695262b3dfcc7f5b941140780d504c5d476080b2f087227e2dbd41bedad9159cf3a11fb0dc7f13581e939c9e29fa391219f5f996b8cf3c870c4de36e424a45f991919420cba4a64c1361d2f84e7db31cad806d408fb947dd7f5fb956cbb36e251b61eec834fea5e13ca726e260021f8902a8305ccbcaf15721e9f518af91dadb4015cf78ad416896de0df01697074b585d389e58d038ec4399b88d6251f2fd234f1f6e1bf304f946f72ba7baad63f17f1e2096c552cd6aec126d4e520dd20ffeb3783a4db8fc1d423b4615dbfa879b3d1c4abd6082260b8159751eb6b30b4d4534828a80ed841948e131533458ad14d980c42104f78e35ac81e094a2b6b3cc7e789b7a2b2352507426b3f214e55eac4b01bbd1cc8180415a1693a27e089413c4a4dd48b6cc12accb88da4eb35469144df9310ff2e9ca1b1d16b59214a1ad3bc0532a6a17a1d9c7ffdf5fc44b3955e9902a0f8202322a9943c7978f3e20c0e239da054316933abe8252b30abd9ce6113a0ad69f4c395fa26fb230b3d89c373001ea085d219fb71386c7da70f6b67a57f46a11e47eef0e48a61b4ca66e88d06dd3d8caa3a1a68c0f091612fa99e4bd3930dfa6de7599f6a3fe2b42c5dd070a4b45c93ba9dc9ec4a85679a14450850de81b2db128ffb96aff5d2549aacac299e040b42c4ef7b29d3e6b101b2be7423423883d3e31f039b968a1d834b852a985e016160e33a495f49f0123aa8cd2fd375b78ec706c3e5963d53682efa5a72752168f5f576b5439ca888761c6b469617908effc1aa9a1502b6f03192de5265a0930cec4d3c7cfa71425a0fb15b1680c03b4def41bc6224b2e31e9e793941ea86f90fff745ff1ae35eca73b2220c2442ec140991c23a09cef2adad94941b08efab790f4098bfd858abaf1feff0ed23f0c7c74076b1cea442049de8f9af4918f9ab70b4da93207fdd7bccba2cfccba06f1d4f2b084840a11b786fab4c68022d73246af7a1fa3048b7d43636cb39541d621620f31f3f0aa205f6a60d2186827e23414d8a4821835aa1d8d118286d893981e6af35a260ac63b57005497baa9047e760b88d6f1aaf461109f75488742882b6cda71f2b641af7af6c37c53ef20e605dbe125ff07249efe90246d4e8a1983890729c364a8aaec18d6c9370fde30b77175870aaf6cd866ffa3d90f6731d4a1e5d132951105e9dd61e35f619d207f60a788ba5afa853512d7445838040beeacb80e11de133042ed85c13826853f6d2b0a22fc3a0105e250c258721326c12cc313eb87e9ec5f1b640747285254abdb1138469872cd5292eb7137841b58e3c79650344d7ff7607950d99e0d3db47fb3df700b90002b1a02057de8d2f2707847fb825d75d6bf460c38dc9b5c2325ce0c0840428e223beb7455e6910fcf69faee8efc24d374ec08cc3202282f356fca8952f3b4fa68ce6011f0794f016edc7f625ff1cbd439774bce414e0bc96651b876d557d8857bce1a16eb3b7ac8097bcaef18dae04c60e71dc8aab00fa411a63d71568f4f53fb18f0a08b8eca8e4d8734a2ce5fad36120aea595a3b3b750910d8defb35c91b221a4a0851341547ca1e9cc450d4837e7173cb73f9cf8d5bf50d8ec07624446eed6da248936abbd2f3ce0a24e94c0c85aef43d717057468ca8b34529cf971a97c45d838c7516843e81451039b1ed138256701efd62ea3c99aee7857eeb09c13a6e877945429e5e6f2ee575a224cba9a04885107c1701f3e00427bad7f1158237e4f7e989fa893113534305e92ddf01c22ad44e150e4f9337fe35ebd6345050278a22ce50495e26c58dd54de20570b39eb1704397afe42bfb471cb5ce0c1795133400d0433a9d5e12512f8647cd2ce3b34088f5679410d1e70880ec6cc06c84d755fffdc8c8f21f7af951cb21f58acc462300d5854c79470caf07ba2db5d8c69572e7b4e331789ac6570e9d89b2d0c7614ca68f15f30e303525ee41258db93d787fa1cf4c4eb3d6b59ade1bcd9817dd4184ac7d6f9bbaf30d01d4cb0921b1068cbf594a2ba3114c70f0eda9febf6e1d191b0b03eb1b9fe52a458b32734179dfbccba5b297fdbf35ea4350e9c6154fae611ead5f181fcb6dda2f1074c336b2b86ca00f8e95cd75068c9f91126c4af0fd80472eb9d8b8c095b1917c1341bf5b2de70a8b93e79106ace489204639667c7fc0625eaf70a8938572aec3a01e2df143f1db1bfe32fda32cb364998448fb202a99a4bb5a83297dcd61ddb9aa3c600736311c293a1930ed957f1fb1550f5122a5965f44131709bae06ad0fb5c3927dc39b3faafa783b8ab85158a44eff484ec8daac24ffc384b23d78aa2b8dfd9b522e93687b9f81f9797372c967a953b742d6dd67d6d1febe703401b2fa09f7d0d57fbfd41bd8debbcd0977aeb0d25e6855d3352dbb645c1d9b3eea59c583ba4e372f9caf4bcaf3c816c4faf66c75cd9b9b66b2c9da2c6ce973921a94c427a107813d4ab7c2db56a5200ed2c21e3dde63237a4eef3439271f72d40c091415cb768a16879ed59f651157332de68256cbdab59b7def5d1cc8689e5b0160eb5f24d619fd4640b5c73ae90988b2f91990bb7e63fe0c013c9627287e94fc0f81ae4e4016fcf63b6107aecca4c8070ba8633fbab8d85346e4dd1afc8d122db2eeb436164612c67154a7109e48f04e9268abf09641ff3e39324aeb87c5eb7e3d6a3e91c7fcebe934a8ea7f5bd2dd2e64adbd83c08d3c2f89b1e802fb476dae47fa67ada7cdd987386ff03f3703d53f1c58e312d150b7beb3fd60e78af14243200621046cb6b7c0978863e89eb2585251fd7ca75d762e6bb9a0c2fecf37e06da20f787abb7626c3fd119a89e0d1bfa281d2526767bd8d60dfc6753ce979a3eef3094b9793291e72d711bf6a19dd2781c48ef7b973af43ac966f5d7e106edbdd6760337e44ac3de8c78a5e25ef299c0d956cc0f7e8fe946751a643f949b7dc8c25a5979ccb841e8cbfb42fc44523baef211d5d6063a26304284d0fea6d2c753c53c82c38498a27c498f06d6562f68e66a3d41040fc72f838e6c6990a7b87fc08fb0c7c15e5ae1f1d2594e63da16af2bfef51e7248ab22f27ac6c9cf90feb990696a6b2abd324004e73124573640e2af13523fcf8054d312e50b91c3c60af443287a671634a4dbabcaaf928240a537ba219569b60d083d3ff9a3212940e0d616f30eeb9cd31652e13beae7c4f29878a188a51521b32690cddb88ff9ae9afd3f7f5e1d23232ec3f8c2ed35938d5e030f0271540383e80f12f9469195bcb5e0652c55460c1bf71ae685a43fbbf94a86b1d065e3048e91467211fadc27714f8f53d52a3505e4f91e94e4958a59eb02d4e689c5c47ad76551a79fec58247cba23b2e745060ea5e6fc2037934e9b9dc5cc19d975022d2ba83779c85d19e33428885c025b179cac40cd644eccd667dd9d11b72216da92ff0de6e7b0cd7c8ca66a3140a1ca55aaf9f3597499a336133722745f7cea700137317630476de25710f4d815817b4c47c5fa1d0180144f98a0beccef4182120b819ae2f2297c7fea6574ef01c4413719654b94282011188cc080ecada91cedba3eb9d85762b27602423dcce4de33dc2cdaa363ac60ae7092886fa30d64416da7f7c5d2b003f43f542bd6fa1ec89a7ca9cbcc72471d8a2aaf368598c65d841d31e9d446efba2e59a0909b305978c24cbe74ae78c768dd42d70a5e7d669a55e114484d6bac2911b7c23e54efd598a8c3b9d7cf66a50fef641eac6101fc40cc79870e06bbbbf3bca7e21f6b55826cf0f9b2d97dd1ac9c74ed5663f0a66e9daf72fa4af294e250f56934147989f1adc8e87e375f5514a1a5a994b02f7cd312629ab48261ebd0b57a89bef4100180c09e6518f8da0ce8ec38594bb4bd772837f7d8fee7fe18e27d6e351325c3151306b858dd1d47bf844ed496570f9e3519fb014c1de8a80892f2edff6b33865647bf29eb895cbe5f4cb7d2bb560827ed06f560cab060a206af04cb520ad64bf644d4cee89a0fae5aaf8261de018f95b729d0869cd1fe3649d3ead3774f1e848ea684886f879e015d1e6895b5b142b5f201c2b2ce5c9bcc6e642c80bd5e4c0d78752b288f6fa29688f2e67ee041c33ed77121ba391305276d997c0b62e3974adaecc831e828f356437f998077377d368520b5beb77b0a29f8a114ffdf3d38fd331f115967ba20f9fe89c57ab872d7ca7491f8212da4f6fdbd51253fce8fcd46a8f6d88a68670ad168de017f3ac7d282fa23cfc2a0f409d526a34a19043bb0236a01d7ec7825180720b8c2584fb97bb26b3a28539b7d4ce5d8498b704529926d773a4456fe275b17b2fd4268f437e03ec111d6d57ace29b069e0af019ef55b184ced72f901f6022e46c922910184049bbbbb1728d698ff4eb71ca53ee74304fc851c7800f17da8246494be69b111fad9bb905f9a633d2a54190f60a0e7a5e95d4a24093cd16a1c5c1b0c42d5a284c66d89556af3a6d48a5a735cf1136e294bf4b929c9cef2fea7f4af327fba6063eb8f9e05a6cdeed1f5a6b68685e8aa885acf496edd1e09156688c654ad85b61bf487e01e7b52fc18d6bb46febd2a4142295d32492ffe995e01cf58bd4839235eda955405d77000d093dd19a8492296a3a6c3ec8e65d29a58d30a2abcf0242b7636afdf653e54b92f908b782c7d6e5d3f9c6c2c28ce2496c2434ce6e34124727db21a200b4af7226cd7600ddf52aaec52173399d3f1e83e6552ce0e7f7b8a0c4a7d5bc7e06980f8ee45ac7e1c3caf14917471b7e4afb28e877f87f4f4f2ed3251cac06b765e5977ef260aa02548bcbc2ad383721a48bd908fc6ccac666f0cc8a33e40bc06f5a23dcc3b32347cc22fce41fafd9f5f5308b649807b4f354c203a34826802f8bb0320f1ffdafd80ffb522e9169ca34c564be6675d8b3b9356f2a17359005b4a8b46ddf66c1c6a82aaada1f124923f4879158bf2c9f58248215c0f8187391fe4bb01d96a4a1828aefb000be7ee45bc310f4abbd9728ab28085bfb11e626e193553085815e300b0f3b44373f4d96db1d8da6eb0b8f7e7db2ef8a50e107f74b38528b9160892f95f89ae762651df373f586c40a447b602ddb2d2518e898bc6e826f5d1aa02c23e46cbcc61a0768cd6c8259d2fd9332ba46221b002be9611beece73584984d451793e723153c5e4e5ea8f00b028429b5a566803f54f9ded70416c2015d069e24e85b73039a376918591db30f4dd837da772198a40e369a3c18451d8ccccf5bcaa86dadf8a5b5dce88a4b451edef3b1a9425fd8f5b319b1b28a38dc548a94f73f69383b7a24592cdde0e7534621d999557a5f409e448d643a23eceaca8216c389707d2f26f8cab62595b15d37947d93868b368641f319132f0d4b28e1fab8af0d9e15d69974890bbaa1f59ce3d46bcc82707e4389c3914106c934bd3acd2a08024c4ce0d418e7933d141329af0cfe6783bb9006799f19a1a6100be1dc832c6439437b623eaf2289110ad2cac884140a225ccaaa5adc354374c961d41c77f62679dfde293742a9360b73cadee044dc668f6bf1902ea1e9f7f6da9fc29dc1a7412de140eac0e757e1b0764f9c7c3dcafa774dfc7a1e9bb7ef64e3c8d6718dd6fe6abd23a83d4489219e44a21404d579cd642c9a280d2e20e5ae74d86ea4374259f449d029a9a96117011d0bdb0f1daa5fc4253be0783978481db3e7b95c06c97b9632c9fb996b85b3b7846cfa4c677d8404bb6285e3d9aadfbcf7bdbd9a3907f06ac11222ffca3a6f1cc8ccd450f8c6e025af84d45d50b2ec85b5adb788bf4b46e6827b6ee2c9044064e93b86bc2aef8794fe05dd9e0337c301549c805e343e17a4ed1f43ccfd3f2eefeb7c5176922eb0c067d68f430a376d1f0282267919c4158e636ec6391c5ff85eff20daea703fe7663991e8f4e3c70f5c55ec2aa30a10cdd433b72eecc0957ad5c584274dbfdd3918bf7eab9b8b7a87c972ede2b3b026e624128f5133eb5e0e67ae9cb57aa41becc4bf86364a369d870bf57e8cd0385f74dc229e44f2fa634670d56cfb7cff3948eb3adaefe32f28fcda24bd2313b613711ec11745ef5b8f54b1ceed93cfdb796c65651643b1caa46a36629682623786579c688ddbeaff901b99ea7266917b030fde018af77e1fb749c445abf354377bcc12a5457e40d3cadb30307184487487fb5067e7b7b5203c7086282ad7f08db11cf9bcbd288b028b8876d77bc2c2050126fbd090e7f0017dabb0e88615595006fcd3d9c80942a7cb88f567f7dd56a47a0819c4a820026bd3d0e82e6d73d0a2b767b073bb1e71069c58fc0cdfed80049b6d57710069362ae7dbf81e0311b1b72ff7ad6e59e3389ccee5b0ca59df9e01efdbc07450385696a3719f78238c032d56c8ad4afb0126a2d4cf962a481ba0c846ab12948d022c91df8ef4b651989e0b30d95bf761febfadce46ab04b15e48a8cbe0ae3ab33ea192a1e23bd8fc8b1df35c7be290f56d3f92d3538c80baf797a069ce65186cf96f83fb3cd0429552790775717f7f86a0c81cdc2667ea373619d9cbe831f5cdb07c45a48c9cb9287f4cd77a4bd52b4c5bcd85fddca36839e7b0f4c4c28ad0ae060f272ecc57f19ccac9cf9918f055f6d478a7eb4fb219bd41a57523268f65775fed37589059c44f3c80d81d859f9d52cd7e26f730c0b9afc4ae0e032ae8fcb9077fc204e709435cd87b902501d3d21025d1f1a7f0290d3df22f161e74426ce3ce4b472e3c9ec4e07ad32716bc01d26fefecfc89b0cb76ef53e7dc574083184dce284e261b830a21efe9f8a418e74ce83dff0aa6e55b1016a33184eec4522a88f1eddf342b38943f668a7a4ae86bd38d03ea5309b94c2fb000456cddd214040fa9d16c1560683f1f5276ae0b45f90a4b5e14cb0bfbeeb6138cd32e6f35e38e3e347c9c8862aaec89644c9c2f15a402849ee05a154a0c02d509958a369b19c1815d7594c2feb88e88ace0eadfc45d569e1b3573952a7062fe6650d1ec98788759a78b5ec20ea341a753f5def46e7ff2ba7be5c43286068973845ed7ebfb64c38431f3a623643d9bc17eb36461ddb4218f6a5dc2804078f7a99aee1896d9113bbb422dd8298ebd85ca4632746dad81706d055a186fab0d78833b19ac0e5f767182ab5d5f8a357ce4716107fa0b79b6b38fd6eda9801013022decc06ea7ee955489c85711384c33b1f04f6b1dbfd4cced4bc8d23138754e97a43c0b9016379a30f2a9524c5e80666ba42f28ceba1632f6725c737d11c5a0c4cc61d7c104c14c3f43546838057e7a5c4f719ab807ca0f6b4244dab72936e005162f98b98b4e63e0b5759334c569bde58bbbc540c6571c0a7415d2808ca6985d2ab3378847d78fb569e6aed53062e30166c59feed515f280f1233e1badeba80448a23e06b68f71be695c646edf20b954df943dc3d1352aa4b7fc159591b6cbe6c39fb81c0bb65a358612dd4a6dd820a6157ae1a4789a86b70f10eba38ec27b3f38992acf93fec0e0568b588e2284b9e76c3d033de243e47f2150a3029430977b21a5e31aac715e673d7130fa9742484abcfe7c0b1224f322bd709a62263760315baa36440875ae1f5fbe7e921d1ddecf89cad0220ccae1b17364103071f18b5f7b05c0e01941a001480897618fac1ed4e8c920302aea9db8cd237f830b6e6c0955141fd6a3e763eeb95cd149129a535136c5fbde89dc2cade6044f23cd9f39426c4eb7e0b57edbd8e9225dd8525a50fe871cc9cd82c56471a6bde1584e9c010241178f80fd483c9d4da899c30aff8167b37c3cf6f460af2e18b196333fddbe6b6d42d570f89985c18b85b770e61d4a7e11a1f3801abfb909f567e9b39dc064ab633703563542540b83b5580c37e4b3b2ac53ed9996efd773f73a54f699d4527f71c3b181240298cb24a8bcacc9adcd947314c13e5d96403df093750670ff2be6edad974e062b6bfeda0ec7d8c3ce50e538dff5122cc98d07429b300d64755206c9d9d2ed907&lt;/script&gt;
  &lt;div class=&#34;hbe hbe-content&#34;&gt;
    &lt;div class=&#34;hbe hbe-input hbe-input-default&#34;&gt;
      &lt;input class=&#34;hbe hbe-input-field hbe-input-field-default&#34; type=&#34;password&#34; id=&#34;hbePass&#34;&gt;
      &lt;label class=&#34;hbe hbe-input-label hbe-input-label-default&#34; for=&#34;hbePass&#34;&gt;
        &lt;span class=&#34;hbe hbe-input-label-content hbe-input-label-content-default&#34;&gt;已被作者加密。&lt;/span&gt;
      &lt;/label&gt;
    &lt;/div&gt;
  &lt;/div&gt;
&lt;/div&gt;
&lt;script data-pjax src=&#34;/lib/hbe.js&#34;&gt;&lt;/script&gt;&lt;link href=&#34;/css/hbe.style.css&#34; rel=&#34;stylesheet&#34; type=&#34;text/css&#34;&gt;</content>
        <category term="2024" />
        <updated>2024-08-28T15:17:00.000Z</updated>
    </entry>
    <entry>
        <id>https://blog.soulter.top/posts/%E5%90%AF%E5%8F%91%E5%BC%8F%E4%BC%98%E5%8C%96%E7%AE%97%E6%B3%95.html</id>
        <title>常见的启发式优化算法</title>
        <link rel="alternate" href="https://blog.soulter.top/posts/%E5%90%AF%E5%8F%91%E5%BC%8F%E4%BC%98%E5%8C%96%E7%AE%97%E6%B3%95.html"/>
        <content type="html">&lt;h1 id=&#34;引子&#34;&gt;&lt;a class=&#34;markdownIt-Anchor&#34; href=&#34;#引子&#34;&gt;&lt;/a&gt; 引子&lt;/h1&gt;
&lt;p&gt;最近在中科院自动化所实习，其中负责了一个保密项目，项目主要涵盖了多目标优化算法。可能是场景比较复杂，发现使用 &lt;code&gt;scipy.optimize&lt;/code&gt; 中的 &lt;code&gt;minimize&lt;/code&gt; 方法得出了与实际值（暴力搜索）相差较大的结果。因此，我开始了解到一些启发式优化算法，如模拟退火、粒子群优化、遗传算法等。它们不同于传统的梯度下降法，而是通过模拟自然界中的某些现象来寻找全局最优解。使用了这些方法之后，发现效果与暴力搜索结果相差很小，并且速度有了很大的提升。这里我将介绍一些常见的启发式优化算法（我觉得这些算法非常地形象，很好理解 😃）。&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://cf.s3.soulter.top/%E5%90%AF%E5%8F%91%E5%BC%8F%E4%BC%98%E5%8C%96%E7%AE%97%E6%B3%95/image.png&#34; alt=&#34;&#34; /&gt;&lt;br /&gt;
这张图描绘了我的问题中采用多种优化方法的解的情况。（误差上界为 20，超出 20 的都改成了 20 以增强可视化效果）&lt;/p&gt;
&lt;h1 id=&#34;0x01-遗传算法-genetic-algorithm&#34;&gt;&lt;a class=&#34;markdownIt-Anchor&#34; href=&#34;#0x01-遗传算法-genetic-algorithm&#34;&gt;&lt;/a&gt; 0x01. 遗传算法 (Genetic Algorithm)&lt;/h1&gt;
&lt;p&gt;遗传算法，顾名思义，就是模拟生物的遗传过程，它通过模拟自然选择中的优胜劣汰，来寻找优化目标全局最优解。让我们回顾中学时学过的生物课：在自然界中，适应能力高的个体会更容易存活下来，而适应能力低的个体会逐渐消失。种群内的个体在繁殖时，会发生基因重组、基因突变等现象，使得后代的性状发生改变，保持其基因多样性。而遗传算法正是基于这些现象来寻找全局最优解的。&lt;/p&gt;
&lt;p&gt;一般来说，遗传算法的步骤有初始化种群、选择、交叉、变异。在初始化了种群之后，就不断地执行之后的步骤，直到满足终止条件。&lt;/p&gt;
&lt;h2 id=&#34;1-初始化种群&#34;&gt;&lt;a class=&#34;markdownIt-Anchor&#34; href=&#34;#1-初始化种群&#34;&gt;&lt;/a&gt; 1. 初始化种群&lt;/h2&gt;
&lt;figure class=&#34;highlight py&#34;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&#34;gutter&#34;&gt;&lt;pre&gt;&lt;span class=&#34;line&#34;&gt;1&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;2&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;3&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&#34;code&#34;&gt;&lt;pre&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;keyword&#34;&gt;def&lt;/span&gt; &lt;span class=&#34;title function_&#34;&gt;init_population&lt;/span&gt;(&lt;span class=&#34;params&#34;&gt;pop_size&lt;/span&gt;):&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;    population = np.random.normal(&lt;span class=&#34;number&#34;&gt;0&lt;/span&gt;, &lt;span class=&#34;number&#34;&gt;1&lt;/span&gt;, (pop_size, &lt;span class=&#34;number&#34;&gt;2&lt;/span&gt;)) &lt;span class=&#34;comment&#34;&gt;# 采用正态分布初始化种群，这里假设个体是一个二维向量。&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;    &lt;span class=&#34;keyword&#34;&gt;return&lt;/span&gt; population&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/figure&gt;
&lt;h2 id=&#34;2-适应度函数&#34;&gt;&lt;a class=&#34;markdownIt-Anchor&#34; href=&#34;#2-适应度函数&#34;&gt;&lt;/a&gt; 2. 适应度函数&lt;/h2&gt;
&lt;p&gt;通过适应度函数来“选择”出适应能力高的个体。适应度函数在优化问题中也可以采用目标函数来代替。&lt;/p&gt;
&lt;figure class=&#34;highlight py&#34;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&#34;gutter&#34;&gt;&lt;pre&gt;&lt;span class=&#34;line&#34;&gt;1&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;2&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&#34;code&#34;&gt;&lt;pre&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;keyword&#34;&gt;def&lt;/span&gt; &lt;span class=&#34;title function_&#34;&gt;fitness_func&lt;/span&gt;(&lt;span class=&#34;params&#34;&gt;population&lt;/span&gt;):&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;    &lt;span class=&#34;keyword&#34;&gt;return&lt;/span&gt; np.&lt;span class=&#34;built_in&#34;&gt;sum&lt;/span&gt;(population ** &lt;span class=&#34;number&#34;&gt;2&lt;/span&gt;, axis=&lt;span class=&#34;number&#34;&gt;1&lt;/span&gt;) &lt;span class=&#34;comment&#34;&gt;# 这里假设目标函数是 f(x) = x1^2 + x2^2。&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/figure&gt;
&lt;h2 id=&#34;3-选择-selection&#34;&gt;&lt;a class=&#34;markdownIt-Anchor&#34; href=&#34;#3-选择-selection&#34;&gt;&lt;/a&gt; 3. 选择 (Selection)&lt;/h2&gt;
&lt;p&gt;有了适应性函数，我们就可以确定谁的适应性高，更符合条件了。但是如何从种群中选择出适应性高的个体呢？很容易就可以想到通过“加权”的方式来选择，即适应性强的个体被选择的几率更大，这就是最基本的选择方法：轮盘赌。&lt;/p&gt;
&lt;p&gt;轮盘赌中，每个个体被选中的概率为&lt;/p&gt;
&lt;p&gt;$ P_i = \frac{f_i}{\sum_{j=1}^{N} f_j} $&lt;/p&gt;
&lt;p&gt;其中，$ f_i $ 为个体 $ i $ 的适应度，$ N $ 为种群大小。&lt;/p&gt;
&lt;figure class=&#34;highlight py&#34;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&#34;gutter&#34;&gt;&lt;pre&gt;&lt;span class=&#34;line&#34;&gt;1&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;2&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;3&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;4&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;5&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&#34;code&#34;&gt;&lt;pre&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;keyword&#34;&gt;def&lt;/span&gt; &lt;span class=&#34;title function_&#34;&gt;select&lt;/span&gt;(&lt;span class=&#34;params&#34;&gt;population, fitness&lt;/span&gt;):&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;    prob = fitness / np.&lt;span class=&#34;built_in&#34;&gt;sum&lt;/span&gt;(fitness)&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;    idx = np.random.choice(np.arange(&lt;span class=&#34;built_in&#34;&gt;len&lt;/span&gt;(population)), size=&lt;span class=&#34;built_in&#34;&gt;len&lt;/span&gt;(population), p=prob)&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;    &lt;span class=&#34;comment&#34;&gt;# p 为每个个体被选中的概率&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;    &lt;span class=&#34;keyword&#34;&gt;return&lt;/span&gt; population[idx]&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/figure&gt;
&lt;p&gt;然而，能够发现，如果个体的适应度差距较大，就可能导致适应度最高的个体反复被选择，而适应度低的可能一直得不到选择，导致解多样性降低，解空间减少，从而过早收敛。在比较复杂的问题下，这种情况可能会导致算法陷入局部最优解。这里就有了第二个方法，也是当今遗传算法中最常用的方法：锦标赛选择。&lt;/p&gt;
&lt;p&gt;竞标赛选择法随机选择 $ k $ 个个体，然后从中选择适应度最高的个体。这个过程重复 $ N $ 次，直到选择出 $ N $ 个个体。&lt;/p&gt;
&lt;figure class=&#34;highlight py&#34;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&#34;gutter&#34;&gt;&lt;pre&gt;&lt;span class=&#34;line&#34;&gt;1&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;2&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;3&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;4&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;5&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;6&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&#34;code&#34;&gt;&lt;pre&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;keyword&#34;&gt;def&lt;/span&gt; &lt;span class=&#34;title function_&#34;&gt;tournament_select&lt;/span&gt;(&lt;span class=&#34;params&#34;&gt;population, fitness, k=&lt;span class=&#34;number&#34;&gt;2&lt;/span&gt;, N=&lt;span class=&#34;number&#34;&gt;10&lt;/span&gt;&lt;/span&gt;):&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;    selected = []&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;    &lt;span class=&#34;keyword&#34;&gt;for&lt;/span&gt; _ &lt;span class=&#34;keyword&#34;&gt;in&lt;/span&gt; &lt;span class=&#34;built_in&#34;&gt;range&lt;/span&gt;(N):&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;        idx = np.random.choice(np.arange(&lt;span class=&#34;built_in&#34;&gt;len&lt;/span&gt;(population)), size=k)&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;        selected.append(population[np.argmax(fitness[idx])])&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;    &lt;span class=&#34;keyword&#34;&gt;return&lt;/span&gt; np.array(selected)&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/figure&gt;
&lt;p&gt;此外，还有一些其他的选择方法，如：线性排名选择、指数排名选择等。&lt;/p&gt;
&lt;h2 id=&#34;4-交叉-crossover&#34;&gt;&lt;a class=&#34;markdownIt-Anchor&#34; href=&#34;#4-交叉-crossover&#34;&gt;&lt;/a&gt; 4. 交叉 (Crossover)&lt;/h2&gt;
&lt;p&gt;交叉模拟了生物的基因重组过程。在交叉过程中，两个个体的基因会发生重组，产生新的个体。交叉的方式有很多种，如单点交叉、多点交叉、均匀交叉等。这里我们以单点交叉为例。&lt;/p&gt;
&lt;p&gt;单点交叉会随机选择一个交叉点，然后将两个个体的基因在交叉点处进行交换。&lt;/p&gt;
&lt;figure class=&#34;highlight py&#34;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&#34;gutter&#34;&gt;&lt;pre&gt;&lt;span class=&#34;line&#34;&gt;1&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;2&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;3&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;4&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;5&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&#34;code&#34;&gt;&lt;pre&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;keyword&#34;&gt;def&lt;/span&gt; &lt;span class=&#34;title function_&#34;&gt;crossover&lt;/span&gt;(&lt;span class=&#34;params&#34;&gt;parent1, parent2&lt;/span&gt;):&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;    point = np.random.randint(&lt;span class=&#34;number&#34;&gt;0&lt;/span&gt;, &lt;span class=&#34;built_in&#34;&gt;len&lt;/span&gt;(parent1))&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;    child1 = np.concatenate([parent1[:point], parent2[point:]])&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;    child2 = np.concatenate([parent2[:point], parent1[point:]])&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;    &lt;span class=&#34;keyword&#34;&gt;return&lt;/span&gt; child1, child2&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/figure&gt;
&lt;h2 id=&#34;5-变异-mutation&#34;&gt;&lt;a class=&#34;markdownIt-Anchor&#34; href=&#34;#5-变异-mutation&#34;&gt;&lt;/a&gt; 5. 变异 (Mutation)&lt;/h2&gt;
&lt;p&gt;变异模拟了生物的基因突变过程。在变异过程中，个体的基因会发生随机变化，引入适当的多样性。变异算子同样有很多种，如：高斯变异、均匀变异等。这里我们以均匀变异为例。&lt;/p&gt;
&lt;p&gt;均匀变异会随机选择一个基因，然后将其变异为另一个值。&lt;/p&gt;
&lt;figure class=&#34;highlight py&#34;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&#34;gutter&#34;&gt;&lt;pre&gt;&lt;span class=&#34;line&#34;&gt;1&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;2&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;3&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;4&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;5&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&#34;code&#34;&gt;&lt;pre&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;keyword&#34;&gt;def&lt;/span&gt; &lt;span class=&#34;title function_&#34;&gt;mutation&lt;/span&gt;(&lt;span class=&#34;params&#34;&gt;child, prob=&lt;span class=&#34;number&#34;&gt;0.1&lt;/span&gt;&lt;/span&gt;):&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;    &lt;span class=&#34;keyword&#34;&gt;for&lt;/span&gt; i &lt;span class=&#34;keyword&#34;&gt;in&lt;/span&gt; &lt;span class=&#34;built_in&#34;&gt;range&lt;/span&gt;(&lt;span class=&#34;built_in&#34;&gt;len&lt;/span&gt;(child)):&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;        &lt;span class=&#34;keyword&#34;&gt;if&lt;/span&gt; np.random.rand() &amp;lt; prob:&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;            child[i] = np.random.normal(&lt;span class=&#34;number&#34;&gt;0&lt;/span&gt;, &lt;span class=&#34;number&#34;&gt;1&lt;/span&gt;)&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;    &lt;span class=&#34;keyword&#34;&gt;return&lt;/span&gt; child&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/figure&gt;
&lt;p&gt;交叉算子因其全局搜索能力而作为主要算子。变异算子通过对个体进行小幅度的随机调整，在当前搜索区域内进行细致的搜索和优化，因其局部搜索能力而作为辅助算子。&lt;/p&gt;
&lt;p&gt;遗传算法就是在不断地重复选择、交叉、变异的过程中，逐渐逼近全局最优解。&lt;/p&gt;
&lt;p&gt;这是一个非常成熟的算法，也有一些很好的 Python 库可以使用，如 &lt;code&gt;deap&lt;/code&gt;。&lt;/p&gt;
&lt;h1 id=&#34;模拟退火算法-simulated-annealing&#34;&gt;&lt;a class=&#34;markdownIt-Anchor&#34; href=&#34;#模拟退火算法-simulated-annealing&#34;&gt;&lt;/a&gt; 模拟退火算法 (Simulated Annealing)&lt;/h1&gt;
&lt;p&gt;模拟退火算法的灵感来源于金属退火过程。在金属加热后的退火过程中，会缓慢冷却达到低能量状态，即高度有序的晶体结构。&lt;/p&gt;
&lt;p&gt;具体来说，它需要一个初始温度 $ T $ 和一个降温速率 $ \alpha $。在每一次迭代中，算法会随机从当前解的邻域中选择一个新解，然后计算新解的能量。如果新解的能量比当前解的能量更低，那么就接受新解。如果新解的能量比当前解的能量更高，那么就以一定的概率接受新解。这个概率由下式给出：&lt;/p&gt;
&lt;p&gt;$ P = e^{-\frac{E_{new} - E_{old}}{T}} $&lt;/p&gt;
&lt;p&gt;其中，$ E_{new} $ 为新解的能量，$ E_{old} $ 为当前解的能量。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;这里聚焦的是最小化优化问题，因此能量低的（解更小）被认为是更优的解。不管怎么样，都会有可能接受一个较差解（能量高的），这与传统的贪心算法不同，避免了早期陷入局部最优解的问题。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;figure class=&#34;highlight py&#34;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&#34;gutter&#34;&gt;&lt;pre&gt;&lt;span class=&#34;line&#34;&gt;1&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;2&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;3&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;4&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;5&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;6&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;7&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;8&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;9&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;10&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;11&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;12&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;13&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;14&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;15&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;16&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;17&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;18&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&#34;code&#34;&gt;&lt;pre&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;comment&#34;&gt;# 假设目标函数为 f(x) = x1^2 + x2^2&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;keyword&#34;&gt;def&lt;/span&gt; &lt;span class=&#34;title function_&#34;&gt;objective_func&lt;/span&gt;(&lt;span class=&#34;params&#34;&gt;x&lt;/span&gt;):&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;    &lt;span class=&#34;keyword&#34;&gt;return&lt;/span&gt; np.&lt;span class=&#34;built_in&#34;&gt;sum&lt;/span&gt;(x ** &lt;span class=&#34;number&#34;&gt;2&lt;/span&gt;)&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;keyword&#34;&gt;def&lt;/span&gt; &lt;span class=&#34;title function_&#34;&gt;simulated_annealing&lt;/span&gt;(&lt;span class=&#34;params&#34;&gt;T, alpha, max_iter&lt;/span&gt;):&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;    x = np.random.normal(&lt;span class=&#34;number&#34;&gt;0&lt;/span&gt;, &lt;span class=&#34;number&#34;&gt;1&lt;/span&gt;, &lt;span class=&#34;number&#34;&gt;2&lt;/span&gt;)&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;    E = objective_func(x)&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;    &lt;span class=&#34;keyword&#34;&gt;for&lt;/span&gt; _ &lt;span class=&#34;keyword&#34;&gt;in&lt;/span&gt; &lt;span class=&#34;built_in&#34;&gt;range&lt;/span&gt;(max_iter):&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;        x_new = x + np.random.normal(&lt;span class=&#34;number&#34;&gt;0&lt;/span&gt;, &lt;span class=&#34;number&#34;&gt;1&lt;/span&gt;, &lt;span class=&#34;number&#34;&gt;2&lt;/span&gt;)&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;        E_new = objective_func(x_new)&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;        &lt;span class=&#34;keyword&#34;&gt;if&lt;/span&gt; E_new &amp;lt; E &lt;span class=&#34;keyword&#34;&gt;or&lt;/span&gt; np.random.rand() &amp;lt; np.exp(-(E_new - E) / T):&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;            x = x_new&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;            E = E_new&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;        T *= alpha&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;    &lt;span class=&#34;keyword&#34;&gt;return&lt;/span&gt; x&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;x = simulated_annealing(&lt;span class=&#34;number&#34;&gt;100&lt;/span&gt;, &lt;span class=&#34;number&#34;&gt;0.99&lt;/span&gt;, &lt;span class=&#34;number&#34;&gt;1000&lt;/span&gt;)&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;built_in&#34;&gt;print&lt;/span&gt;(x)&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/figure&gt;
&lt;p&gt;在我的问题中，对于这三种方法，模拟算法所花费的时间非常少。&lt;/p&gt;
&lt;p&gt;&lt;code&gt;scipy&lt;/code&gt; 中有模拟退火算法的实现，可以直接使用。&lt;/p&gt;
&lt;h1 id=&#34;粒子群优化算法-particle-swarm-optimization&#34;&gt;&lt;a class=&#34;markdownIt-Anchor&#34; href=&#34;#粒子群优化算法-particle-swarm-optimization&#34;&gt;&lt;/a&gt; 粒子群优化算法 (Particle Swarm Optimization)&lt;/h1&gt;
&lt;p&gt;粒子群优化算法是一种基于群体智能的优化算法，模仿了鸟群、鱼群等社会动物群体的行为，通过个体间的信息共享实现全局最优解的搜索。&lt;/p&gt;
&lt;p&gt;在粒子群优化算法中，每个个体被称为粒子，每个粒子都有自己的位置和速度。粒子的位置代表了解空间中的一个解，而粒子的速度代表了粒子在解空间中的搜索方向。粒子的速度会受到自身的历史最优解和群体的历史最优解（整个群体中目前的最优的解）的影响，根据个体最佳位置和群体最佳位置进行更新，具体的更新公式如下：&lt;/p&gt;
&lt;p&gt;速度更新公式：&lt;/p&gt;
&lt;p&gt;$ v_{i}^{t+1} = w \cdot v_{i}^{t} + c_1 \cdot r_1 \cdot (pbest_{i} - x_{i}^{t}) + c_2 \cdot r_2 \cdot (gbest - x_{i}^{t}) $&lt;/p&gt;
&lt;p&gt;位置更新公式：&lt;/p&gt;
&lt;p&gt;$ x_{i}^{t+1} = x_{i}^{t} + v_{i}^{t+1} $&lt;/p&gt;
&lt;p&gt;其中，$ v_{i}^{t} $ 为粒子 $ i $ 在第 $ t $ 代的速度，$ x_{i}^{t} $ 为粒子 $ i $ 在第 $ t $ 代的位置，$ pbest_{i} $ 为粒子 $ i $ 的历史最优位置，$ gbest $ 为群体的历史最优位置，$ w $ 为惯性权重，$ c_1 $ 和 $ c_2 $ 为加速因子，控制粒子向个体最佳位置和群体最佳位置的加速程度，$ r_1 $ 和 $ r_2 $ 为随机数。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;w 相当于给了粒子记忆历史速度的能力，让历史速度也有一部分权重参与新速度的生成，就如同控制学习率的动量法一样。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;我们依旧以目标函数 $ f(x) = x1^2 + x2^2 $ 为例，来实现粒子群优化算法。&lt;/p&gt;
&lt;figure class=&#34;highlight py&#34;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&#34;gutter&#34;&gt;&lt;pre&gt;&lt;span class=&#34;line&#34;&gt;1&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;2&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&#34;code&#34;&gt;&lt;pre&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;keyword&#34;&gt;def&lt;/span&gt; &lt;span class=&#34;title function_&#34;&gt;objective_func&lt;/span&gt;(&lt;span class=&#34;params&#34;&gt;x&lt;/span&gt;):&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;    &lt;span class=&#34;keyword&#34;&gt;return&lt;/span&gt; np.&lt;span class=&#34;built_in&#34;&gt;sum&lt;/span&gt;(x ** &lt;span class=&#34;number&#34;&gt;2&lt;/span&gt;)&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/figure&gt;
&lt;p&gt;一些参数设定：&lt;/p&gt;
&lt;figure class=&#34;highlight py&#34;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&#34;gutter&#34;&gt;&lt;pre&gt;&lt;span class=&#34;line&#34;&gt;1&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;2&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;3&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;4&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;5&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;6&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;7&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&#34;code&#34;&gt;&lt;pre&gt;&lt;span class=&#34;line&#34;&gt;num_particles = &lt;span class=&#34;number&#34;&gt;30&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;dimensions = &lt;span class=&#34;number&#34;&gt;2&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;bounds = [-&lt;span class=&#34;number&#34;&gt;10&lt;/span&gt;, &lt;span class=&#34;number&#34;&gt;10&lt;/span&gt;] &lt;span class=&#34;comment&#34;&gt;# 解空间的边界&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;max_iterations = &lt;span class=&#34;number&#34;&gt;100&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;w = &lt;span class=&#34;number&#34;&gt;0.5&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;c1 = &lt;span class=&#34;number&#34;&gt;1.5&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;c2 = &lt;span class=&#34;number&#34;&gt;1.5&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/figure&gt;
&lt;p&gt;初始化粒子群：&lt;/p&gt;
&lt;figure class=&#34;highlight py&#34;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&#34;gutter&#34;&gt;&lt;pre&gt;&lt;span class=&#34;line&#34;&gt;1&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;2&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;3&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;4&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;5&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&#34;code&#34;&gt;&lt;pre&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;comment&#34;&gt;# 初始化粒子群&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;keyword&#34;&gt;def&lt;/span&gt; &lt;span class=&#34;title function_&#34;&gt;initialize_particles&lt;/span&gt;(&lt;span class=&#34;params&#34;&gt;num_particles, dimensions, bounds&lt;/span&gt;):&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;    particles = np.random.uniform(bounds[&lt;span class=&#34;number&#34;&gt;0&lt;/span&gt;], bounds[&lt;span class=&#34;number&#34;&gt;1&lt;/span&gt;], (num_particles, dimensions)) &lt;span class=&#34;comment&#34;&gt;# (30, 2)&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;    velocities = np.random.uniform(-&lt;span class=&#34;number&#34;&gt;1&lt;/span&gt;, &lt;span class=&#34;number&#34;&gt;1&lt;/span&gt;, (num_particles, dimensions)) &lt;span class=&#34;comment&#34;&gt;# (30, 2)&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;    &lt;span class=&#34;keyword&#34;&gt;return&lt;/span&gt; particles, velocities&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/figure&gt;
&lt;p&gt;更新粒子群：&lt;/p&gt;
&lt;figure class=&#34;highlight py&#34;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&#34;gutter&#34;&gt;&lt;pre&gt;&lt;span class=&#34;line&#34;&gt;1&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;2&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;3&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;4&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;5&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;6&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&#34;code&#34;&gt;&lt;pre&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;comment&#34;&gt;# 更新粒子速度和位置&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;keyword&#34;&gt;def&lt;/span&gt; &lt;span class=&#34;title function_&#34;&gt;update_particles&lt;/span&gt;(&lt;span class=&#34;params&#34;&gt;particles, velocities, p_best, g_best, w, c1, c2&lt;/span&gt;):&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;    r1, r2 = np.random.rand(), np.random.rand()&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;    velocities = w * velocities + c1 * r1 * (p_best - particles) + c2 * r2 * (g_best - particles)&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;    particles = particles + velocities&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;    &lt;span class=&#34;keyword&#34;&gt;return&lt;/span&gt; particles, velocities&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/figure&gt;
&lt;p&gt;主算法：&lt;/p&gt;
&lt;figure class=&#34;highlight py&#34;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&#34;gutter&#34;&gt;&lt;pre&gt;&lt;span class=&#34;line&#34;&gt;1&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;2&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;3&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;4&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;5&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;6&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;7&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;8&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;9&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;10&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;11&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;12&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;13&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;14&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;15&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;16&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;17&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;18&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;19&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;20&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;21&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;22&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;23&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;24&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;25&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;26&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;27&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&#34;code&#34;&gt;&lt;pre&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;comment&#34;&gt;# PSO算法&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;keyword&#34;&gt;def&lt;/span&gt; &lt;span class=&#34;title function_&#34;&gt;pso&lt;/span&gt;(&lt;span class=&#34;params&#34;&gt;num_particles, dimensions, bounds, max_iterations, w, c1, c2&lt;/span&gt;):&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;    particles, velocities = initialize_particles(num_particles, dimensions, bounds)&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;    p_best = particles.copy()&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;    p_best_scores = np.apply_along_axis(objective_function, &lt;span class=&#34;number&#34;&gt;1&lt;/span&gt;, p_best) &lt;span class=&#34;comment&#34;&gt;# 每个粒子历史最优解 (30,)&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;    g_best = particles[np.argmin(p_best_scores)] &lt;span class=&#34;comment&#34;&gt;# 群体历史最优解，np.argmin 返回最小值索引 (2,)&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;    &lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;    &lt;span class=&#34;keyword&#34;&gt;for&lt;/span&gt; iteration &lt;span class=&#34;keyword&#34;&gt;in&lt;/span&gt; &lt;span class=&#34;built_in&#34;&gt;range&lt;/span&gt;(max_iterations):&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;        &lt;span class=&#34;comment&#34;&gt;# 更新粒子速度和位置&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;        particles, velocities = update_particles(particles, velocities, p_best, g_best, w, c1, c2)&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;        &lt;span class=&#34;comment&#34;&gt;# 计算新的适应度&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;        scores = np.apply_along_axis(objective_function, &lt;span class=&#34;number&#34;&gt;1&lt;/span&gt;, particles)&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;        &lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;        &lt;span class=&#34;keyword&#34;&gt;for&lt;/span&gt; i &lt;span class=&#34;keyword&#34;&gt;in&lt;/span&gt; &lt;span class=&#34;built_in&#34;&gt;range&lt;/span&gt;(num_particles):&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;            &lt;span class=&#34;comment&#34;&gt;# 更新个体历史最优解&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;            &lt;span class=&#34;keyword&#34;&gt;if&lt;/span&gt; scores[i] &amp;lt; p_best_scores[i]:&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;                p_best[i] = particles[i]&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;                p_best_scores[i] = scores[i]&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;        &lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;        &lt;span class=&#34;comment&#34;&gt;# 更新群体历史最优解&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;        g_best = p_best[np.argmin(p_best_scores)]&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;        &lt;span class=&#34;built_in&#34;&gt;print&lt;/span&gt;(&lt;span class=&#34;string&#34;&gt;f&amp;quot;Iteration &lt;span class=&#34;subst&#34;&gt;&amp;#123;iteration+&lt;span class=&#34;number&#34;&gt;1&lt;/span&gt;&amp;#125;&lt;/span&gt;/&lt;span class=&#34;subst&#34;&gt;&amp;#123;max_iterations&amp;#125;&lt;/span&gt;, Best Score: &lt;span class=&#34;subst&#34;&gt;&amp;#123;&lt;span class=&#34;built_in&#34;&gt;min&lt;/span&gt;(p_best_scores)&amp;#125;&lt;/span&gt;&amp;quot;&lt;/span&gt;)&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;    &lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;    &lt;span class=&#34;keyword&#34;&gt;return&lt;/span&gt; g_best, &lt;span class=&#34;built_in&#34;&gt;min&lt;/span&gt;(p_best_scores)&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;best_position, best_score = pso(num_particles, dimensions, bounds, max_iterations, w, c1, c2)&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;built_in&#34;&gt;print&lt;/span&gt;(&lt;span class=&#34;string&#34;&gt;f&amp;quot;Best Position: &lt;span class=&#34;subst&#34;&gt;&amp;#123;best_position&amp;#125;&lt;/span&gt;, Best Score: &lt;span class=&#34;subst&#34;&gt;&amp;#123;best_score&amp;#125;&lt;/span&gt;&amp;quot;&lt;/span&gt;)&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/figure&gt;
&lt;p&gt;在我的问题当中，粒子群优化算法的效果比前两者要慢很多，甚至有时候比暴力搜索还要慢。我们也容易看出粒子群优化算法的计算量是比较大的。&lt;/p&gt;
&lt;h1 id=&#34;总结&#34;&gt;&lt;a class=&#34;markdownIt-Anchor&#34; href=&#34;#总结&#34;&gt;&lt;/a&gt; 总结&lt;/h1&gt;
&lt;p&gt;这三种方法都是基于概率的优化算法。在比较复杂的优化问题中，往往比较难推导出一个可微的目标函数，以至于无法使用梯度下降法。这时候，这些启发式优化算法就显得非常有用了。&lt;/p&gt;
</content>
        <category term="2024" />
        <updated>2024-08-05T00:00:00.000Z</updated>
    </entry>
    <entry>
        <id>https://blog.soulter.top/posts/self-host-mailserver.html</id>
        <title>自部署邮件服务器</title>
        <link rel="alternate" href="https://blog.soulter.top/posts/self-host-mailserver.html"/>
        <content type="html">&lt;p&gt;最近和 @Rockchin 合资购买了一个位于日本东京的 4C8G 的高性能 VPS，用于部署一些服务。在这里记录一下自己搭建邮件服务器的过程。&lt;/p&gt;
&lt;h2 id=&#34;0x01-smtp-imap-pop3-协议&#34;&gt;&lt;a class=&#34;markdownIt-Anchor&#34; href=&#34;#0x01-smtp-imap-pop3-协议&#34;&gt;&lt;/a&gt; 0x01. SMTP、IMAP、POP3 协议&lt;/h2&gt;
&lt;p&gt;SMTP（Simple Mail Transfer Protocol）是用于发送邮件的位于应用层的协议，使用 TCP 协议作为传输层协议，端口号为 25。SMTP 协议规定了很多命令，它告诉客户端或服务器要采取什么操作及如何处理任何伴随的数据。比如&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;HELO：客户端向服务器标识自己&lt;/li&gt;
&lt;li&gt;MAIL FROM：指定邮件的发件人&lt;/li&gt;
&lt;li&gt;RCPT TO：指定邮件的收件人&lt;/li&gt;
&lt;li&gt;DATA：发送邮件内容&lt;/li&gt;
&lt;li&gt;QUIT：结束会话&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;客户端与服务器建立 TCP 连接后，客户端会首先发送一条 HELO 命令，然后服务器会返回一条 250 OK 命令，表示连接成功。可以使用 telnet 命令测试 SMTP 服务器是否正常工作：&lt;/p&gt;
&lt;figure class=&#34;highlight shell&#34;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&#34;gutter&#34;&gt;&lt;pre&gt;&lt;span class=&#34;line&#34;&gt;1&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&#34;code&#34;&gt;&lt;pre&gt;&lt;span class=&#34;line&#34;&gt;telnet smtp.idoknow.top 25&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/figure&gt;
&lt;p&gt;连接成功后，输入 HELO 命令。&lt;/p&gt;
&lt;figure class=&#34;highlight bash&#34;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&#34;gutter&#34;&gt;&lt;pre&gt;&lt;span class=&#34;line&#34;&gt;1&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;2&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;3&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;4&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;5&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;6&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;7&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&#34;code&#34;&gt;&lt;pre&gt;&lt;span class=&#34;line&#34;&gt;Trying 45.137.180.174...&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;Connected to smtp.idoknow.top.&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;Escape character is &lt;span class=&#34;string&#34;&gt;&amp;#x27;^]&amp;#x27;&lt;/span&gt;.&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;220-smtp.idoknow.top ESMTP&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;220 smtp.idoknow.top ESMTP&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;HELO smtp.idoknow.top&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;250 smtp.idoknow.top&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/figure&gt;
&lt;p&gt;我们要搭建的邮件服务器需要支持 SMTP 协议才能发送邮件。如今，随着 TLS/SSL 的普及，SMTP 服务器也支持了加密传输，形成 SMTPS 协议，端口号为 465。&lt;/p&gt;
&lt;p&gt;IMAP（Internet Message Access Protocol）和 POP3（Post Office Protocol 3）是用于接收邮件的协议。IMAP 和 POP3 都是基于 TCP 协议的应用层协议，IMAP 默认端口号为 143，POP3 默认端口号为 110。IMAP 和 POP3 协议规定了客户端如何从服务器上获取邮件。这两者的区别在于 IMAP 协议是双向的，可以在客户端和服务器之间同步邮件状态，而 POP3 协议是单向的，只能将邮件从服务器下载到客户端。这意味着标记邮件为已读、删除邮件等操作，如果使用 POP3 协议，只会在客户端上生效，不会同步到服务器上。因此我们更常用的是 IMAP 协议。&lt;/p&gt;
&lt;h2 id=&#34;0x02-搭建&#34;&gt;&lt;a class=&#34;markdownIt-Anchor&#34; href=&#34;#0x02-搭建&#34;&gt;&lt;/a&gt; 0x02. 搭建&lt;/h2&gt;
&lt;h3 id=&#34;添加-dns-记录&#34;&gt;&lt;a class=&#34;markdownIt-Anchor&#34; href=&#34;#添加-dns-记录&#34;&gt;&lt;/a&gt; 添加 DNS 记录&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;添加一条 A 记录：指定邮件服务器的 IP 地址。比如 smtp.idoknow.top。&lt;/li&gt;
&lt;li&gt;添加一条 MX 记录：指定邮件服务器的域名。比如 smtp.idoknow.top。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;MX 记录是邮件交换记录，它指定了接收邮件的服务器。当发送邮件时，邮件服务器会根据 MX 记录找到接收邮件的服务器。比如我们设置了 &lt;code&gt;idoknow.top&lt;/code&gt; 指向 &lt;code&gt;smtp.idoknow.top&lt;/code&gt;。因此我们最终的邮箱地址就可以是 &lt;code&gt;admin@idoknow.top&lt;/code&gt;。&lt;/p&gt;
&lt;p&gt;我们使用 &lt;a href=&#34;https://docker-mailserver.github.io/docker-mailserver/latest/&#34;&gt;docker-mailserver&lt;/a&gt; 来搭建邮件服务器，它是一个基于 Docker 的邮件服务器，支持 SMTP、IMAP、POP3 协议。我们可以使用 docker-compose 来管理容器。&lt;/p&gt;
&lt;h3 id=&#34;申请-ssl-证书&#34;&gt;&lt;a class=&#34;markdownIt-Anchor&#34; href=&#34;#申请-ssl-证书&#34;&gt;&lt;/a&gt; 申请 SSL 证书&lt;/h3&gt;
&lt;p&gt;我们使用了免费的 &lt;a href=&#34;https://letsencrypt.org/&#34;&gt;Let’s Encrypt&lt;/a&gt; 证书。官方提供了一个方便的工具 &lt;a href=&#34;https://certbot.eff.org/&#34;&gt;Certbot&lt;/a&gt;，它可以自动为我们申请证书。当然，由于我们在自己的服务器中使用了 caddy 作为反向代理，caddy 也支持自动申请证书，因此我们使用的是 caddy。&lt;/p&gt;
&lt;h3 id=&#34;下载-配置&#34;&gt;&lt;a class=&#34;markdownIt-Anchor&#34; href=&#34;#下载-配置&#34;&gt;&lt;/a&gt; 下载、配置&lt;/h3&gt;
&lt;p&gt;首先，前往 &lt;a href=&#34;https://github.com/docker-mailserver/docker-mailserver&#34;&gt;GitHub&lt;/a&gt; 下载 &lt;code&gt;compose.yaml&lt;/code&gt; 和 &lt;code&gt;mailserver.env&lt;/code&gt; 文件。&lt;/p&gt;
&lt;p&gt;修改 &lt;code&gt;compose.yaml&lt;/code&gt; 文件：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;hostname: &lt;a href=&#34;http://smtp.xxx.com&#34;&gt;smtp.xxx.com&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;volumes 处新挂载一个目录，用于存放 SSL 证书&lt;figure class=&#34;highlight yaml&#34;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&#34;gutter&#34;&gt;&lt;pre&gt;&lt;span class=&#34;line&#34;&gt;1&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&#34;code&#34;&gt;&lt;pre&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;bullet&#34;&gt;-&lt;/span&gt; &lt;span class=&#34;string&#34;&gt;/root/docker/caddy/config/caddy-data/caddy/certificates/acme-v02.api.letsencrypt.org-directory/smtp.idoknow.top/:/etc/letsencrypt&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/figure&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;宿主机中的证书路径请根据自己的实际情况修改，需要保证路径下直接含有 &lt;code&gt;xxx.com.crt&lt;/code&gt; 和 &lt;code&gt;xxx.com.key&lt;/code&gt; 两个文件。&lt;/p&gt;
&lt;p&gt;然后修改 &lt;code&gt;mailserver.env&lt;/code&gt; 文件：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;TZ=Asia/Shanghai&lt;/li&gt;
&lt;li&gt;SSL_TYPE=manual&lt;/li&gt;
&lt;li&gt;SSL_CERT_PATH=/etc/letsencrypt/smtp.idoknow.top.crt&lt;/li&gt;
&lt;li&gt;SSL_KEY_PATH=/etc/letsencrypt/smtp.idoknow.top.key&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;这里的时区和 SSL 证书的文件名请根据自己的实际情况修改。&lt;/p&gt;
&lt;p&gt;修改好后，使用 &lt;code&gt;docker compose up -d&lt;/code&gt; 启动容器。使用 &lt;code&gt;docker logs -f mailserver&lt;/code&gt; 查看日志，如果没有报错，说明启动成功。这时我们需要在 120s 内设置一个新的邮箱，否则容器会自动关闭。&lt;/p&gt;
&lt;figure class=&#34;highlight shell&#34;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&#34;gutter&#34;&gt;&lt;pre&gt;&lt;span class=&#34;line&#34;&gt;1&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&#34;code&#34;&gt;&lt;pre&gt;&lt;span class=&#34;line&#34;&gt;docker exec -it mailserver setup email add admin@idoknow.top&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/figure&gt;
&lt;h2 id=&#34;0x03-后期配置&#34;&gt;&lt;a class=&#34;markdownIt-Anchor&#34; href=&#34;#0x03-后期配置&#34;&gt;&lt;/a&gt; 0x03. 后期配置&lt;/h2&gt;
&lt;p&gt;现在，我们就成功搭建了一个邮件服务器，可以使用一些邮件客户端，比如 OS自带的、Outlook 等来连接到邮件服务器了。&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://cf.s3.soulter.top/SelfHost1-MailServer/image.png&#34; alt=&#34;&#34; /&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://cf.s3.soulter.top/SelfHost1-MailServer/image-1.png&#34; alt=&#34;&#34; /&gt;&lt;/p&gt;
&lt;p&gt;然而，我们一定要给我们的邮件服务器添加一些安全性配置，比如 SPF、DKIM、DMARC 等。这些配置可以提高邮件的送达率，减少被识别为垃圾邮件的概率。&lt;/p&gt;
&lt;p&gt;在接下来的操作中，我们可以使用 &lt;a href=&#34;https://dmarcguide.globalcyberalliance.org/&#34;&gt;https://dmarcguide.globalcyberalliance.org/&lt;/a&gt; 这个网站来检测我们是否成功配置了 SPF、DKIM、DMARC。&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://cf.s3.soulter.top/SelfHost1-MailServer/image-2.png&#34; alt=&#34;&#34; /&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://cf.s3.soulter.top/SelfHost1-MailServer/image-3.png&#34; alt=&#34;&#34; /&gt;&lt;/p&gt;
&lt;h3 id=&#34;spf&#34;&gt;&lt;a class=&#34;markdownIt-Anchor&#34; href=&#34;#spf&#34;&gt;&lt;/a&gt; SPF&lt;/h3&gt;
&lt;p&gt;SPF（Sender Policy Framework）是一种用于防范伪造邮件的技术，它通过 DNS 记录来验证发件人的 IP 地址是否合法。我们需要在 DNS 记录中添加一条 TXT 记录，记录值为 &lt;code&gt;v=spf1 a mx ip4:xxx.xxx.xxx.xxx -all&lt;/code&gt;。其中 &lt;code&gt;a&lt;/code&gt; 表示允许发件人的域名解析为发件人的 IP 地址，&lt;code&gt;mx&lt;/code&gt; 表示允许发件人的域名解析为发件人的 MX 记录，&lt;code&gt;ip4:xxx.xxx.xxx.xxx&lt;/code&gt; 表示允许发件人的 IP 地址为 &lt;code&gt;xxx.xxx.xxx.xxx&lt;/code&gt;。&lt;/p&gt;
&lt;p&gt;DNS 设置如下：&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://cf.s3.soulter.top/SelfHost1-MailServer/image-4.png&#34; alt=&#34;&#34; /&gt;&lt;/p&gt;
&lt;p&gt;即可应用 SPF。&lt;/p&gt;
&lt;p&gt;通过如下实验可以认识到 SPF 的作用：&lt;/p&gt;
&lt;figure class=&#34;highlight bash&#34;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&#34;gutter&#34;&gt;&lt;pre&gt;&lt;span class=&#34;line&#34;&gt;1&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;2&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;3&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;4&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&#34;code&#34;&gt;&lt;pre&gt;&lt;span class=&#34;line&#34;&gt;telnet smtp.idoknow.top 25&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;HELO smtp.idoknow.top&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;MAIL FROM:&amp;lt;elonmusk@x.com&amp;gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;RCPT TO:&amp;lt;soulter@idoknow.top&amp;gt;&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/figure&gt;
&lt;p&gt;我们在这里伪造了 &lt;a href=&#34;http://x.com&#34;&gt;x.com&lt;/a&gt; 的邮件地址，期望以 &lt;a href=&#34;mailto:elonmusk@x.com&#34;&gt;elonmusk@x.com&lt;/a&gt; 的名义发送垃圾邮件到 soulter@idoknow.top，但是由于 &lt;a href=&#34;http://x.com&#34;&gt;x.com&lt;/a&gt; 设置了 SPF。&lt;br /&gt;
&lt;img src=&#34;https://cf.s3.soulter.top/SelfHost1-MailServer/image-5.png&#34; alt=&#34;&#34; /&gt;&lt;br /&gt;
因此邮件服务器会拒绝这封邮件：&lt;/p&gt;
&lt;figure class=&#34;highlight bash&#34;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&#34;gutter&#34;&gt;&lt;pre&gt;&lt;span class=&#34;line&#34;&gt;1&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&#34;code&#34;&gt;&lt;pre&gt;&lt;span class=&#34;line&#34;&gt;550 5.7.23 &amp;lt;soulter@idoknow.top&amp;gt;: Recipient address rejected: Message rejected due to: SPF fail - not authorized.&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/figure&gt;
&lt;h3 id=&#34;dkim&#34;&gt;&lt;a class=&#34;markdownIt-Anchor&#34; href=&#34;#dkim&#34;&gt;&lt;/a&gt; DKIM&lt;/h3&gt;
&lt;p&gt;DKIM（DomainKeys Identified Mail）是一种用于验证邮件的技术，有助于防止违法人冒充发件人发送邮件。具体来说，DKIM 需要在 DNS 记录中添加一条 TXT 记录，记录值为 &lt;code&gt;v=DKIM1; k=rsa; p=xxx&lt;/code&gt;。其中 &lt;code&gt;v&lt;/code&gt; 表示 DKIM 的版本，&lt;code&gt;k&lt;/code&gt; 表示使用的加密算法，&lt;code&gt;p&lt;/code&gt; 表示公钥。所有从该域名发送的邮件都会包含一个 DKIM 标头，其中包含一段使用私钥加密的签名，接收邮件的服务器会使用公钥来验证签名的合法性。在没有 DNS 污染的情况下，这个过程可以保证邮件没有被任何中间人篡改。&lt;/p&gt;
&lt;p&gt;在 &lt;code&gt;docker-mailserver&lt;/code&gt; 中，使用&lt;/p&gt;
&lt;figure class=&#34;highlight bash&#34;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&#34;gutter&#34;&gt;&lt;pre&gt;&lt;span class=&#34;line&#34;&gt;1&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&#34;code&#34;&gt;&lt;pre&gt;&lt;span class=&#34;line&#34;&gt;docker &lt;span class=&#34;built_in&#34;&gt;exec&lt;/span&gt; -it mailserver setup config dkim&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/figure&gt;
&lt;p&gt;来生成 DKIM 的公钥和私钥。然后使用 &lt;code&gt;docker exec -it mailserver cat /tmp/docker-mailserver/opendkim/keys/idoknow.top/mail.txt&lt;/code&gt; 来查看 DKIM 的公钥，如下：&lt;/p&gt;
&lt;figure class=&#34;highlight bash&#34;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&#34;gutter&#34;&gt;&lt;pre&gt;&lt;span class=&#34;line&#34;&gt;1&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;2&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;3&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&#34;code&#34;&gt;&lt;pre&gt;&lt;span class=&#34;line&#34;&gt;mail._domainkey	IN	TXT	( &lt;span class=&#34;string&#34;&gt;&amp;quot;v=DKIM1; h=sha256; k=rsa; &amp;quot;&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;	  &lt;span class=&#34;string&#34;&gt;&amp;quot;p=MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEArOC9Y2APgcfDqRgHKO03dk1ysxRG355vkUD+75a7ghbNZen1H9WTHHCWdgQql2QZ5xhaJFFviBkgyE2zQTXtfnYkaV6+j4Q8/ij51/bgkEGjUVitZuGGvL8x1dGrYph19sYI6tSBgKkIZBG7M1lUtVU/Wt8KpUWklgDylX9v+rhL71mHgIqIhNjB41dj9YxPfXFfRsHBIDwntg&amp;quot;&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;	  &lt;span class=&#34;string&#34;&gt;&amp;quot;DlG1ClJC5NXMa94ru/Yl/Aba2Fw/nW4dxkcDq9XazkLMGYVWnG+knoKMwn33ii9yFl9p1RHjqIHIDSSBKNOnLyXhxhgTGqLXrCACsScJbOL20XQeq8PG7VsUX4sK8koUU4sdfrnwIDAQAB&amp;quot;&lt;/span&gt; )  ; ----- DKIM key mail &lt;span class=&#34;keyword&#34;&gt;for&lt;/span&gt; idoknow.top&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/figure&gt;
&lt;p&gt;然后添加 TXT 记录到 DNS 中：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;记录名为 &lt;code&gt;mail._domainkey&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;记录值为 () 当中的内容，并且去掉换行符、引号。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;在 Gmail 中，随便打开一个权威网站发来的邮件。点击 View Source，可以看到 DKIM 的签名。下面展示了 &lt;code&gt;noreply@telegram.org&lt;/code&gt; 发送给我的邮件的 DKIM 签名：&lt;/p&gt;
&lt;figure class=&#34;highlight bash&#34;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&#34;gutter&#34;&gt;&lt;pre&gt;&lt;span class=&#34;line&#34;&gt;1&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;2&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;3&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;4&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;5&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;6&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&#34;code&#34;&gt;&lt;pre&gt;&lt;span class=&#34;line&#34;&gt;DKIM-Signature: v=1; a=rsa-sha256; c=simple/simple; d=telegram.org; s=mail2; t=1721635112; bh=Cgi+lNIWbizWIJWQP/B/QYforTgBVB9G6u0qqoSXeWQ=; h=To:Subject:From:Date:From; b=b3Rq7sjIpTpMP3ZEpMudLhf9tcZ5jrm3K9vcCEfOPLMvsNZ6OB9mjSrF32xdPREMP&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;	 AegNrLYttPXdfyPyTdJVMaivyLQLKyEhIn2AxB9L5uWD6YhPdkL7mYM4EbaE6B+TXk&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;	 aRkA/LceHnweIkiIGuVz5aa9+RsgUMxbvLP5j41I3HjyFHKJe5+NH2hzxwFOECplgR&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;	 jC3BKZmFbkLWQwLG0u12cfnhRWNAAObhtZ52AVx+qVrpXjCJjY9bsqGgTykyuT94Ih&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;	 +4Wpno3WIqXGhFCUBQFyCRRbB8uZiRzhNeef9SPKGgkrt/H/bUiSxkc8728CV51Lij&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;	 tDODoxHVIstfQ==&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/figure&gt;
&lt;p&gt;其中，&lt;code&gt;h&lt;/code&gt; 的内容说明这个数字签名包含了哪部分的邮件内容。&lt;code&gt;bh&lt;/code&gt; 是邮件正文的 hash 值，用来在打开正文前就能计算签名。&lt;code&gt;b&lt;/code&gt; 是使用了私钥加密的数字签名。&lt;/p&gt;
&lt;h3 id=&#34;dmarc&#34;&gt;&lt;a class=&#34;markdownIt-Anchor&#34; href=&#34;#dmarc&#34;&gt;&lt;/a&gt; DMARC&lt;/h3&gt;
&lt;p&gt;DMARC（Domain-based Message Authentication, Reporting and Conformance）是一种用于验证邮件的技术，它可以防止伪造邮件。它需要 SPF 和 DKIM 的支持。&lt;/p&gt;
&lt;p&gt;和 SPF 一样，我们需要在 DNS 记录中添加一条 TXT 记录来应用 DMARC。具体的记录值请使用 &lt;a href=&#34;https://dmarcguide.globalcyberalliance.org/dmarc&#34;&gt;DMARC&lt;/a&gt; 生成。&lt;/p&gt;
</content>
        <category term="2024" />
        <updated>2024-07-23T00:00:00.000Z</updated>
    </entry>
    <entry>
        <id>https://blog.soulter.top/posts/astrbot-workflow-github.html</id>
        <title>AstrBot 开发记录 / Workflow 编写不当导致的更新异常</title>
        <link rel="alternate" href="https://blog.soulter.top/posts/astrbot-workflow-github.html"/>
        <content type="html">&lt;h2 id=&#34;前言&#34;&gt;&lt;a class=&#34;markdownIt-Anchor&#34; href=&#34;#前言&#34;&gt;&lt;/a&gt; 前言&lt;/h2&gt;
&lt;p&gt;GitHub Workflow 是一个非常强大的工具，可以帮助我们自动化很多工作，它能够在检测到我们推送的提交、合并请求等事件时，自动新建一个虚机来执行我们预先编写好的脚本。&lt;/p&gt;
&lt;p&gt;我的很多项目都使用了这个功能来帮助我执行自动化流程，比如这篇博客的渲染就是由 GitHub Workflow 来完成的。在我推送 Markdown 格式的博客内容后，它会首先签出到我最新的提交，然后调用 &lt;code&gt;hugo&lt;/code&gt; 的工具链来渲染成 HTML 页面，然后将打包好的文件夹压缩，通过 &lt;code&gt;scp&lt;/code&gt; 指令发送到我位于日本东京的生产服务器 &lt;code&gt;tokyo-neko&lt;/code&gt; 上，再执行相关操作后，网站上就会显示我写的博文——整个流程小于 1 分钟。而我要做的，只是专注于写作，然后 git push。&lt;/p&gt;
&lt;p&gt;此外，我使用它来自动发布 &lt;a href=&#34;https://github.com/Soulter/hugging-chat-api&#34;&gt;hugging-face-api&lt;/a&gt;的新版本到 PyPi 平台上，以及自动构建 &lt;a href=&#34;https://github.com/Sulter/Astrbot&#34;&gt;AstrBot&lt;/a&gt; Docker 镜像。&lt;/p&gt;
&lt;p&gt;而本篇文章要记录的，就是由于对 GitHub Workflow 中某个插件的不熟悉导致的 AstrBot Docker 端更新业务的大规模异常。&lt;/p&gt;
&lt;h2 id=&#34;问题的发现&#34;&gt;&lt;a class=&#34;markdownIt-Anchor&#34; href=&#34;#问题的发现&#34;&gt;&lt;/a&gt; 问题的发现&lt;/h2&gt;
&lt;p&gt;起因是群里有部分用户反馈 Docker 的 AstrBot 更新后无法正常运行。&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://cf.s3.soulter.top/astrbot-workflow-github/image.png&#34; alt=&#34;&#34; /&gt;&lt;/p&gt;
&lt;p&gt;排查之后，推送了一个修复版本，但是在测试尝试更新的时候，发现可视化面板页面一直显示 “正在检查更新”。&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://cf.s3.soulter.top/astrbot-workflow-github/image-1.png&#34; alt=&#34;&#34; /&gt;&lt;/p&gt;
&lt;p&gt;用户也反馈遇到了这个问题。&lt;/p&gt;
&lt;p&gt;于是我使用 &lt;code&gt;docker logs&lt;/code&gt; 查看了日志，发现显示了这么一句话：&lt;/p&gt;
&lt;figure class=&#34;highlight shell&#34;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&#34;gutter&#34;&gt;&lt;pre&gt;&lt;span class=&#34;line&#34;&gt;1&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&#34;code&#34;&gt;&lt;pre&gt;&lt;span class=&#34;line&#34;&gt;Username for &amp;#x27;https://github.com&amp;#x27;: &lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/figure&gt;
&lt;p&gt;这是 AstrBot 在调用 git 指令时，需要输入用户名的提示。&lt;/p&gt;
&lt;p&gt;然而，AstrBot 是 GitHub 上的一个 Public 的项目，拉取代码是不需要输入用户名的。&lt;/p&gt;
&lt;h2 id=&#34;问题的分析&#34;&gt;&lt;a class=&#34;markdownIt-Anchor&#34; href=&#34;#问题的分析&#34;&gt;&lt;/a&gt; 问题的分析&lt;/h2&gt;
&lt;p&gt;我猜测是 GitHub Workflow 在签出仓库时，将我的账号的某些令牌数据写入了某个 .git/ 下的配置文件，然后令牌过期了，导致了这个问题。下面的实验也佐证了我的猜测。&lt;/p&gt;
&lt;p&gt;我重新构建了一次 Workflow，代码签出到早期版本的 AstrBot。然后在测试机上拉取该新构建的镜像来运行，执行更新操作，发现可以正常更新——说明肯定是有一个令牌的机制导致了这个问题。&lt;/p&gt;
&lt;h2 id=&#34;对-git-文件夹的深入分析&#34;&gt;&lt;a class=&#34;markdownIt-Anchor&#34; href=&#34;#对-git-文件夹的深入分析&#34;&gt;&lt;/a&gt; 对 .git 文件夹的深入分析&lt;/h2&gt;
&lt;p&gt;.git 文件夹是所有使用 git 进行版本管理的项目都会有的，它存储了 git 的一些配置信息，比如分支信息、提交信息等。由于以 . 开头，因此他在大多数文件资源管理器中是隐藏的。具体来说，.git 文件夹包含有如下文件：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;HEAD：指向当前签出的分支的引用，一般内容是 &lt;code&gt;ref: refs/heads/main&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;COMMIT_EDITMSG：存储了最近一次提交的 message 文本。&lt;/li&gt;
&lt;li&gt;FETCH_HEAD：存储了最近一次 fetch 的信息：commit_id、分支名、仓库地址（remotes）等。&lt;/li&gt;
&lt;li&gt;ORIG_HEAD：存储了最近一次 HEAD 的 commit_id。&lt;/li&gt;
&lt;li&gt;discritpion：存储了仓库的描述信息。&lt;/li&gt;
&lt;li&gt;config：存储了 git 的配置信息&lt;/li&gt;
&lt;li&gt;hooks/：存储了 git 的钩子脚本，用于在 git 的生命周期中执行一些操作，比如 pre-commit、pre-push 等。默认情况下，这个文件夹下有一些示例脚本：
&lt;ul&gt;
&lt;li&gt;
&lt;pre class=&#34;highlight&#34;&gt;&lt;code class=&#34;&#34;&gt;pre-commit.sample
pre-push.sample
...
&lt;figure class=&#34;highlight plaintext&#34;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&#34;gutter&#34;&gt;&lt;pre&gt;&lt;span class=&#34;line&#34;&gt;1&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;2&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;3&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;4&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;5&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;6&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;7&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;8&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;9&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;10&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;11&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;12&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;13&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;14&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;15&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;16&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;17&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;18&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;19&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;20&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;21&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;22&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;23&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;24&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;25&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;26&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;27&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;28&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;29&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;30&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&#34;code&#34;&gt;&lt;pre&gt;&lt;span class=&#34;line&#34;&gt;- index：存储了暂存区的信息，是一个二进制文件。&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;- logs/：存储了 git 的日志信息，比如 HEAD 的变更记录。&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;- refs/：存储了 git 的引用信息，比如分支、标签等。&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;- objects/：存储了 git 的对象信息，比如 commit、tree、blob 等，是 git 的核心。&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;![](https://cf.s3.soulter.top/astrbot-workflow-github/image-2.png)&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;而我们问题的罪魁祸首就出现在这个 .git 文件夹下的 config 文件中。&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;config 文件内容以 ini 格式存储，包含了 git 的一些配置信息。&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;比如我的博客项目的 config 文件内容如下：&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;```ini&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;[core] // 核心配置&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;        repositoryformatversion = 0&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;        filemode = true&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;        bare = false&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;        logallrefupdates = true&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;        ignorecase = true&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;        precomposeunicode = true&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;[submodule &amp;quot;themes/paper&amp;quot;] // 子模块配置&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;        url = https://github.com/nanxiaobei/hugo-paper&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;        active = true&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;[remote &amp;quot;origin&amp;quot;] // 远程仓库配置&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;        url = https://github.com/Soulter/SoulterBlogv3.git&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;        fetch = +refs/heads/*:refs/remotes/origin/*&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;[branch &amp;quot;main&amp;quot;] // 分支配置，记录了当前分支的一些信息&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;        remote = origin&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;        merge = refs/heads/main&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/figure&gt;


&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;而在我调研异常版本的 AstrBot 的 config 文件中，发现了这么一段：&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://cf.s3.soulter.top/astrbot-workflow-github/image-3.png&#34; alt=&#34;&#34; /&gt;&lt;/p&gt;
&lt;p&gt;&lt;code&gt;http &amp;quot;https://github.com/&amp;quot;&lt;/code&gt; 节下的内容是我在 GitHub 上的令牌，这个令牌是使用 GitHub Workflow 的 &lt;code&gt;actions/checkout@v2&lt;/code&gt; 这一个插件时，自动在 .git/config 文件中写入的。&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://cf.s3.soulter.top/astrbot-workflow-github/image-4.png&#34; alt=&#34;&#34; /&gt;&lt;/p&gt;
&lt;p&gt;而我在 docker build 指令中，直接将 AstrBot 下的所有文件打包进了镜像，包括了 .git 文件夹。&lt;/p&gt;
&lt;p&gt;这就导致了用户在使用该镜像时，与 git 之间的交互都走的是我的账号！当令牌过期后，git 将该令牌发送给 github，而 github 识别到这是一个过期令牌，就返回需要重新验证账户的响应。这是一个巨大的安全隐患，如果被恶意利用，我的账号可能就不复存在了。&lt;/p&gt;
&lt;h2 id=&#34;解决方案&#34;&gt;&lt;a class=&#34;markdownIt-Anchor&#34; href=&#34;#解决方案&#34;&gt;&lt;/a&gt; 解决方案&lt;/h2&gt;
&lt;p&gt;直接使用 git clone 指令拉取代码，以代替 GitHub Workflow 的 &lt;code&gt;actions/checkout@v2&lt;/code&gt; 这一个插件。&lt;/p&gt;
</content>
        <category term="2024" />
        <updated>2024-06-09T00:00:00.000Z</updated>
    </entry>
    <entry>
        <id>https://blog.soulter.top/posts/2024-4-monthly-record.html</id>
        <title>4 月 / 美妙人生的关键在于你能迷上什么东西</title>
        <link rel="alternate" href="https://blog.soulter.top/posts/2024-4-monthly-record.html"/>
        <content type="html">&lt;p&gt;这么快 2024 年就过去三分之一了。趁五月还没来，抓住四月的尾巴来记录一下自己的这前四个月的经历。&lt;/p&gt;
&lt;p&gt;说到这几个月最重大的事情，应该就是第一次出国旅行和确定了自己以后的探索方向吧。人们都常说「多走出去看看，增长见识」。这是有他的道理的，在日本玩了两周之后，视野不知不觉间就开阔起来了，发现很多之前纠结的问题都迎来了终结。很多想不通的问题其实换个角度去想就会迎刃而解。&lt;/p&gt;
&lt;p&gt;在二月左右，偶然之间玩了「ATRI」这款游戏，这个本来是我在番荒之时用来打发时间的游戏，没想到却改变了我以后的探索方向，成为了我这三年最重大的一个人生转折点之一。在游玩之前，我还在纠结于是直接就业还是读研深造，走数据库的研究方向。从就业的角度来说，我已经有过好几段实习经历，并且还拿到了字节和美团的 Offer。&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://cf.s3.soulter.top/record_2024_04/image-17.png&#34; alt=&#34;&#34; /&gt;&lt;/p&gt;
&lt;p&gt;从读研深造的角度来说，我成绩不差，当时在专业里是第三名，能够直接保研，并且家里也倾向于我去读研。而在我玩过这个游戏之后，满脑子只有一个想法——好想创造一个现实版的 ATRI 出来啊！！！&lt;/p&gt;
&lt;p&gt;于是，我开始学习机器学习、深度学习。在学习之余，使用「GPT-SoViTs」和一些 LLM 的函数调用创造了「ATRI ver0.00001」，说是 ATRI，其实就只会根据你的询问和写好的 prompt 生成对应的文本，然后生成和游戏中 ATRI 相同音色的语音回复罢了，非常简单。不过在做出来之后，还是非常高兴。&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://cf.s3.soulter.top/record_2024_04/image.png&#34; alt=&#34;&#34; /&gt;&lt;/p&gt;
&lt;p&gt;之后为了模仿对话，通过自己的 AstrBot 项目接入到了 QQ 中&lt;/p&gt;
&lt;p&gt;在此期间，收到了来自腾讯的面试，方向是自己很感兴趣的 QQ 机器人，也是因为这个原因吧，中途有一段时间非常内耗。面试官最后的意思就是说很希望能来（毕竟我就是做这个的哈哈哈），也就是口头 Offer 了。不过由于和夏令营冲突，最终还是狠心放弃了。至此，成功拒掉了三家所谓的互联网大厂的 offer（难绷）。&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://cf.s3.soulter.top/record_2024_04/image-1.png&#34; alt=&#34;&#34; /&gt;&lt;/p&gt;
&lt;p&gt;总之，我的梦想就是创造一个具有感知、决策的人型机器人了。在科研方向上也找到了和这个类似的名词：「具身智能」。我一定会朝着这个大方向不断努力的。虽然让我产生这个想法的原因非常幼稚，虽然我现在对于实现这个梦想所需要的知识储备少得可怜。&lt;/p&gt;
&lt;p&gt;现在在中科院自动化所实习也是做这方面的，希望能学到点东西吧！&lt;br /&gt;
&lt;img src=&#34;https://cf.s3.soulter.top/record_2024_04/image-10.png&#34; alt=&#34;&#34; /&gt;&lt;/p&gt;
&lt;p&gt;让我们换些话题吧！&lt;/p&gt;
&lt;p&gt;过年后，动身去学校前，和高中同学们小聚了一次。这是我们每年的传统了，在每年的暑假和寒假，开学前一两个星期都会聚一次，话话大家各自的琐事。真的很喜欢这种和好兄弟们团聚时的氛围，能够毫无压力地将自己的情绪和经历表达出来，互相水水以前高中的生活，八卦谁谁谁又怎么样了。以至于分别前在马路边大家还迟迟不愿散场。希望大家都能过上自己想要的人生吧。&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://cf.s3.soulter.top/record_2024_04/image-2.png&#34; alt=&#34;&#34; /&gt;&lt;/p&gt;
&lt;p&gt;幸运的是，在大学期间我也遇到了一群好兄弟们。我们习惯每隔几周就出去玩一次。&lt;/p&gt;
&lt;p&gt;自助烤肉！&lt;br /&gt;
&lt;img src=&#34;https://cf.s3.soulter.top/record_2024_04/image-4.png&#34; alt=&#34;&#34; /&gt;&lt;/p&gt;
&lt;p&gt;忘了叫什么了！&lt;br /&gt;
&lt;img src=&#34;https://cf.s3.soulter.top/record_2024_04/image-3.png&#34; alt=&#34;&#34; /&gt;&lt;/p&gt;
&lt;p&gt;传说中的大水法！&lt;br /&gt;
&lt;img src=&#34;https://cf.s3.soulter.top/record_2024_04/image-5.png&#34; alt=&#34;&#34; /&gt;&lt;/p&gt;
&lt;p&gt;还不错的一家川菜馆！&lt;br /&gt;
&lt;img src=&#34;https://cf.s3.soulter.top/record_2024_04/image-6.png&#34; alt=&#34;&#34; /&gt;&lt;/p&gt;
&lt;p&gt;《周处除三害》！&lt;br /&gt;
&lt;img src=&#34;https://cf.s3.soulter.top/record_2024_04/image-7.png&#34; alt=&#34;&#34; /&gt;&lt;/p&gt;
&lt;p&gt;《沙丘 2》一个人看的awa&lt;br /&gt;
&lt;img src=&#34;https://cf.s3.soulter.top/record_2024_04/image-8.png&#34; alt=&#34;&#34; /&gt;&lt;/p&gt;
&lt;p&gt;《你想活出怎样的人生》一个人看的awa *2&lt;br /&gt;
&lt;img src=&#34;https://cf.s3.soulter.top/record_2024_04/image-9.png&#34; alt=&#34;&#34; /&gt;&lt;/p&gt;
&lt;p&gt;不得不提到人生中的第二次去漫展的经历了，我还是太社恐了，出发前找室友借了相机，信誓旦旦说要拍好多漂亮 coser，结果到了现场却唯唯诺诺…&lt;/p&gt;
&lt;p&gt;在朋友的助攻下才集到一个邮qwq，也是年轻人第一张邮qwq&lt;br /&gt;
&lt;img src=&#34;https://cf.s3.soulter.top/record_2024_04/image-11.png&#34; alt=&#34;&#34; /&gt;&lt;/p&gt;
&lt;p&gt;避雷漫展的 30s 吃面包挑战！！在挑战前看到只要花 20 元，在 30 秒内吃完 1 块吐司面包就可以任选一个手办带回家，试了之后才知道不是这么简单。。那个是全麦面包，非常难嚼，别说 30s 了，一分钟都吃不完。&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://cf.s3.soulter.top/record_2024_04/image-12.png&#34; alt=&#34;&#34; /&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://cf.s3.soulter.top/record_2024_04/image-13.png&#34; alt=&#34;&#34; /&gt;&lt;/p&gt;
&lt;p&gt;网易云的一起听功能真不错，会根据你的听歌偏好匹配和你相似的人。偶然认识了两个老哥，互相加了网易云好友，现在每天晚上都会邀请我一起听歌。&lt;br /&gt;
&lt;img src=&#34;https://cf.s3.soulter.top/record_2024_04/image-18.png&#34; alt=&#34;&#34; /&gt;&lt;/p&gt;
&lt;p&gt;最近心血来潮，再次装饰了一下自己的宿舍工位 😄&lt;br /&gt;
&lt;img src=&#34;https://cf.s3.soulter.top/record_2024_04/image-14.png&#34; alt=&#34;&#34; /&gt;&lt;/p&gt;
&lt;p&gt;从 B 站上刷到的一个桌搭视频里借来的灵感，直接把笔记本竖起来当作副屏：&lt;br /&gt;
&lt;img src=&#34;https://cf.s3.soulter.top/record_2024_04/image-15.png&#34; alt=&#34;&#34; /&gt;&lt;/p&gt;
&lt;p&gt;蓝桥杯成绩出来了，最终获得了省二，有点可惜没有进省一（想体验一下 OI 国赛hhh），在考前基本上就没有准备过算法，要是早点准备就好了。不过为了即将到来的夏令营，我要开始刷算法题了！！！洛谷，启动！&lt;/p&gt;
&lt;p&gt;还是 B 站，最近在 B 站首页刷到一个父子弹吉他的视频，发现是自己早就关注的一对父子了。建议看完。&lt;/p&gt;
&lt;iframe style=&#34;width: 100%; height: 300px&#34; src=&#34;//player.bilibili.com/player.html?aid=1102307334&amp;bvid=BV1sA4m1N75x&amp;cid=1487371854&amp;p=1&amp;autoplay=0&#34; scrolling=&#34;no&#34; border=&#34;0&#34; frameborder=&#34;no&#34; framespacing=&#34;0&#34; allowfullscreen=&#34;true&#34;&gt; &lt;/iframe&gt;
&lt;p&gt;看多了被精心设计的视频，突然发现如此淳朴、简单的视频也能带来如此丰富的体验。刚吃完午饭，坐在餐桌前，手里端着米酒，父子俩随口就唱了起来，把牙签盒当作沙锤，闭着眼睛，全身心地将自己融入在歌声中。他们是真正乐在其中的。没有精心的提前准备，没有华丽的背景，他们最普通的录制方式和娴熟高超的技艺依然赢得了大家的喝彩。我也时常感叹这样的家庭，如此融洽，如此热爱音乐，一起演奏。想给这对父子充电却意外地发现他们都没有开放充电渠道。&lt;/p&gt;
&lt;p&gt;刘慈欣的《球状闪电》里有一句话让我印象深刻：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;美妙人生的关键在于你能迷上什么东西。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;美妙人生的关键不就是这样吗？找到一件或者多件自己热爱的事情，然后切实去做，并且不在意别人的评价，自己乐在其中，这正是生活的精髓所在。无论是失败还是成功，我们都能从中获得无穷的满足感和成就感。&lt;/p&gt;
</content>
        <category term="2024" />
        <updated>2024-04-30T00:00:00.000Z</updated>
    </entry>
    <entry>
        <id>https://blog.soulter.top/posts/gal-comment-%E6%98%9F%E7%A9%BA%E5%88%97%E8%BD%A6%E4%B8%8E%E7%99%BD%E7%9A%84%E6%97%85%E8%A1%8C.html</id>
        <title>Gal 评 / 像星空列车一样，坚定地走下去吧。</title>
        <link rel="alternate" href="https://blog.soulter.top/posts/gal-comment-%E6%98%9F%E7%A9%BA%E5%88%97%E8%BD%A6%E4%B8%8E%E7%99%BD%E7%9A%84%E6%97%85%E8%A1%8C.html"/>
        <content type="html">&lt;blockquote&gt;
&lt;p&gt;本文涉及剧透，会严重损害你的游戏体验。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;audio id=&#34;audio&#34; controls=&#34;&#34; preload=&#34;none&#34;&gt;
      &lt;source id=&#34;m4a&#34; src=&#34;https://cf.s3.soulter.top/gal-comment-星空列车与白的旅行/gal_shiro.m4a&#34;&gt;
      Your browser does not support the audio element.
&lt;/audio&gt;
&lt;h2 id=&#34;前言&#34;&gt;&lt;a class=&#34;markdownIt-Anchor&#34; href=&#34;#前言&#34;&gt;&lt;/a&gt; 前言&lt;/h2&gt;
&lt;p&gt;最近几个月逐渐喜欢上了玩 Galgame。其实我从初中就或多或少接触到一些 Galgame 了。只不过到今年之前，都还只把这类游戏当作毫无价值的 “小黄油” 来看待。而在寒假游玩了《ATRI - My Dear Moments》这款 Galgame 之后，我才逐渐明白所谓的小黄油只是 Galgame 中的一个子集，很多优秀作品是没有（或者很少）这种剧情的。我们由于自身对事物的不全面的认知导致我们对它们抱有偏见，带上了有色的眼睛去看待它们。甚至…在我推完 ATRI 和本文要介绍的作品之后都不由得感叹 Galgame 也能有如此丰富且深邃的世界观。虽然很多动漫都是从 Galgame、轻小说、漫画动画化而来的，可在我以前看来，动漫相比这些更能称得上是「完整的作品」。当然，现在已经不这么认为了。&lt;/p&gt;
&lt;p&gt;一部文学作品要打动人心，可能并不更多在于他的物理层面的表现形式。从奔走相告的口头故事，到一本纸质小说，再到鸿篇巨制的好莱坞电影，都有打动人心的能力。关键在于作者想要传达什么，读者想要从中领悟什么。&lt;/p&gt;
&lt;p&gt;从字里行间中感受到作者想要传达的东西，并幡然醒悟——这不就是自己吗？并将自己带入作品，开始将自己的世界观与作品的世界观结合，形成共鸣。这个过程不断重复，就打动人心了。&lt;/p&gt;
&lt;p&gt;所以很多作品金玉其外，声称采用了最先进的技术、最高级的制作团队、大量的资金投入，然而剧情内核脱离社会、人们无法与之共鸣，最后销量惨淡就是这个道理。技术、制作和资金是打造好作品的一部分，但想要打造好作品，更需要能够让人们产生共鸣的剧情内核做支撑。&lt;/p&gt;
&lt;h2 id=&#34;浅谈星白&#34;&gt;&lt;a class=&#34;markdownIt-Anchor&#34; href=&#34;#浅谈星白&#34;&gt;&lt;/a&gt; 浅谈星白&lt;/h2&gt;
&lt;p&gt;&lt;img src=&#34;https://cf.s3.soulter.top/gal-comment-%E6%98%9F%E7%A9%BA%E5%88%97%E8%BD%A6%E4%B8%8E%E7%99%BD%E7%9A%84%E6%97%85%E8%A1%8C/image.png&#34; alt=&#34;alt text&#34; /&gt;&lt;/p&gt;
&lt;p&gt;可能是我文学功底还不深厚，这部作品直到快结尾之前，我都没能猜到他的结局，让我觉得这是一部“欧亨利”式的作品。到了快结尾的某一个点，突然间领悟到了剧情的核心与贯穿全文的线索，然后不由得赞叹作者设计此剧情的精妙之处。&lt;/p&gt;
&lt;p&gt;这里仅花极短的篇幅快速介绍故事背景。&lt;/p&gt;
&lt;h3 id=&#34;缘起&#34;&gt;&lt;a class=&#34;markdownIt-Anchor&#34; href=&#34;#缘起&#34;&gt;&lt;/a&gt; 缘起&lt;/h3&gt;
&lt;p&gt;故事全文都发生在立春过后的一架 SL（蒸汽）列车上，其名曰「星空列车」。主要角色一共七人：空（主人公，男）、诺瓦、狩叶（乘务员）、音理（主人公的妹妹，因车祸去世）、花江、野鸟、鹰世。&lt;/p&gt;
&lt;p&gt;主人公是一个动画师，鹰世（男）是一个列车维修工，极度热爱 SL 列车。主人公和鹰世一样都是社会分工中类似中介的存在。野鸟（女）性格活泼，是一个瑞士人。花江（女）温柔知性，喜欢抽烟。狩叶（女）开朗活泼，未成年。&lt;/p&gt;
&lt;p&gt;野鸟家庭不允许吃肉，然而她到了日本之后在吃了肉之后发现很美味，但也因此害怕回家。&lt;/p&gt;
&lt;p&gt;故事起因是空工作劳累，闲暇之余在网络上看到了「星空列车」旅游项目，即在晚上乘坐上世纪的蒸汽列车欣赏星空。空想要解压，于是报名了该项目。&lt;/p&gt;
&lt;p&gt;启程前，他在站台上看到了一只黑猫，他没有关注太多，可他不知道的是接下来的故事都以这只猫展开。&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://cf.s3.soulter.top/gal-comment-%E6%98%9F%E7%A9%BA%E5%88%97%E8%BD%A6%E4%B8%8E%E7%99%BD%E7%9A%84%E6%97%85%E8%A1%8C/image-2.png&#34; alt=&#34;&#34; /&gt;&lt;/p&gt;
&lt;p&gt;上车后，他发现加上自己也只有五人，并且乘务员居然是一个未成年的女生。五人分布在&lt;strong&gt;每一节&lt;/strong&gt;车厢（这是一个伏笔）。简单地和他们寒暄过后，空回到了自己的车厢。&lt;/p&gt;
&lt;p&gt;紧接着他遇到了诺瓦（Noir），一只长着猫耳朵、猫尾巴的女孩，她很怕生，以至于没办法与之正常谈话…像猫一样。不过在后来，他与男主也慢慢熟了起来。&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://cf.s3.soulter.top/gal-comment-%E6%98%9F%E7%A9%BA%E5%88%97%E8%BD%A6%E4%B8%8E%E7%99%BD%E7%9A%84%E6%97%85%E8%A1%8C/image-4.png&#34; alt=&#34;&#34; /&gt;&lt;/p&gt;
&lt;p&gt;风轻轻吹过，柔和地拍打在脸上，没有光污染，望着窗外的浩瀚星海，真惬意。不觉得很美吗？夜晚的火车之旅，夜晚的银河之旅，一曲献给银河铁道之夜的情歌。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;不知晓美丽事物的人生是不幸的，能与美丽事物邂逅的人生是幸福的。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;img src=&#34;https://cf.s3.soulter.top/gal-comment-%E6%98%9F%E7%A9%BA%E5%88%97%E8%BD%A6%E4%B8%8E%E7%99%BD%E7%9A%84%E6%97%85%E8%A1%8C/image-1.png&#34; alt=&#34;&#34; /&gt;&lt;/p&gt;
&lt;p&gt;慢慢地，车上的六人逐渐破冰，打成一片。&lt;/p&gt;
&lt;p&gt;到了第一站，一片森林。野鸟开始发现这片森林与自己的家乡的森林很相似，有自己&lt;strong&gt;很喜欢&lt;/strong&gt;的味道。男主游完，在回站的时候，隐约听见了“来找我吧…”的声音。&lt;/p&gt;
&lt;p&gt;回到列车上后，发现第一节车厢的蒸汽炉前的桌子上出现了一本写生本。&lt;/p&gt;
&lt;p&gt;到了第二站，沙滩与海。众人在这里烧烤，友情持续升温。&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://cf.s3.soulter.top/gal-comment-%E6%98%9F%E7%A9%BA%E5%88%97%E8%BD%A6%E4%B8%8E%E7%99%BD%E7%9A%84%E6%97%85%E8%A1%8C/image-3.png&#34; alt=&#34;&#34; /&gt;&lt;/p&gt;
&lt;p&gt;真好啊，这场旅途，结识了很多人，大家都很友善。&lt;/p&gt;
&lt;p&gt;在晚上，大家放起了烟花。由于诺瓦害怕大型烟花，于是空给了她一根小小的铁线烟花。&lt;/p&gt;
&lt;p&gt;诺瓦从没吃过烧烤，从没见过大海，也从没放过烟花，体弱多病的她从一出生就没有体验到大多数人体验过的快乐。不过在今天，这些都被体验到了。空希望诺瓦以后勇于体验各种事情，美丽的、好吃的…&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;这个世界或许就是一个装满熠熠生辉的宝石的宝石箱，我却逐渐感受不到宝石的价值，我已成定局，或许这就是成长的代价。可诺瓦不一样。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;作者在诺瓦身上看到了音理的身影（这里也是伏笔，这个伏笔在推完整部作品之前都想不到）。音理是作者房东家的女儿，整天都玩在一起，因此结下了深厚的感情。可有一天，在音理找完作者回家后，遇到了车祸去世了。&lt;/p&gt;
&lt;p&gt;在这里，空与诺瓦结下了一个约定，如果诺瓦结识新朋友，空就给诺瓦也画一幅画。（这里还是一个伏笔）&lt;/p&gt;
&lt;p&gt;大家都玩得很欢乐。&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;不过鹰世（右一）还是发现了异常——这片沙滩，不论是形状，还是灯塔，都与自己的家乡相似，甚至灯塔墙壁上的印记都一模一样——而他的家乡在一个小岛上，根本不可能有铁轨通往日本岛。&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://cf.s3.soulter.top/gal-comment-%E6%98%9F%E7%A9%BA%E5%88%97%E8%BD%A6%E4%B8%8E%E7%99%BD%E7%9A%84%E6%97%85%E8%A1%8C/image-5.png&#34; alt=&#34;&#34; /&gt;&lt;br /&gt;
到了第三站，大家一眼就看出了这里是函馆——也就是花江的故乡。大家一起前往了星空馆。&lt;/p&gt;
&lt;p&gt;就在这时，意想不到的事情发生了。&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://cf.s3.soulter.top/gal-comment-%E6%98%9F%E7%A9%BA%E5%88%97%E8%BD%A6%E4%B8%8E%E7%99%BD%E7%9A%84%E6%97%85%E8%A1%8C/03ceca08011833ae52d2c32c00e30bbb.png&#34; alt=&#34;alt text&#34; /&gt;&lt;/p&gt;
&lt;p&gt;一个白发少女，全身都是惨淡的白色，眼睛发红，这与空过去几天每天晚上都梦到的那位少女外观一样。空意识到她的脸与诺瓦的实在是太像了，简直就是一模一样。&lt;/p&gt;
&lt;p&gt;那位少女语气激烈，抓住了男主的手，男主顿时跪在地上喘不过气。陷入了昏迷。最终醒来之后发现大家都在担心。&lt;/p&gt;
&lt;p&gt;傍晚回到列车上后，大家开始了复盘。几个人逐渐发现了异常：从第一站开始，就没有见过其他人，森林也好、沙滩也好。并且如果是直行，两三天的车程足以把大家从日本本州地方运送到北海道，可是这里完全不像北海道的样子。鹰世也发现了：列车从一开始就在不停地轻微地转弯——似乎列车在一直绕圈！&lt;/p&gt;
&lt;p&gt;空主也意识到，自己当时在网上订购时，车票根本就没写明目的地。&lt;/p&gt;
&lt;p&gt;他尝试打开手机，却发现新闻动态都停止在了某一个时间节点，虽然有信号，但是无法与外界通讯。&lt;/p&gt;
&lt;p&gt;野鸟、花江也意识到，车上自带的书都是自己特别喜欢的。&lt;/p&gt;
&lt;p&gt;越来越多难以解释的事情发生了。众人纷纷怀疑自己进入了异世界。但由于所有异常都没有对自己发生危害，因此大家都还是没有太放在心上。&lt;/p&gt;
&lt;p&gt;那位白发少女是谁？与诺瓦是什么关系？&lt;/p&gt;
&lt;p&gt;…&lt;/p&gt;
&lt;p&gt;高潮就从这里开始。&lt;/p&gt;
&lt;h3 id=&#34;高潮&#34;&gt;&lt;a class=&#34;markdownIt-Anchor&#34; href=&#34;#高潮&#34;&gt;&lt;/a&gt; 高潮&lt;/h3&gt;
&lt;audio id=&#34;audio&#34; controls=&#34;&#34; preload=&#34;none&#34;&gt;
      &lt;source id=&#34;m4a&#34; src=&#34;https://cf.s3.soulter.top/gal-comment-星空列车与白的旅行/gal_shiro.m4a&#34;&gt;
      Your browser does not support the audio element.
&lt;/audio&gt;
(BGM: スタートリップ)
&lt;p&gt;到了第四站，空发现这里是自己的故乡——密密麻麻的住宅，仍然空无一人。&lt;/p&gt;
&lt;p&gt;空飞奔到自己家，发现也空无一人。但是也发现了此时的时间——立春过后——音理出车祸去世的当天。&lt;/p&gt;
&lt;p&gt;恐惧感袭来。&lt;/p&gt;
&lt;p&gt;他意识到在第一站听到的“来找我吧，就在心脏处。”，明白了所指的心脏就是列车的蒸汽炉所在的车厢。最终，在这里找到了已经去世的音理。&lt;/p&gt;
&lt;p&gt;激动。&lt;/p&gt;
&lt;p&gt;但是音理看到其他人后，便逃走了。空好不容易追上她，却发现这里就是车祸现场。&lt;/p&gt;
&lt;p&gt;恐惧。&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://cf.s3.soulter.top/gal-comment-%E6%98%9F%E7%A9%BA%E5%88%97%E8%BD%A6%E4%B8%8E%E7%99%BD%E7%9A%84%E6%97%85%E8%A1%8C/image-6.png&#34; alt=&#34;&#34; /&gt;&lt;/p&gt;
&lt;p&gt;在现场，音理跟他说了很多，然后跟他讲了一个故事，下一秒就消失了。&lt;/p&gt;
&lt;p&gt;音理对空说的每一个故事的主人公都非常「可怜」，这是第四个，也是最后一个。空一下子就明白了这第四个故事说的是鹰世。前几个也都是列车上的几个人。包括自己。&lt;/p&gt;
&lt;p&gt;后来，众人进入了车祸现场的医院——这座城市唯一没有上锁的建筑，其他的建筑都被上锁了。这里是音理出车祸时动手术的医院，是狩叶的姐姐工作的地方…也是诺瓦以前做手术的地方。&lt;/p&gt;
&lt;p&gt;到了医院偏顶的某一层，空打开了一个房间的门，门牌号写着“夜羽真白” —— 这是诺瓦在某一天晚上跟空坦言的自己的真名。&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://cf.s3.soulter.top/gal-comment-%E6%98%9F%E7%A9%BA%E5%88%97%E8%BD%A6%E4%B8%8E%E7%99%BD%E7%9A%84%E6%97%85%E8%A1%8C/image-7.png&#34; alt=&#34;&#34; /&gt;&lt;/p&gt;
&lt;p&gt;进入房间后，发现这里就是自己梦境中的那个白发少女的房间。这时，诺瓦进来了，她坦言这里就是自己的房间。&lt;/p&gt;
&lt;p&gt;突然，诺瓦痛苦地大叫起来，变成了白发少女的模样，也就是梦中的白发少女，也是在星空馆袭击作者的白发少女。&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://cf.s3.soulter.top/gal-comment-%E6%98%9F%E7%A9%BA%E5%88%97%E8%BD%A6%E4%B8%8E%E7%99%BD%E7%9A%84%E6%97%85%E8%A1%8C/fa25f079ed40d2343b8609ddcfd701e4.png&#34; alt=&#34;&#34; /&gt;&lt;/p&gt;
&lt;p&gt;白发少女叫夜羽真白。她对众人解释了这一切的原因：&lt;/p&gt;
&lt;p&gt;真白就是诺瓦，真白从小患白化病不能出门，身体极度虚弱。在音理出车祸后，真白的病况刚好恶化，急需心脏移植。真白的爷爷就是这个医院的院长，爷爷下令护士（狩叶的姐姐）将音理的心脏移植到真白上，以挽救真白的生命。虽然护士认为这是违法的，因为音理没有提供器官捐献说明。护士再三劝说爷爷，但是最后还是无法改变爷爷的态度，执行了手术。&lt;/p&gt;
&lt;p&gt;真白转变回了诺瓦。&lt;/p&gt;
&lt;p&gt;回到列车上后，诺瓦非常内疚，认为自己夺走了空的要好的朋友的生命。空对她解释，这是不对的，你的人生应该由你自己来掌握。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;如果实在害怕的话，就尽量增加几个可以增添麻烦的人。恋人、家人，还有…朋友。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;诺瓦心动了…或许自己之前想的是错的。这时…诺瓦突然意识到，自己夺走的并不只有音理的生命。&lt;/p&gt;
&lt;p&gt;突然，在车上的四人——空、鹰世、野鸟和花江全部呼吸困难倒在地上。狩叶握起白的手，下一秒就转变成了真白。&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://cf.s3.soulter.top/gal-comment-%E6%98%9F%E7%A9%BA%E5%88%97%E8%BD%A6%E4%B8%8E%E7%99%BD%E7%9A%84%E6%97%85%E8%A1%8C/bacb8d6f4d36ad1ae92416266457c930.png&#34; alt=&#34;alt text&#34; /&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://cf.s3.soulter.top/gal-comment-%E6%98%9F%E7%A9%BA%E5%88%97%E8%BD%A6%E4%B8%8E%E7%99%BD%E7%9A%84%E6%97%85%E8%A1%8C/af3640a6b23f6d48a3ae2f62a5adc335.png&#34; alt=&#34;alt text&#34; /&gt;&lt;/p&gt;
&lt;p&gt;白陷入昏迷，他的眼前的画面忽然转到了自己的房间，在这里，他亲眼目睹了自己的死亡——在音理出车祸后，他为了消愁，酗酒成瘾，在闷热的夏天由于中暑去世了。房东和警察正在检点他的尸体和房间。&lt;/p&gt;
&lt;p&gt;突然画面又转换到了真白的房间，但这次，他是以真白的视角。他感受到了身体里传来的从未感受到的痛苦。&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://cf.s3.soulter.top/gal-comment-%E6%98%9F%E7%A9%BA%E5%88%97%E8%BD%A6%E4%B8%8E%E7%99%BD%E7%9A%84%E6%97%85%E8%A1%8C/image-8.png&#34; alt=&#34;alt text&#34; /&gt;&lt;/p&gt;
&lt;p&gt;上次心脏移植手术结束后过了半年，真白病情再次发生了恶化，原本的心脏移植后的排异反应让她整天痛苦难耐，生不如死——这还是在吃了药之后。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;经过心脏的血液都被当成外来毒素，以至于全身都充满了毒素，全身的免疫细胞都在奋力消灭毒素。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;真白的多个器官都衰竭了，急需再次进行器官移植。&lt;/p&gt;
&lt;p&gt;爷爷这时发现刚好出现了四个写明器官捐献的逝者，因此，再次将他们的器官移植到了真白身体内。&lt;/p&gt;
&lt;p&gt;更强大的排异反应，让术后虚弱的真白更加痛苦。&lt;/p&gt;
&lt;p&gt;白了解到了，现在自己感受到的痛苦，都是真白天天要忍耐的。&lt;/p&gt;
&lt;p&gt;白也理解了，&lt;strong&gt;自己和另外三人都已经在现实中去世。&lt;/strong&gt; 真白的爷爷将他们的器官移植到了真白身上。&lt;/p&gt;
&lt;p&gt;又是一片眩晕，眼前突然变黑，意识逐渐模糊。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;居然这样就死了吗？&lt;br /&gt;
我的人生居然这样就结束了吗？&lt;br /&gt;
死得也太轻率了吧。&lt;br /&gt;
我可是拼命活了好几十年啊。&lt;br /&gt;
就没能发生些什么事，以填补那些凭借着与身俱来的好运过着好日子的人的差距吗？&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;不行。&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://cf.s3.soulter.top/gal-comment-%E6%98%9F%E7%A9%BA%E5%88%97%E8%BD%A6%E4%B8%8E%E7%99%BD%E7%9A%84%E6%97%85%E8%A1%8C/image-9.png&#34; alt=&#34;alt text&#34; /&gt;&lt;/p&gt;
&lt;p&gt;白努力地操控着这副身体，让自己站起来。走到前台的写生本上。护士询问发生了什么？白回答道：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;为了约定。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;（伏笔）&lt;/p&gt;
&lt;h3 id=&#34;结局&#34;&gt;&lt;a class=&#34;markdownIt-Anchor&#34; href=&#34;#结局&#34;&gt;&lt;/a&gt; 结局&lt;/h3&gt;
&lt;audio id=&#34;audio&#34; controls=&#34;&#34; preload=&#34;none&#34;&gt;
      &lt;source id=&#34;m4a&#34; src=&#34;https://cf.s3.soulter.top/gal-comment-星空列车与白的旅行/gal_shiro.m4a&#34;&gt;
      Your browser does not support the audio element.
&lt;/audio&gt;
(BGM: スタートリップ)
&lt;p&gt;此时已经步入结尾，我在这里会阐释对这个作品的理解。&lt;/p&gt;
&lt;p&gt;画面又转到了自己的家。&lt;/p&gt;
&lt;p&gt;白在黑猫的指引下，跋山涉水回到列车上，突然从昏迷中醒来。看到了真白。&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://cf.s3.soulter.top/gal-comment-%E6%98%9F%E7%A9%BA%E5%88%97%E8%BD%A6%E4%B8%8E%E7%99%BD%E7%9A%84%E6%97%85%E8%A1%8C/image-10.png&#34; alt=&#34;&#34; /&gt;&lt;/p&gt;
&lt;p&gt;真白阻挡了白，她不想白再救自己，她不想让白再阻碍自己走向死亡。她不想再让这段旅程继续下去——这里的世界就是真白所做的梦。因此当诺瓦、空等人意识到时，梦境就要崩塌了，于是旅程也要结束了。&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://cf.s3.soulter.top/gal-comment-%E6%98%9F%E7%A9%BA%E5%88%97%E8%BD%A6%E4%B8%8E%E7%99%BD%E7%9A%84%E6%97%85%E8%A1%8C/image-11.png&#34; alt=&#34;&#34; /&gt;&lt;/p&gt;
&lt;p&gt;整篇文章在结尾将前面的伏笔一一揭示，并且大量运用了象征手法。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;这个世界的空、鹰世、野鸟和花江，代表了移植到真白体内的自己的某个器官&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;这个世界的真白，正象征了现实中真白体内的免疫细胞，当真白抓住空等三人的手时，就代表现实体内的免疫细胞正在攻击空等三人的移植到真白体内的器官。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;为什么一开始空和其他人分布在不同的车厢？我想大家看到这里都有了答案。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;这列蒸汽列车正象征了现实中真白的身体。蒸汽列车行进如此缓慢，但是内饰干净豪华，象征着真白虚弱的身体，但是内心是一个好人。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;蒸汽列车缓慢行进在铁轨上，象征着真白的人生。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;然而，这辆列车现在已经熄火，正在靠惯性向前缓慢移动。&lt;/p&gt;
&lt;p&gt;空与真白硬碰硬（天马行空一下，移植到真白身上的大家的器官通过某种超自然现象影响了真白的大脑，使得其能与免疫系统对话），最终感化了真白。&lt;/p&gt;
&lt;p&gt;这时大家都醒了（大家昏迷的时候也梦到了自己的事情吧）。大家也都了解了一切。&lt;/p&gt;
&lt;p&gt;大家齐手走进蒸汽炉间，努力让这辆列车重新运作起来，努力把诺瓦从生命边界的悬崖上拉回来。&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://cf.s3.soulter.top/gal-comment-%E6%98%9F%E7%A9%BA%E5%88%97%E8%BD%A6%E4%B8%8E%E7%99%BD%E7%9A%84%E6%97%85%E8%A1%8C/image-14.png&#34; alt=&#34;&#34; /&gt;&lt;/p&gt;
&lt;p&gt;真白将烧好的木炭丢进炉子…列车重新运作了起来——大家联手。&lt;/p&gt;
&lt;p&gt;在故事的最后，大家一一向真白告别。&lt;/p&gt;
&lt;p&gt;此时，大家在碰到真白后已经不会有不良反应了，&lt;strong&gt;这象征着真白体内的免疫细胞已经接受了这几个外来的器官。&lt;/strong&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;这才是真正旅途的终点。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;img src=&#34;https://cf.s3.soulter.top/gal-comment-%E6%98%9F%E7%A9%BA%E5%88%97%E8%BD%A6%E4%B8%8E%E7%99%BD%E7%9A%84%E6%97%85%E8%A1%8C/image-12.png&#34; alt=&#34;&#34; /&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://cf.s3.soulter.top/gal-comment-%E6%98%9F%E7%A9%BA%E5%88%97%E8%BD%A6%E4%B8%8E%E7%99%BD%E7%9A%84%E6%97%85%E8%A1%8C/image-13.png&#34; alt=&#34;&#34; /&gt;&lt;/p&gt;
&lt;p&gt;“白…不要走，不要消失…” 真白哭喊着, “我想就留在这里，和你一起。”&lt;/p&gt;
&lt;p&gt;这很正常，真白一直都在他人的指示下活着，不管是自己的母亲，还是在见到白等人后。真白不知道自己是活着好，还是不活好。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;To be or not to be. 她还很迷惘。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;不过，这样就好，大人一般也这样。&lt;/p&gt;
&lt;p&gt;心存迷惘未尝不可，不置可否亦未尝不可，谁也没办法真正分清黑白。可是人生就是这样，即使抱着不置可否的心情，人生也必须要选择某一个方向。他人的期望会对你产生很大的影响。我们都希望你活下去。你的身体外也有很多人希望你活下去，你的妈妈，你的爷爷，护士…&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://cf.s3.soulter.top/gal-comment-%E6%98%9F%E7%A9%BA%E5%88%97%E8%BD%A6%E4%B8%8E%E7%99%BD%E7%9A%84%E6%97%85%E8%A1%8C/image-15.png&#34; alt=&#34;&#34; /&gt;&lt;/p&gt;
&lt;p&gt;世界就是充满熠熠生辉的宝石箱，或许也有烦恼的时候，人生中总要和别人互相争夺那些宝石，总会留下不好的回忆。但是就算是这样，我们也要正视这段经历，用自己的双眼，去注视那些别人看不见的光芒。&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://cf.s3.soulter.top/gal-comment-%E6%98%9F%E7%A9%BA%E5%88%97%E8%BD%A6%E4%B8%8E%E7%99%BD%E7%9A%84%E6%97%85%E8%A1%8C/image-16.png&#34; alt=&#34;alt text&#34; /&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://cf.s3.soulter.top/gal-comment-%E6%98%9F%E7%A9%BA%E5%88%97%E8%BD%A6%E4%B8%8E%E7%99%BD%E7%9A%84%E6%97%85%E8%A1%8C/image-18.png&#34; alt=&#34;alt text&#34; /&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://cf.s3.soulter.top/gal-comment-%E6%98%9F%E7%A9%BA%E5%88%97%E8%BD%A6%E4%B8%8E%E7%99%BD%E7%9A%84%E6%97%85%E8%A1%8C/image-19.png&#34; alt=&#34;alt text&#34; /&gt;&lt;/p&gt;
&lt;p&gt;现实。真白突然醒了过来，护士非常高兴。&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://cf.s3.soulter.top/gal-comment-%E6%98%9F%E7%A9%BA%E5%88%97%E8%BD%A6%E4%B8%8E%E7%99%BD%E7%9A%84%E6%97%85%E8%A1%8C/image-20.png&#34; alt=&#34;alt text&#34; /&gt;&lt;/p&gt;
&lt;p&gt;这是大家的成果。&lt;/p&gt;
&lt;p&gt;真白站起来后，看见了摆在前台的写生本，非常熟悉。打开一看，一幅画着自己的画映入眼帘。这就是约定，空与真白的约定。&lt;/p&gt;
&lt;p&gt;全篇以梦幻而又温暖的结局收场：&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://cf.s3.soulter.top/gal-comment-%E6%98%9F%E7%A9%BA%E5%88%97%E8%BD%A6%E4%B8%8E%E7%99%BD%E7%9A%84%E6%97%85%E8%A1%8C/image-21.png&#34; alt=&#34;&#34; /&gt;&lt;/p&gt;
&lt;p&gt;依然是同样的蒸汽列车，可它的速度已经不再是以前那样慢悠悠的了，而是「像子弹一样」的快。&lt;strong&gt;这里是一个细节&lt;/strong&gt;，这暗示着醒来后的真白的身体状况越来越好。&lt;/p&gt;
&lt;p&gt;列车上的，除了狩叶，其他人都在，在各自的车厢。大家一起讨论着诺瓦下一个要去的景点，也就是列车的下一站，人生的下一站。这样，他们也可以一起游玩了。&lt;/p&gt;
&lt;p&gt;这部作品的结尾方式非常温暖，以一种特别的方式让全文看起来没有任何人发生悲剧，可怜的五个人仍然活着，在列车中，在诺瓦的身体中活着。&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://cf.s3.soulter.top/gal-comment-%E6%98%9F%E7%A9%BA%E5%88%97%E8%BD%A6%E4%B8%8E%E7%99%BD%E7%9A%84%E6%97%85%E8%A1%8C/image-22.png&#34; alt=&#34;alt text&#34; /&gt;&lt;/p&gt;
&lt;p&gt;空也因此得以与音理重新见面。&lt;/p&gt;
&lt;h2 id=&#34;玩后感想&#34;&gt;&lt;a class=&#34;markdownIt-Anchor&#34; href=&#34;#玩后感想&#34;&gt;&lt;/a&gt; 玩后感想&lt;/h2&gt;
&lt;p&gt;全文以列车、真白（诺瓦）为线索，白等人教会了真白如何生活、如何看待这个世界，最终救赎了真白的生命。&lt;/p&gt;
&lt;p&gt;在沙滩时，白想到美术老师告诉他的：只有不断地接触新生事物，才能保持自己感性的一面。高三之后，我的生活逐渐被一些琐事占满，大学以来，各种繁重的工作、任务将我们的感性消磨殆尽，以至于见到新鲜事物再难以唤起以前内心的那份激动。&lt;/p&gt;
&lt;p&gt;寒假时也和自己的弟弟一起放了烟花，同样是小小的拿在手上的铁线烟花。燃烧的样子多美啊，铁树银花。然而看到弟弟欢呼雀跃的样子后，我也意识到自己内心再难对这种简单的事物泛起波澜。&lt;/p&gt;
&lt;p&gt;这个世界充满了熠熠生辉的宝石箱，小的时候知道这一点时只是充满了憧憬和敬畏。长大后，内心也悄然发生了变化，变得想要去得到。每个人都是这样想的，因此就产生了争执。小到人与人之间，大到国家与国家之间，都在争夺那璀璨的宝石。即使得到了一个，还想去得到下一个。&lt;/p&gt;
&lt;p&gt;看到这里，我也恍然大悟，这部作品虽说是类科幻，但却聚焦于现实，其中穿插着很多现实的元素。&lt;/p&gt;
&lt;p&gt;在这个童话一样的剧情当中，教会大家如何在遇到困难时，不要自暴自弃，而是坚强的走下去；教会大家成人的世界除了黑和白，还多了一种“灰色”，这份灰色可能来源于童年时注意不到的事物，但即使是这样也要怀揣着童年的梦想，寻求快乐的事物，寻找那些藏在宝石箱中的宝石；教会大家要广交朋友，心怀感激，积极回报他人。&lt;/p&gt;
</content>
        <category term="2024" />
        <category term="游戏" />
        <updated>2024-03-30T14:23:00.000Z</updated>
    </entry>
    <entry>
        <id>https://blog.soulter.top/posts/%E6%97%A5%E6%9C%AC%E6%B8%B8%E8%AE%B0%E4%B9%8B%E4%BA%8C.html</id>
        <title>日本游记 / 雪国风光</title>
        <link rel="alternate" href="https://blog.soulter.top/posts/%E6%97%A5%E6%9C%AC%E6%B8%B8%E8%AE%B0%E4%B9%8B%E4%BA%8C.html"/>
        <content type="html">&lt;h2 id=&#34;序言&#34;&gt;&lt;a class=&#34;markdownIt-Anchor&#34; href=&#34;#序言&#34;&gt;&lt;/a&gt; 序言&lt;/h2&gt;
&lt;p&gt;在来到北海道之前，就有朋友提醒我：北海道最近经常下暴雪，航班延误率也很高。不过好在我们的去程和返程都非常顺利，尤其是返程，当天飞往东京的航班中，在我们之前和之后出发的航班基本都取消了，唯独我们的航班只延误了一个小时。&lt;br /&gt;
&lt;img src=&#34;https://cf.s3.soulter.top/%E6%97%A5%E6%9C%AC%E6%B8%B8%E8%AE%B0%E4%B9%8B%E4%BA%8C/20240215082343789.png&#34; alt=&#34;&#34; /&gt;&lt;/p&gt;
&lt;p&gt;我们在北海道的行程安排是：第一天前往札幌的北海道大学、白色恋人博物馆和札幌电视塔。第二天前往滨海城市小樽。原定第三天去旭川、美瑛和富良野的行程由于太累就取消了，改为前往札幌的公园和 PARCO 商场。&lt;/p&gt;
&lt;p&gt;到达札幌后，迎面而来的就是刺骨的寒气，但和中国北方的城市一样，这边的冷与中国南方的冷还是不一样的。中国南方的冷由于湿度高，水分子无孔不入，接触皮肤后气化吸热，造成魔法伤害。而这里仅仅只算是物理伤害。&lt;/p&gt;
&lt;p&gt;由于在北京读书，在经历三场北京的大雪后，对雪本来也不足为奇了。但来到这里后，发现这里的雪居然能堆 3 米高，还随处可见！虽然雪都是平平淡淡的白色，各个地方下的雪都相差无几。但当它们成为点缀，修饰在建筑、植被等各国独特的风景上时，或许能发现不一样的美。&lt;/p&gt;
&lt;h2 id=&#34;day3-day4-札幌&#34;&gt;&lt;a class=&#34;markdownIt-Anchor&#34; href=&#34;#day3-day4-札幌&#34;&gt;&lt;/a&gt; DAY.3 - DAY.4 札幌&lt;/h2&gt;
&lt;h3 id=&#34;arrival-the-sapporo&#34;&gt;&lt;a class=&#34;markdownIt-Anchor&#34; href=&#34;#arrival-the-sapporo&#34;&gt;&lt;/a&gt; Arrival the Sapporo&lt;/h3&gt;
&lt;p&gt;&lt;img src=&#34;https://cf.s3.soulter.top/%E6%97%A5%E6%9C%AC%E6%B8%B8%E8%AE%B0%E4%B9%8B%E4%BA%8C/20240211023447582.png&#34; alt=&#34;&#34; /&gt;&lt;br /&gt;
晚上到达新千岁机场。这里的标志牌上印有六种语言：简中、繁中、俄、日、韩、英。&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://cf.s3.soulter.top/%E6%97%A5%E6%9C%AC%E6%B8%B8%E8%AE%B0%E4%B9%8B%E4%BA%8C/20240211023032999.png&#34; alt=&#34;&#34; /&gt;&lt;br /&gt;
乘坐大巴前往札幌市区的酒店。&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://cf.s3.soulter.top/%E6%97%A5%E6%9C%AC%E6%B8%B8%E8%AE%B0%E4%B9%8B%E4%BA%8C/20240211023555663.png&#34; alt=&#34;&#34; /&gt;&lt;br /&gt;
&lt;img src=&#34;https://cf.s3.soulter.top/%E6%97%A5%E6%9C%AC%E6%B8%B8%E8%AE%B0%E4%B9%8B%E4%BA%8C/20240211024037337.png&#34; alt=&#34;&#34; /&gt;&lt;br /&gt;
令南方人震惊的雪量。&lt;/p&gt;
&lt;h3 id=&#34;北海道大学&#34;&gt;&lt;a class=&#34;markdownIt-Anchor&#34; href=&#34;#北海道大学&#34;&gt;&lt;/a&gt; 北海道大学&lt;/h3&gt;
&lt;p&gt;&lt;img src=&#34;https://cf.s3.soulter.top/%E6%97%A5%E6%9C%AC%E6%B8%B8%E8%AE%B0%E4%B9%8B%E4%BA%8C/20240211023734805.png&#34; alt=&#34;&#34; /&gt;&lt;br /&gt;
北海道大学内，大雪后宁静的校园中流淌着一条小溪。&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://cf.s3.soulter.top/%E6%97%A5%E6%9C%AC%E6%B8%B8%E8%AE%B0%E4%B9%8B%E4%BA%8C/20240211024133662.png&#34; alt=&#34;&#34; /&gt;&lt;br /&gt;
北海道大学的校训。和我初高中的校训“专心志，忧天下”很像。&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://cf.s3.soulter.top/%E6%97%A5%E6%9C%AC%E6%B8%B8%E8%AE%B0%E4%B9%8B%E4%BA%8C/20240211112104727.png&#34; alt=&#34;&#34; /&gt;&lt;br /&gt;
第一次品尝了北海道牛乳制作的冰激凌。奶香味十足！&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://cf.s3.soulter.top/%E6%97%A5%E6%9C%AC%E6%B8%B8%E8%AE%B0%E4%B9%8B%E4%BA%8C/20240211112707792.png&#34; alt=&#34;&#34; /&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://cf.s3.soulter.top/%E6%97%A5%E6%9C%AC%E6%B8%B8%E8%AE%B0%E4%B9%8B%E4%BA%8C/20240211113246104.png&#34; alt=&#34;&#34; /&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://cf.s3.soulter.top/%E6%97%A5%E6%9C%AC%E6%B8%B8%E8%AE%B0%E4%B9%8B%E4%BA%8C/20240211113523523.png&#34; alt=&#34;&#34; /&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;“With malice toward none, with charity for all.”&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;img src=&#34;https://cf.s3.soulter.top/%E6%97%A5%E6%9C%AC%E6%B8%B8%E8%AE%B0%E4%B9%8B%E4%BA%8C/20240211112804680.png&#34; alt=&#34;&#34; /&gt;&lt;br /&gt;
北海道的汤咖喱。顾名思义，这就是由咖喱粉、辣椒、水（或者豚骨汤？）制成的汤底，再加上秋葵、鸡腿、西兰花、木耳、茄子、红薯等等任凭喜好的蔬菜或肉类。&lt;br /&gt;
在我们选定的这家店，辣度可选 10-30，我选了 25。辣死了🥵。咖喱味很浓，非常下饭，总体上很好吃！&lt;/p&gt;
&lt;h3 id=&#34;白色恋人博物馆&#34;&gt;&lt;a class=&#34;markdownIt-Anchor&#34; href=&#34;#白色恋人博物馆&#34;&gt;&lt;/a&gt; 白色恋人博物馆&lt;/h3&gt;
&lt;p&gt;&lt;img src=&#34;https://cf.s3.soulter.top/%E6%97%A5%E6%9C%AC%E6%B8%B8%E8%AE%B0%E4%B9%8B%E4%BA%8C/20240211113826705.png&#34; alt=&#34;&#34; /&gt;&lt;br /&gt;
这是日本巧克力著名品牌白色恋人在札幌的一个工厂，也是一个著名景点。&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://cf.s3.soulter.top/%E6%97%A5%E6%9C%AC%E6%B8%B8%E8%AE%B0%E4%B9%8B%E4%BA%8C/20240211115000399.png&#34; alt=&#34;&#34; /&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://cf.s3.soulter.top/%E6%97%A5%E6%9C%AC%E6%B8%B8%E8%AE%B0%E4%B9%8B%E4%BA%8C/20240211115123350.png&#34; alt=&#34;&#34; /&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://cf.s3.soulter.top/%E6%97%A5%E6%9C%AC%E6%B8%B8%E8%AE%B0%E4%B9%8B%E4%BA%8C/20240211115204938.png&#34; alt=&#34;&#34; /&gt;&lt;br /&gt;
工厂内公园的钟楼。在漆黑的夜晚和鹅毛大雪的衬托下显得熠熠生辉。在钟楼的基座处（图中突出来的一部分），演员们正在表演戏剧。&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://cf.s3.soulter.top/%E6%97%A5%E6%9C%AC%E6%B8%B8%E8%AE%B0%E4%B9%8B%E4%BA%8C/20240211115459525.png&#34; alt=&#34;&#34; /&gt;&lt;br /&gt;
宛如童话世界一般。&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://cf.s3.soulter.top/%E6%97%A5%E6%9C%AC%E6%B8%B8%E8%AE%B0%E4%B9%8B%E4%BA%8C/20240211115547740.png&#34; alt=&#34;&#34; /&gt;&lt;/p&gt;
&lt;h3 id=&#34;札幌电视塔&#34;&gt;&lt;a class=&#34;markdownIt-Anchor&#34; href=&#34;#札幌电视塔&#34;&gt;&lt;/a&gt; 札幌电视塔&lt;/h3&gt;
&lt;p&gt;&lt;img src=&#34;https://cf.s3.soulter.top/%E6%97%A5%E6%9C%AC%E6%B8%B8%E8%AE%B0%E4%B9%8B%E4%BA%8C/20240211115733264.png&#34; alt=&#34;&#34; /&gt;&lt;br /&gt;
札幌的地标性建筑。可以花费 1500 日元登顶欣赏札幌城市夜景。不过！！！千万别在非晴朗的天气欣赏，不然画面会是这样的——&lt;br /&gt;
&lt;img src=&#34;https://cf.s3.soulter.top/%E6%97%A5%E6%9C%AC%E6%B8%B8%E8%AE%B0%E4%B9%8B%E4%BA%8C/20240211115916813.png&#34; alt=&#34;&#34; /&gt;&lt;br /&gt;
血亏呀😭&lt;/p&gt;
&lt;p&gt;帮朋友买好雪 miku、北海道限定的 chiikawa 等周边之后，结束了这天的行程。（不得不吐槽一句，chiikawa 国内炒得这么火吗，65 人民币不到的玩偶听说国内炒到上百了）&lt;/p&gt;
&lt;h2 id=&#34;day5-day6-小樽-札幌市区&#34;&gt;&lt;a class=&#34;markdownIt-Anchor&#34; href=&#34;#day5-day6-小樽-札幌市区&#34;&gt;&lt;/a&gt; DAY.5 - DAY.6 小樽、札幌市区&lt;/h2&gt;
&lt;p&gt;&lt;img src=&#34;https://cf.s3.soulter.top/%E6%97%A5%E6%9C%AC%E6%B8%B8%E8%AE%B0%E4%B9%8B%E4%BA%8C/20240215083834881.png&#34; alt=&#34;&#34; /&gt;&lt;br /&gt;
强推北果楼的“梦不思议”，类似泡芙，但是里面的馅料个人认为更香&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://cf.s3.soulter.top/%E6%97%A5%E6%9C%AC%E6%B8%B8%E8%AE%B0%E4%B9%8B%E4%BA%8C/20240215075437015.png&#34; alt=&#34;&#34; /&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://cf.s3.soulter.top/%E6%97%A5%E6%9C%AC%E6%B8%B8%E8%AE%B0%E4%B9%8B%E4%BA%8C/20240215075530250.png&#34; alt=&#34;&#34; /&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://cf.s3.soulter.top/%E6%97%A5%E6%9C%AC%E6%B8%B8%E8%AE%B0%E4%B9%8B%E4%BA%8C/20240215075659145.png&#34; alt=&#34;&#34; /&gt;&lt;br /&gt;
小樽天狗山上的城市全览，风景真的非常棒。&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://cf.s3.soulter.top/%E6%97%A5%E6%9C%AC%E6%B8%B8%E8%AE%B0%E4%B9%8B%E4%BA%8C/20240215075842085.png&#34; alt=&#34;&#34; /&gt;&lt;br /&gt;
二刷汤咖喱。这家显然没上家好吃&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://cf.s3.soulter.top/%E6%97%A5%E6%9C%AC%E6%B8%B8%E8%AE%B0%E4%B9%8B%E4%BA%8C/20240215083254103.png&#34; alt=&#34;&#34; /&gt;&lt;br /&gt;
SOULTER.TOP !&lt;br /&gt;
上面那个网站是朋友叫帮画的，可以忽略:)&lt;/p&gt;
&lt;h2 id=&#34;day7-登别&#34;&gt;&lt;a class=&#34;markdownIt-Anchor&#34; href=&#34;#day7-登别&#34;&gt;&lt;/a&gt; DAY.7 登别&lt;/h2&gt;
&lt;p&gt;闪现到了北海道东部的登别，来这里体验温泉~&lt;br /&gt;
&lt;img src=&#34;https://cf.s3.soulter.top/%E6%97%A5%E6%9C%AC%E6%B8%B8%E8%AE%B0%E4%B9%8B%E4%BA%8C/20240215081032542.png&#34; alt=&#34;&#34; /&gt;&lt;br /&gt;
上面的景象拍下来好像 Mac 的壁纸啊（狂喜）&lt;br /&gt;
&lt;img src=&#34;https://cf.s3.soulter.top/%E6%97%A5%E6%9C%AC%E6%B8%B8%E8%AE%B0%E4%B9%8B%E4%BA%8C/20240215081805899.png&#34; alt=&#34;&#34; /&gt;&lt;br /&gt;
这里飘起的烟就是温度高达 80 摄氏度的温泉水（铁泉）蒸发出的。天上还在下雪的同时能看到水受热蒸发，很神奇，这下水的三相在接近 90 摄氏度的温差下团圆了（doge）&lt;/p&gt;
&lt;h2 id=&#34;day8-新千岁机场飞东京&#34;&gt;&lt;a class=&#34;markdownIt-Anchor&#34; href=&#34;#day8-新千岁机场飞东京&#34;&gt;&lt;/a&gt; DAY.8 新千岁机场（飞东京）&lt;/h2&gt;
&lt;p&gt;&lt;img src=&#34;https://cf.s3.soulter.top/%E6%97%A5%E6%9C%AC%E6%B8%B8%E8%AE%B0%E4%B9%8B%E4%BA%8C/20240215081254943.png&#34; alt=&#34;&#34; /&gt;&lt;br /&gt;
雪miku可爱捏&lt;/p&gt;
</content>
        <category term="2024" />
        <category term="日本" />
        <updated>2024-02-11T13:56:00.000Z</updated>
    </entry>
    <entry>
        <id>https://blog.soulter.top/posts/%E6%97%A5%E6%9C%AC%E6%B8%B8%E8%AE%B0%E4%B9%8B%E4%B8%80.html</id>
        <title>日本游记 / 迈向世界</title>
        <link rel="alternate" href="https://blog.soulter.top/posts/%E6%97%A5%E6%9C%AC%E6%B8%B8%E8%AE%B0%E4%B9%8B%E4%B8%80.html"/>
        <content type="html">&lt;h2 id=&#34;序言&#34;&gt;&lt;a class=&#34;markdownIt-Anchor&#34; href=&#34;#序言&#34;&gt;&lt;/a&gt; 序言&lt;/h2&gt;
&lt;p&gt;从小学到现在看了不少日漫了，日漫里反复出现的的神社鸟居、榻榻米、拉面、章鱼烧等文化给我留下了很深刻的印象。我也愈发好奇，希望能有朝一日去日本主动体验一下当地的文化。如今，在机缘巧合之下，终于能够实现这一愿望。&lt;/p&gt;
&lt;p&gt;在写这篇游记时，已经是春节时分了，距离到达日本时已经过去快一个月了。不希望这次宝贵的记忆随着时间的流逝而像沙漏般慢慢滑走，故作文以记之。&lt;/p&gt;
&lt;h2 id=&#34;day1&#34;&gt;&lt;a class=&#34;markdownIt-Anchor&#34; href=&#34;#day1&#34;&gt;&lt;/a&gt; DAY.1&lt;/h2&gt;
&lt;p&gt;&lt;img src=&#34;https://cf.s3.soulter.top/%E6%97%A5%E6%9C%AC%E6%B8%B8%E8%AE%B0%E4%B9%8B%E4%B8%80/20240210103543794.png&#34; alt=&#34;&#34; /&gt;&lt;br /&gt;
在异国他乡拍下的第一张风景画。&lt;/p&gt;
&lt;p&gt;由于语言交流不通畅，入关时被卡了好久（那个工作人员说的中文和英文太难听懂了awa）。&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://cf.s3.soulter.top/%E6%97%A5%E6%9C%AC%E6%B8%B8%E8%AE%B0%E4%B9%8B%E4%B8%80/20240210103522593.png&#34; alt=&#34;&#34; /&gt;&lt;br /&gt;
怪吓人的，原来是大阪福岛&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://cf.s3.soulter.top/%E6%97%A5%E6%9C%AC%E6%B8%B8%E8%AE%B0%E4%B9%8B%E4%B8%80/20240210103428081.png&#34; alt=&#34;&#34; /&gt;&lt;br /&gt;
第一餐：「纪州鸭荞麦拉面」。味道还行，中规中矩。由于这个是酱油拉面，所以没有想象中那种浓郁的白汤。&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;这趟旅程令我印象最深刻的是日本人的礼节。&lt;/p&gt;
&lt;p&gt;无论是机场的工作人员、餐饮店和酒店的服务人员还是路人，让我切身感受到了日本数一数二的礼貌——在机场，工作人员会热情地告诉你该往哪走、该做什么；在餐饮店，服务员会耐心地教你如何点餐；而在其他地方，举个例子，当需要出电梯或者走在大街上互相挡住路或者麻烦到你时，他们会微微鞠躬并说 simennasai（不好意思）…&lt;/p&gt;
&lt;p&gt;中日文化的这个差别在我回国后出机场，坐火车时感受得淋漓尽致——在重温家乡的温暖「给我快点」「别挡在这里」「快点往里面走」「别堵着门！」「赶紧走后边干什么呢！」之后，很难相信世界上还存在着那么一个有礼貌的国家。&lt;/p&gt;
&lt;p&gt;日本人内心的真实想法如何我们不得而知，但为什么要去知道呢？只要他们（服务员和路人）能够对外表现得彬彬有礼就足够了吧。&lt;/p&gt;
&lt;hr /&gt;
&lt;h2 id=&#34;day2&#34;&gt;&lt;a class=&#34;markdownIt-Anchor&#34; href=&#34;#day2&#34;&gt;&lt;/a&gt; DAY.2&lt;/h2&gt;
&lt;p&gt;&lt;img src=&#34;https://cf.s3.soulter.top/%E6%97%A5%E6%9C%AC%E6%B8%B8%E8%AE%B0/20240210015535269.png&#34; alt=&#34;&#34; /&gt;&lt;br /&gt;
今天去大阪环球影城玩！&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://cf.s3.soulter.top/%E6%97%A5%E6%9C%AC%E6%B8%B8%E8%AE%B0/20240210014131114.png&#34; alt=&#34;&#34; /&gt;&lt;br /&gt;
霍格沃茨园区。阴天让这里更加有气氛了。&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://cf.s3.soulter.top/%E6%97%A5%E6%9C%AC%E6%B8%B8%E8%AE%B0/20240210014355986.png&#34; alt=&#34;&#34; /&gt;&lt;br /&gt;
排了两个小时的队，在这里面体验了一次哈利波特里面的扫帚飞行。效果非常逼真，身临其境的感觉，以至于每次在向下俯冲的时候都有很强烈的失重感。&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://cf.s3.soulter.top/%E6%97%A5%E6%9C%AC%E6%B8%B8%E8%AE%B0/20240210014625747.png&#34; alt=&#34;&#34; /&gt;&lt;br /&gt;
左边的鸡腿和其他的菜都很一般，右边的土豆汤芝士味很浓。&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://cf.s3.soulter.top/%E6%97%A5%E6%9C%AC%E6%B8%B8%E8%AE%B0/20240210015336393.png&#34; alt=&#34;&#34; /&gt;&lt;br /&gt;
来到马里奥园区。一切都是动态的！伴随着马里奥的各种 BGM 和游戏音效，仿佛置身于马里奥世界里面。&lt;/p&gt;
&lt;p&gt;{视频 - 马里奥}&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://cf.s3.soulter.top/%E6%97%A5%E6%9C%AC%E6%B8%B8%E8%AE%B0/20240210015615256.png&#34; alt=&#34;&#34; /&gt;&lt;br /&gt;
酒店楼下的晚餐——我也不知道叫什么菜品了。但是这个鸡胸肉非常鲜嫩多汁，很好吃！&lt;/p&gt;
&lt;h2 id=&#34;day3&#34;&gt;&lt;a class=&#34;markdownIt-Anchor&#34; href=&#34;#day3&#34;&gt;&lt;/a&gt; DAY.3&lt;/h2&gt;
&lt;p&gt;&lt;img src=&#34;https://cf.s3.soulter.top/%E6%97%A5%E6%9C%AC%E6%B8%B8%E8%AE%B0/20240210020001173.png&#34; alt=&#34;&#34; /&gt;&lt;br /&gt;
今天前往离大阪不远的京都——一座以悠久而深厚的历史文化沉淀而闻名世界的城市。&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://cf.s3.soulter.top/%E6%97%A5%E6%9C%AC%E6%B8%B8%E8%AE%B0/20240210020054538.png&#34; alt=&#34;&#34; /&gt;&lt;br /&gt;
&lt;img src=&#34;https://cf.s3.soulter.top/%E6%97%A5%E6%9C%AC%E6%B8%B8%E8%AE%B0/20240210020203620.png&#34; alt=&#34;&#34; /&gt;&lt;br /&gt;
首先前往的是京都伏见区的伏见稻荷大社。蔚蓝清澈的天空下，这座寺庙的颜色显得如此鲜艳和明亮。&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://cf.s3.soulter.top/%E6%97%A5%E6%9C%AC%E6%B8%B8%E8%AE%B0/20240210020343409.png&#34; alt=&#34;&#34; /&gt;&lt;br /&gt;
这个景区的地图。&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://cf.s3.soulter.top/%E6%97%A5%E6%9C%AC%E6%B8%B8%E8%AE%B0/20240210020804280.png&#34; alt=&#34;&#34; /&gt;&lt;br /&gt;
正好赶上了巫女的神乐舞。&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://cf.s3.soulter.top/%E6%97%A5%E6%9C%AC%E6%B8%B8%E8%AE%B0/20240210020912124.png&#34; alt=&#34;&#34; /&gt;&lt;br /&gt;
跟着人流往里走，不久便看到了壮观的鸟居群。在这个神社，布满了超过 10,000 座鸟居。&lt;br /&gt;
&lt;img src=&#34;https://cf.s3.soulter.top/%E6%97%A5%E6%9C%AC%E6%B8%B8%E8%AE%B0/20240210021041923.png&#34; alt=&#34;&#34; /&gt;&lt;br /&gt;
在日本文化中，鸟居是连接凡间和神明居住地的通道。穿梭其间，耳畔回荡着乌鸦和其他鸟类的歌声，宛如神秘的旋律贯穿空气。在这神奇的往来中，仿佛能感受到凡间与神明之间微妙而又神秘的联系，仿佛在时光的隧道中徜徉。&lt;/p&gt;
&lt;p&gt;这让我联想到了《Hello World》这部动漫电影。乌鸦和鸟居贯穿全片始终，片中拯救男主的“神之手”就是乌鸦化身而来，而男主一开始就是通过穿行鸟居来前往虚拟世界中。&lt;/p&gt;
&lt;p&gt;上网一搜，好家伙，原来这部片的取景地正是这里。&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://cf.s3.soulter.top/%E6%97%A5%E6%9C%AC%E6%B8%B8%E8%AE%B0/20240210022207059.png&#34; alt=&#34;&#34; /&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://cf.s3.soulter.top/%E6%97%A5%E6%9C%AC%E6%B8%B8%E8%AE%B0/20240210022219234.png&#34; alt=&#34;&#34; /&gt;&lt;br /&gt;
爬了一个小时，终于登到山顶。&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://cf.s3.soulter.top/%E6%97%A5%E6%9C%AC%E6%B8%B8%E8%AE%B0/20240210022257247.png&#34; alt=&#34;&#34; /&gt;&lt;br /&gt;
这个场景非常有日漫的氛围呢！&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://cf.s3.soulter.top/%E6%97%A5%E6%9C%AC%E6%B8%B8%E8%AE%B0/20240210022330055.png&#34; alt=&#34;&#34; /&gt;&lt;br /&gt;
沿着一条预定好的路线向清水寺步行，虽说耽误时间，但是也更能够领略到这座城市的风貌。当天天气非常好，温度适宜。微风时不时吹来，空气中弥漫着湖边的气息。&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://cf.s3.soulter.top/%E6%97%A5%E6%9C%AC%E6%B8%B8%E8%AE%B0/20240210023935416.png&#34; alt=&#34;&#34; /&gt;&lt;br /&gt;
这个地方给我的第一印象就是干净和精致。地上没有一点垃圾，寺庙的修缮和植物的护理也做得相当好。加上周围宁静的环境氛围，给我的感觉就有点像桃花源记中归隐于世的桃花源。&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://cf.s3.soulter.top/%E6%97%A5%E6%9C%AC%E6%B8%B8%E8%AE%B0/20240210022536701.png&#34; alt=&#34;&#34; /&gt;&lt;br /&gt;
抵达清水寺。&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://cf.s3.soulter.top/%E6%97%A5%E6%9C%AC%E6%B8%B8%E8%AE%B0/20240210022632290.png&#34; alt=&#34;&#34; /&gt;&lt;br /&gt;
中日友好~&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://cf.s3.soulter.top/%E6%97%A5%E6%9C%AC%E6%B8%B8%E8%AE%B0/20240210022749143.png&#34; alt=&#34;&#34; /&gt;&lt;br /&gt;
清水寺旁的小巷。&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://cf.s3.soulter.top/%E6%97%A5%E6%9C%AC%E6%B8%B8%E8%AE%B0/20240210022945254.png&#34; alt=&#34;&#34; /&gt;&lt;br /&gt;
随后，我们乘坐电车又来到了京都动画公司的一个工作室所在地。玻璃窗上贴着《吹响吧 上低音号》的第四季。京阿尼！&lt;/p&gt;
&lt;p&gt;说巧不巧，当天晚上，对京阿尼第一工作室纵火的犯罪分子被判决了死刑。&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://cf.s3.soulter.top/%E6%97%A5%E6%9C%AC%E6%B8%B8%E8%AE%B0/20240210022835060.png&#34; alt=&#34;&#34; /&gt;&lt;br /&gt;
回酒店的路上。&lt;/p&gt;
&lt;h2 id=&#34;day4&#34;&gt;&lt;a class=&#34;markdownIt-Anchor&#34; href=&#34;#day4&#34;&gt;&lt;/a&gt; DAY.4&lt;/h2&gt;
&lt;p&gt;&lt;img src=&#34;https://cf.s3.soulter.top/%E6%97%A5%E6%9C%AC%E6%B8%B8%E8%AE%B0/20240210023537338.png&#34; alt=&#34;&#34; /&gt;&lt;br /&gt;
道顿掘旁的美食街。夸张华丽的装饰。&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://cf.s3.soulter.top/%E6%97%A5%E6%9C%AC%E6%B8%B8%E8%AE%B0/20240210023613782.png&#34; alt=&#34;&#34; /&gt;&lt;br /&gt;
道顿掘就是这条运河，全长约2.5公里，与木津川及东横堀川连接。&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://cf.s3.soulter.top/%E6%97%A5%E6%9C%AC%E6%B8%B8%E8%AE%B0/20240210024552418.png&#34; alt=&#34;&#34; /&gt;&lt;br /&gt;
&lt;img src=&#34;https://cf.s3.soulter.top/%E6%97%A5%E6%9C%AC%E6%B8%B8%E8%AE%B0/20240210023849655.png&#34; alt=&#34;&#34; /&gt;&lt;br /&gt;
街头的一些文艺工作者。&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://cf.s3.soulter.top/%E6%97%A5%E6%9C%AC%E6%B8%B8%E8%AE%B0/20240210024628465.png&#34; alt=&#34;&#34; /&gt;&lt;br /&gt;
被广告铺满的壮观的建筑。&lt;/p&gt;
&lt;p&gt;逛完这里，我们接下来就飞往了北海道地区的札幌。&lt;/p&gt;
</content>
        <category term="2024" />
        <category term="日本" />
        <updated>2024-02-10T03:02:00.000Z</updated>
    </entry>
    <entry>
        <id>https://blog.soulter.top/posts/%E5%BF%AB%E9%A4%90%E5%BC%8F%E6%97%85%E6%B8%B8%E5%92%8C%E8%A7%86%E9%A2%91.html</id>
        <title>社评 / 谈谈旅游和快餐式视频</title>
        <link rel="alternate" href="https://blog.soulter.top/posts/%E5%BF%AB%E9%A4%90%E5%BC%8F%E6%97%85%E6%B8%B8%E5%92%8C%E8%A7%86%E9%A2%91.html"/>
        <content type="html">&lt;p&gt;虽然这个标题可能已经烂大街了，毕竟「快餐式XX」这个词已经诞生很多年了，也有很多的媒体专题发表过相关的文章。在观看了 Youtube 博主錫蘭 Ceylan 的视频「Shorts正在摧毀YouTube，但我們都沒發現」之后，百感交集，于是想专门写篇文章浅谈一下自己的拙见。&lt;/p&gt;
&lt;h2 id=&#34;旅游&#34;&gt;&lt;a class=&#34;markdownIt-Anchor&#34; href=&#34;#旅游&#34;&gt;&lt;/a&gt; 旅游&lt;/h2&gt;
&lt;p&gt;我不是一个经常旅游的人，以往的寒暑假的活动基本都是以家为中心，以所在城市为半径。这次由于众多原因，心血来潮，下定决心花了 15 天赶赴日本旅游。&lt;/p&gt;
&lt;p&gt;在欣赏景点时，我偶然发现了令我印象比较深刻的一幕，大概是这样：一位女生向一个寺庙景点走来，挤入人群中，打开手机，按下拍照按钮，转头跟旁边的男生笑着说“OK，下一站是哪？”，然后两个人便脱离人群，朝其他方向走去。&lt;/p&gt;
&lt;p&gt;我开始对这个现象产生了好奇。我留意了一下周围的游客，我发现很大部分的游客对欣赏景点的流程都是：拍景点照/自拍/朝着景点自拍/叫其他人以景点为背景给自己拍照。并且我发现就连我自己也有时会这样。&lt;/p&gt;
&lt;p&gt;智能手机的普及的确给我们的生活带来了诸多便利，人生中太多无法弥补的失去也让我们越来越认识到「保存」的重要性。对景点拍照和录像确实是一种非常好的保存生活的方式，因为我们知道这个地方我们大抵只会来一次了，而拍照和录像之后我们便可以将这一次变为无限次。我们可以在未来某个无聊的时间点打开相册，通过照片和视频回顾生活的点滴，拾起记忆的碎片，并且还可能会发现当时没有发现的细节。&lt;/p&gt;
&lt;p&gt;但这似乎不代表我们在旅游时只是为了拍照——有点本末倒置了。&lt;/p&gt;
&lt;p&gt;我认为旅游是品味当地文化、将自己融入当地文化的一种契机，旅游对自己最大的好处之一就是能够感受异乡文化、对比自己所处的文化圈和当地文化圈的不同，并且吸纳其优良的文化。这是这里的文化包括但不局限于：风景、建筑、人文。这是一个拓宽自己眼界、认识差异的过程，也是一个长期的过程，需要我们用五官去持续且长时间的感受。如果光顾着拍照、自拍，把整个旅行当作快餐一样，浅尝辄止，五官就难以完整地感受到这些文化，可谓是因小失大了。&lt;/p&gt;
&lt;p&gt;这其实由很多方面造成，一方面，受到现在社会的快节奏的影响，我们似乎已经“习惯”了追求眼前所谓的效率，而细心品味文化太花时间了，假期又短，这使得我们没办法沉淀下来去耐心享受旅行，只能走马观花。这和短视频、速通电影的火爆现象本质一致。&lt;/p&gt;
&lt;p&gt;另一方面，我们都是社会性的动物，难以脱离社会而单独存在，我们有分享欲并渴望获得他人关注，我们喜欢把自己遇到的独特的事情记录下来上传到社交媒体上博得大家的青睐和关注。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Travel is about culture, not just photos —— 《The Arc browser》&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Google 了一番，看到网上也有和我类似的声音：&lt;/p&gt;
&lt;p&gt;&lt;a href=&#34;https://observingleslie.com/magazine/when-travel-is-just-about-the-photos&#34;&gt;When Travel is Just about the Photos&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;其中留言区的一则留言很能表达我的态度：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;我 100% 支持人们拍摄日常生活照片，记录大大小小的时刻，以及值得记住的事情，毕竟旅游是一件很有意义的事。我担心的那些在某些地方旅行但从未体验或者享受那个地方或时刻的人，他们只关心为他们的社交媒体账户获得尽可能最好的照片 😦&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id=&#34;关于长视频和短视频&#34;&gt;&lt;a class=&#34;markdownIt-Anchor&#34; href=&#34;#关于长视频和短视频&#34;&gt;&lt;/a&gt; 关于长视频和短视频&lt;/h2&gt;
&lt;p&gt;这一部分是我在看了 Ceylan 的视频之后才想写的，他和另一位顶流油管博主认为 Shorts 也就是短视频，正在摧毁 Youtube，因为 Shorts 让观众们越来越忽视创作背后的「Influencer」，也就是创作者。&lt;/p&gt;
&lt;p&gt;短视频，不论是出自 YouTube、抖音还是快手，多是借助时下流行的梗、新闻才得以火爆，然而这些选材门槛极低，人人都可以做，实在不会做，只要仿照别人也能做出一个像模像样的短视频，例如挑战XX时间内吃完XX、XX食物有害不能吃、科目三等等。在加上这些平台的主动的相似的内容投喂，用户便会慢慢忽视这个视频背后的创作者。并且会放弃主动性地检索信息，最终活在这些平台精准而无限的内容推流茧房中。&lt;/p&gt;
&lt;p&gt;我想说的是，短视频给观众和创作者都带来了危害。对于用户，如果没有节制，他们接受到了大量碎片而没意义的视频（当然不排除有一些质量高的短视频，这里是相对而言），虽然能够获得多巴胺，缓解一天的压力，但如果不多加注意，长此以往就容易变得懒惰，懒于去主动获取有价值的信息。并且一些数据也表明短视频的确会让人上瘾，就像吃垃圾食品一样停不下来。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;世界上没有任何一个人在刷完两个小时短视频之后对自己说：你知道吗，我看完之后感觉自己真满足。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;对于创作者，显而易见，由于用户开始不在乎视频背后是谁做的了，创作者和观众的关系就会越来越疏远。最终，正如视频里说的——创作者将成为观众获取多巴胺的一个齿轮。&lt;/p&gt;
&lt;p&gt;对我来说，我会更喜欢长视频，因为长视频虽然时间长，但是他能够给我带来更多的连续且有用的知识，并且时间长也会给自己留出更多的思考时间去分析这个视频的观点，而不会轻易被视频中的观点带节奏。&lt;/p&gt;
&lt;p&gt;我也非常认同 Ceylan 的观点：在这些平台和媒体当中，我们要学会经常性地「醒过来」，不管是在时间方面、还是在认知方面。在短视频平台眼中，用户的时间只是数字，我们越花时间在短视频平台上，平台就能够获得更多的广告收入。然而我们本该可以用这些被侵占的时间去做更有意义的事情。我们应该学会减少一些在快餐式的短视频洪流中驻足的时间。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;本文仅代表个人观点和意见。&lt;/strong&gt;&lt;/p&gt;
</content>
        <category term="2024" />
        <updated>2024-02-01T01:23:00.000Z</updated>
    </entry>
    <entry>
        <id>https://blog.soulter.top/posts/2023-yearly-record.html</id>
        <title>2023 年 / 总结</title>
        <link rel="alternate" href="https://blog.soulter.top/posts/2023-yearly-record.html"/>
        <content type="html">&lt;h2 id=&#34;累与矛盾&#34;&gt;&lt;a class=&#34;markdownIt-Anchor&#34; href=&#34;#累与矛盾&#34;&gt;&lt;/a&gt; 累与矛盾&lt;/h2&gt;
&lt;p&gt;过去从没有哪一年能如今年一样，如此迫切地想要写一篇文章来记录自己的这一年。&lt;/p&gt;
&lt;p&gt;如果要让我为我的 2023 经历做一个关键词总结，&lt;em&gt;&lt;strong&gt;”忙“&lt;em&gt;&lt;strong&gt;和&lt;/strong&gt;&lt;/em&gt;”矛盾“&lt;/strong&gt;&lt;/em&gt;。再贴切不过&lt;/p&gt;
&lt;p&gt;2023，是近些年来世界经济动荡最为明显的一年，&lt;/p&gt;
&lt;p&gt;是国内实体经济萎靡的一年，&lt;/p&gt;
&lt;p&gt;是大裁员的一年，&lt;/p&gt;
&lt;p&gt;也是我心路历程变化最大、最&lt;strong&gt;矛盾&lt;/strong&gt;的一年。&lt;/p&gt;
&lt;p&gt;曾在大一信誓旦旦地说毕业直接找工作的我，&lt;/p&gt;
&lt;p&gt;到了大二，由于成绩还算可观，慢慢改变了想法。&lt;/p&gt;
&lt;p&gt;我自认为是一个很容易受他人影响的人，&lt;/p&gt;
&lt;p&gt;大三前的暑假，看见周围的人开始为工作而准备面试，自己也心动了，慢慢地，就产生了对未来职业的焦虑。&lt;/p&gt;
&lt;p&gt;有同学曾问过我对未来的打算，我只好尴尬地回答：先”并行“发展吧。&lt;/p&gt;
&lt;p&gt;然而这分明就是两条非常矛盾的道路。&lt;/p&gt;
&lt;p&gt;大三的课很多，如果要兼顾课内成绩的同时还要为工作而考虑，时间将会非常紧。&lt;/p&gt;
&lt;p&gt;从七月底开始，咬着牙坚持了小半年，竟真给我坚持了下来。&lt;/p&gt;
&lt;p&gt;这小半年，我需要同时兼顾学业、实习、竞赛、开源项目，以及学校实验室的一些项目。&lt;/p&gt;
&lt;p&gt;只能说相当累。&lt;/p&gt;
&lt;p&gt;也不知道哪天晚上喝多了，竟做出这么个玩意儿：&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://cf.s3.soulter.top/2023/20231231114031828.png&#34; alt=&#34;&#34; /&gt;&lt;/p&gt;
&lt;p&gt;两次实习经历也让我深刻意识到了互联网企业是普遍存在加班现象的。&lt;/p&gt;
&lt;p&gt;尤其是小红书，大家默认都加班到晚上九点，甚至还保留着臭名昭著的大小周。当然，小红书的福利和办公氛围也是肉眼可见的好。&lt;/p&gt;
&lt;p&gt;可能就国内现状来说有点理想化了，但是我还是非常希望我以后的工作能够实现 &lt;strong&gt;Work-Life Balance&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;对于技术的掌握方面，从国内就业的角度出发也是主打一个「矛盾」。年初由于目标不清晰，定下了前端的目标，开始吭哧吭哧学 &lt;code&gt;js&lt;/code&gt; 和 &lt;code&gt;vue.js&lt;/code&gt;，然后学到一半又转去学 &lt;code&gt;golang&lt;/code&gt;，学完之后呢，又转回“温习” &lt;code&gt;java&lt;/code&gt;。别急，到了暑假，偶然间听到了数据库大赛，感觉感兴趣，又开始学习数据库内核，看&lt;code&gt;CMU 15-445&lt;/code&gt;。现在，又主要用 &lt;code&gt;Python&lt;/code&gt; 进行开发工作了。真是把国内主流的技术栈学了个遍。&lt;/p&gt;
&lt;p&gt;当然在我看来，技术不分具体的实现，归根到底都是一个东西。我个人更倾向于不将技术划分得太细，软件开发就是软件开发，如果不是巨型企业需要从宏观上区分，其他企业不应该将软件开发人员的技术栈划分得太细（当然应用层、底层这种比较大的层面还是需要明确的）。&lt;/p&gt;
&lt;p&gt;（学了这么多领域，越来越发现 &lt;code&gt;python&lt;/code&gt; 是多么地人性化x）&lt;/p&gt;
&lt;p&gt;在未来，读研的方向大概率就是 &lt;code&gt;Database&lt;/code&gt; 了**（？）**。（当然，从个人兴趣出发，也有其他的方向，比如 &lt;code&gt;AIGC&lt;/code&gt;，&lt;code&gt;虚拟现实&lt;/code&gt;）&lt;/p&gt;
&lt;h2 id=&#34;初入&#34;&gt;&lt;a class=&#34;markdownIt-Anchor&#34; href=&#34;#初入&#34;&gt;&lt;/a&gt; 初入 🎸&lt;/h2&gt;
&lt;p&gt;不知是不是看了《孤独摇滚》的缘故，我今年开始入坑 Guitar 了。&lt;/p&gt;
&lt;p&gt;其实主要是以前对一些轻音乐特别喜欢，特别想演奏一番的缘故吧，趁着这个热度就顺手点上这个技能点了（）&lt;/p&gt;
&lt;p&gt;当然也有一小部分原因是年初还没走出上一段恋情的阴影，想靠艺术宣泄一下自己，并且还可以让前对象知道自己很了不起什么的hhh（（（&lt;/p&gt;
&lt;p&gt;跟着 Bilibili 上的一个 UP “吉他大学霸” 学了一下基础乐理和和弦，慢慢地也能“野弹”一两首了（不过很菜就是了 😦）&lt;/p&gt;
&lt;p&gt;放上练习一首曲子后的惨照😕：&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://cf.s3.soulter.top/2023/20240113091522406.png&#34; alt=&#34;&#34; /&gt;&lt;/p&gt;
&lt;p&gt;和大多数人一上来弹的《成都》等国内之名入门吉他曲不同，我比较倾向于弹 ACG 的曲子，毕竟受这方面影响更深。&lt;/p&gt;
&lt;p&gt;看到网上有人提到自己刚入门就放弃在了爬格子上，我个人其实没有系统地去练习爬格子（也许就爬了十分钟不到），当时最想的还是去直接上手弹喜欢的那首曲子。&lt;/p&gt;
&lt;p&gt;刚开始一直在感叹和弦怎么这么难记、难切换！其实所谓熟能生巧确实是对的，练多了速度自然就跟上来了（可能需要花费你 5-7 天不等，每天四五个小时吧）。&lt;/p&gt;
&lt;p&gt;想嘲笑一番？我把最近的一个练习成果（2023.08）发布到了 Youtube 和 Bilibili 上，勿喷…&lt;/p&gt;
&lt;div align=&#34;center&#34;&gt;
&lt;iframe src=&#34;https://www.youtube.com/embed/Q5N1Ys3y6SI?si=yl4LU0-jzWPdmRhF&#34; title=&#34;YouTube video player&#34; frameborder=&#34;0&#34; allow=&#34;accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share&#34; allowfullscreen&gt;&lt;/iframe&gt;
&lt;/div&gt;
&lt;p&gt;放上跟练的大佬的 Youtube 视频链接：&lt;a href=&#34;https://www.youtube.com/watch?v=DxY4OvxREPM&#34;&gt;戳这里!&lt;/a&gt;&lt;/p&gt;
&lt;h2 id=&#34;为世界贡献&#34;&gt;&lt;a class=&#34;markdownIt-Anchor&#34; href=&#34;#为世界贡献&#34;&gt;&lt;/a&gt; 为世界贡献&lt;/h2&gt;
&lt;p&gt;2023 年，在 GitHub 上共提交了 1,512 次，收获了 1.1k 的 stars。过去一年，Coding 了 1,037 小时。希望未来的我也能保持这空前的热爱吧&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://cf.s3.soulter.top/2023/20240113084807578.png&#34; alt=&#34;&#34; /&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://cf.s3.soulter.top/2023/20240113084839782.png&#34; alt=&#34;&#34; /&gt;&lt;/p&gt;
&lt;p&gt;收到其他人提交的 Pull Request 时确实感到挺自豪、挺有成就感的，感觉自己融入了世界级的开发圈子。&lt;/p&gt;
&lt;h2 id=&#34;杂感&#34;&gt;&lt;a class=&#34;markdownIt-Anchor&#34; href=&#34;#杂感&#34;&gt;&lt;/a&gt; 杂感&lt;/h2&gt;
&lt;p&gt;心境上，发现自己慢慢地变得不太在意一些初高中时非常在意的东西了，比如说成绩。的确仍然希望得到好成绩，但是没有向初高中、大一那时如此渴求了。&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;偶然发现 wikipedia 上有各年的大事年表，这是 2023 年的：&lt;a href=&#34;https://zh.wikipedia.org/wiki/2023%E5%B9%B4&#34;&gt;2023 - 大事年表&lt;/a&gt;&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;或许真就开始怀念过去的生活了，没想到到头来自己所追求的目标是以前的自己。&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://cf.s3.soulter.top/2023/20231231115609996.png&#34; alt=&#34;&#34; /&gt;&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;过去一年，观看了《白圣女与黑牧师》《Darling in the FRANXX》《铃芽之旅》《我推的孩子》《久保同学不放过我》《夏日幻魂》，以及一些番剧的二周目。&lt;/p&gt;
&lt;p&gt;然而这些都是前半年观看的。过去半年，由于实在抽不出时间，基本上就没有好好看过一部番剧。&lt;/p&gt;
&lt;p&gt;几年前，对动漫钟爱有加，看到 bilibili 上的一句评论：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;你们继续支持！我长大了，要退坑了。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;当时还不以为然&lt;/p&gt;
&lt;p&gt;而现在现在越来越能理解这句话了。&lt;/p&gt;
&lt;p&gt;我也处于退坑的边缘了？&lt;/p&gt;
&lt;p&gt;不好说。&lt;/p&gt;
&lt;p&gt;至少，一定不会是我主动选择离开。&lt;/p&gt;
&lt;p&gt;我能有如今的成就，一定有很大一部分归功于动漫带给我的动力。&lt;/p&gt;
&lt;p&gt;很难忘记《刀剑神域》的主人公就是我以前体育中考跑 1000 米时坚持下来并且成功拿下满分的动力。&lt;/p&gt;
&lt;p&gt;不过还是强推：《夏日幻魂》！&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;暑假前夕，碰巧和 nelson 长时间体验了一下实验室的 VR（Pico 4），玩了一下 &lt;code&gt;The Forest&lt;/code&gt; 和 &lt;code&gt;Minecraft&lt;/code&gt; 两款游戏，感觉真奇妙！尤其是 The Forest，你必须真的挥动手柄才能砍树，就跟现实的砍树姿势一样！沉浸感满满🤩（好像 MC 还不支持这样，快快开发一个选项出来！）&lt;/p&gt;
&lt;p&gt;要是茅场晶言能在我有生之年开发出出完全潜行设备和没有登出键的刀剑神域游戏就好了☺️&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://cf.s3.soulter.top/2023/20240113093902426.png&#34; alt=&#34;&#34; /&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;照片里的不是我，我是拍照的那个&lt;/li&gt;
&lt;li&gt;为什么坐着：type-c 线太短了&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src=&#34;https://cf.s3.soulter.top/2023/20240113093917006.png&#34; alt=&#34;&#34; /&gt;&lt;/p&gt;
&lt;p&gt;Apple 的 Vision Pro 也快上市了，虽然 3999 bucks 完全买不起，但是如果能看到量产产品的体验真的能够做到宣传片那样，就心满意足了，至少人类现在真的能够量产出如此酷炫的产品！&lt;/p&gt;
&lt;h2 id=&#34;目标&#34;&gt;&lt;a class=&#34;markdownIt-Anchor&#34; href=&#34;#目标&#34;&gt;&lt;/a&gt; 目标？&lt;/h2&gt;
&lt;p&gt;在 2024 年，立下几个小目标：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;尽量每天 00:00 前睡觉。&lt;/li&gt;
&lt;li&gt;尽量每天跑一到两圈操场。&lt;/li&gt;
&lt;li&gt;准备保研！&lt;/li&gt;
&lt;li&gt;在小公司找一份轻松、感兴趣的实习&lt;/li&gt;
&lt;li&gt;买天文望远镜&lt;/li&gt;
&lt;li&gt;买 VR&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;update: 上面的 6 条目标已经有 4 条被打破了 😕&lt;/p&gt;
&lt;h2 id=&#34;posts-in-the-last-year&#34;&gt;&lt;a class=&#34;markdownIt-Anchor&#34; href=&#34;#posts-in-the-last-year&#34;&gt;&lt;/a&gt; Posts in the last year&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;记录一下自己过去一年中遇到的难忘的事 提取自 WeChat&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr /&gt;
&lt;p&gt;寒假打算实现一个梦寐以求多年的梦想，去日本旅游几天。作为一个没出过国的人来说，还是很兴奋的！&lt;br /&gt;
&lt;img src=&#34;https://cf.s3.soulter.top/2023/20231231114116845.png&#34; alt=&#34;&#34; /&gt;&lt;/p&gt;
&lt;p&gt;2024.01.02 UPDATED: 我淦，日本地震了😓&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;Starship二号机&lt;/p&gt;
&lt;p&gt;发射场面真壮观啊&lt;/p&gt;
&lt;p&gt;看来是两个都炸了(一级没有回收的计划)👀&lt;/p&gt;
&lt;p&gt;过了MAX-Q&lt;/p&gt;
&lt;p&gt;感觉已经进步很多了，至少分离前一级发动机没熄火（）&lt;/p&gt;
&lt;p&gt;Starship本体飞到了148KM&lt;br /&gt;
&lt;img src=&#34;https://cf.s3.soulter.top/2023/20231231114236326.png&#34; alt=&#34;&#34; /&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://cf.s3.soulter.top/2023/20231231114259618.png&#34; alt=&#34;&#34; /&gt;&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;在搜狐实习告一段落啦~🥳&lt;br /&gt;
&lt;img src=&#34;https://cf.s3.soulter.top/2023/20231231114408432.png&#34; alt=&#34;&#34; /&gt;&lt;/p&gt;
&lt;hr /&gt;
&lt;blockquote&gt;
&lt;p&gt;这或许是今年做过的最有意义的一件事之一了。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;为期两周半的数据库内核赛告一段落啦，暂时在全国范围内获得第 62/1154 的名次，校一，可惜差6名进复赛。这些天日均8小时代码时长，debug太痛苦了；；&lt;br /&gt;
不过确实还是学到了很多有意思的东西，从写语法树解析，update，like，再到实现join tables，order by，group by，再到复杂子查询等等，看着数据库的功能一点一点被亲手设计和实现还是很有成就感的🍻&lt;/p&gt;
&lt;p&gt;这算是我参加的第一个如此长时间全身心投入的真正意义上以编程能力为评判标准的比赛了，发条说说纪念一下&lt;/p&gt;
&lt;p&gt;这比赛太卷了&lt;/p&gt;
&lt;p&gt;感谢北科发的奖励&lt;/p&gt;
&lt;p&gt;下次一定！&lt;br /&gt;
&lt;img src=&#34;https://cf.s3.soulter.top/2023/20231231114456878.png&#34; alt=&#34;&#34; /&gt;&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;蛙趣，自己的 hugging-chat-api 项目上电视了！&lt;br /&gt;
&lt;img src=&#34;https://cf.s3.soulter.top/2023/20231231114541812.png&#34; alt=&#34;&#34; /&gt;&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;几年来见过的最行为艺术的塔家方式😂😂&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://cf.s3.soulter.top/2023/20231231114613939.png&#34; alt=&#34;&#34; /&gt;&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;hugging-chat-api 项目被 huggingface CTO cue了：D&lt;br /&gt;
&lt;img src=&#34;https://cf.s3.soulter.top/2023/20231231114736087.png&#34; alt=&#34;&#34; /&gt;&lt;/p&gt;
&lt;hr /&gt;
</content>
        <category term="2024" />
        <updated>2024-01-08T11:40:00.000Z</updated>
    </entry>
    <entry>
        <id>https://blog.soulter.top/posts/ElasticSearch.html</id>
        <title>ElasticSearch 的一些内部原理</title>
        <link rel="alternate" href="https://blog.soulter.top/posts/ElasticSearch.html"/>
        <content type="html">&lt;h1 id=&#34;部署的注意事项&#34;&gt;&lt;a class=&#34;markdownIt-Anchor&#34; href=&#34;#部署的注意事项&#34;&gt;&lt;/a&gt; 部署的注意事项&lt;/h1&gt;
&lt;p&gt;网上已经有很多部署教程了，这里不做详细阐述&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;注意如果要自己安装，jdk要用oracle的rpm包。&lt;/li&gt;
&lt;li&gt;关闭高水位只读限制：&lt;/li&gt;
&lt;/ul&gt;
&lt;figure class=&#34;highlight python&#34;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&#34;gutter&#34;&gt;&lt;pre&gt;&lt;span class=&#34;line&#34;&gt;1&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&#34;code&#34;&gt;&lt;pre&gt;&lt;span class=&#34;line&#34;&gt;curl -XPUT -H &lt;span class=&#34;string&#34;&gt;&amp;quot;Content-Type: application/json&amp;quot;&lt;/span&gt; http://&lt;span class=&#34;number&#34;&gt;192.168&lt;/span&gt;&lt;span class=&#34;number&#34;&gt;.111&lt;/span&gt;&lt;span class=&#34;number&#34;&gt;.300&lt;/span&gt;:&lt;span class=&#34;number&#34;&gt;8060&lt;/span&gt;/_&lt;span class=&#34;built_in&#34;&gt;all&lt;/span&gt;/_settings -d &lt;span class=&#34;string&#34;&gt;&amp;#x27;&amp;#123;&amp;quot;index.blocks.read_only_allow_delete&amp;quot;: null&amp;#125;&amp;#x27;&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/figure&gt;
&lt;h1 id=&#34;原理&#34;&gt;&lt;a class=&#34;markdownIt-Anchor&#34; href=&#34;#原理&#34;&gt;&lt;/a&gt; 原理&lt;/h1&gt;
&lt;h2 id=&#34;存储&#34;&gt;&lt;a class=&#34;markdownIt-Anchor&#34; href=&#34;#存储&#34;&gt;&lt;/a&gt; 存储&lt;/h2&gt;
&lt;p&gt;ElasticSearch 基于 Apache Lucene 倒排索引实现，比关系型数据库如MySQL过滤更快。&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://cf.s3.soulter.top/20231027082224923/20231027082331610.png&#34; alt=&#34;&#34; /&gt;&lt;/p&gt;
&lt;p&gt;MySQL通过将 Term Dictionary 以 B-Tree/B+Tree 的形式存到磁盘中。将多个值作为一个 tuple 通过连续区间存放。 能减少寻道次数。&lt;/p&gt;
&lt;p&gt;Lucene 在 Term Dictionary 的基础上加了一层索引——Term Index（在内存中，并且可以使用一些压缩技术, 如 FST , 来减少内存占用）,通过它可以快速找到 Term Dictionary 的块的位置之后再去磁盘上找term. 这样就可以快速根据 term 找到 Posting list, 从而快速找到数据.&lt;/p&gt;
&lt;h3 id=&#34;压缩技巧&#34;&gt;&lt;a class=&#34;markdownIt-Anchor&#34; href=&#34;#压缩技巧&#34;&gt;&lt;/a&gt; 压缩技巧&lt;/h3&gt;
&lt;p&gt;存 Term Index 的压缩方案: FST&lt;/p&gt;
&lt;p&gt;&lt;a href=&#34;https://cs.nyu.edu/~mohri/pub/fla.pdf&#34;&gt;fla.dvi (nyu.edu)&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;存 PostingList 的压缩方案: 增量编码存储和Roaring BitMaps。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;增量编码存储&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;通过增量，将原来的大数变成小数仅存储增量值，再精打细算按 Bit 排好队，最后通过字节存储。&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://cf.s3.soulter.top/20231027082224923/20231027082345228.png&#34; alt=&#34;&#34; /&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Roaring BitMaps&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;如 [1,3,4,7,10] 这个 Posting BitMap 对应的Bitmap 就是：[1,0,1,1,0,0,1,0,0,1]。BitMap第 0 对应 PostingList 的1。&lt;/p&gt;
&lt;p&gt;可想而知，这会指数级地增加BitMap的大小. 因此 ES 对其做了限制：Posting List 以 65536 为界限分割。这样如果仅用 BitMap，那最多只有 65536bit，即 8KiB。如果当前块仅有少于 4096 个值, 那么直接用 Short[] 来存而不用 BitMap。(这也说明了为什么是65536)&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://cf.s3.soulter.top/20231027082224923/20231027082354481.png&#34; alt=&#34;&#34; /&gt;&lt;/p&gt;
&lt;h3 id=&#34;联合索引&#34;&gt;&lt;a class=&#34;markdownIt-Anchor&#34; href=&#34;#联合索引&#34;&gt;&lt;/a&gt; 联合索引&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;获得多个索引的 BitMap，然后&lt;strong&gt;按位与&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;利用跳表。&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 id=&#34;写入&#34;&gt;&lt;a class=&#34;markdownIt-Anchor&#34; href=&#34;#写入&#34;&gt;&lt;/a&gt; 写入&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;路由选择协议通过设置的 routing 或者hash 确定写入到哪一个 Shard。(一个 Shard 包含多个节点，Primary 和 Replica 节点)， 然后找到 Primary 节点.&lt;/li&gt;
&lt;li&gt;写入数据到该节点： 写入到其内存缓冲区中，再写入 Translog 的缓冲区中， Translog 实时刷入磁盘。 一定时间 (1s) 后，将缓存的 doc 形成一个新的 Segment，并打开该 Segment 使其可以被搜索(&lt;strong&gt;Refresh&lt;/strong&gt;)，并生成一个新的Commit point 来记录当前可用的Segment。一定时间间隔后（或者translog足够大时），执行 Commit， 将缓存的所有 Segment 写入磁盘(&lt;strong&gt;Flush&lt;/strong&gt;).&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;*Segment 已经写入不可更新和删除，可以打标记。&lt;/p&gt;
&lt;p&gt;*ES会定期将小的 Segment 和使用量小的 Segment 合并, 合并时如果发现其 doc 在 .del中有记录就删掉，并且 .del 也删掉.&lt;/p&gt;
&lt;h2 id=&#34;集群&#34;&gt;&lt;a class=&#34;markdownIt-Anchor&#34; href=&#34;#集群&#34;&gt;&lt;/a&gt; 集群&lt;/h2&gt;
&lt;h3 id=&#34;node&#34;&gt;&lt;a class=&#34;markdownIt-Anchor&#34; href=&#34;#node&#34;&gt;&lt;/a&gt; Node&lt;/h3&gt;
&lt;p&gt;支持水平扩容，PB 级别的存储量.&lt;/p&gt;
&lt;p&gt;节点加入后默认是 &lt;strong&gt;Data Node, Ingest Node, Master Eligible Node, Coordinating Node。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Ingest Node 用来对文档在编制成索引之前进行预处理, 因此 CPU 需求较大.&lt;/p&gt;
&lt;p&gt;Coordinating Node 处理/分发路由请求, 并对请求结果进行汇总.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;最佳实践: 为一个集群分配多个 Master Node， 每个 Master Node 只承担一个角色。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Master Node： 创建，删除索引。决定分片被分配到那个节点。维护并更新 Cluster State&lt;/p&gt;
&lt;h3 id=&#34;发现机制&#34;&gt;&lt;a class=&#34;markdownIt-Anchor&#34; href=&#34;#发现机制&#34;&gt;&lt;/a&gt; 发现机制&lt;/h3&gt;
&lt;blockquote&gt;
&lt;ol&gt;
&lt;li&gt;新增节点时要用到&lt;/li&gt;
&lt;/ol&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;ol start=&#34;2&#34;&gt;
&lt;li&gt;Master 节点故障时，要通过选举找到新的 Master.&lt;/li&gt;
&lt;/ol&gt;
&lt;/blockquote&gt;
&lt;p&gt;节点间通信是通过&lt;strong&gt;基于 AIO 的 TCP 实现的Transport 模块&lt;/strong&gt;实现的.&lt;/p&gt;
&lt;p&gt;一个节点启动之后通过 Seed Hosts Providers 得到 Seed addresses，然后在这之中去一个一个尝试连接, 直到找到足够数量的 Master Eligible Node 可以参与选举 Master Node 为止。如果没有 Node，则会每隔 1s 重复搜索。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;选主&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;在所有的 Quorums 中选择一个 Node 作为 Master Node。Quorums 是集群中所有 Master Eligible Node 的一个子集&lt;/p&gt;
&lt;p&gt;采用抢占式选举：如果 Master 挂掉之后, Master Eligible 会&lt;strong&gt;随机时间&lt;/strong&gt;进行选举, 第一个选举的会成为新的 Master。 当多个 Master Eligible &lt;strong&gt;同时&lt;/strong&gt;选举时, 选举失败并重试.&lt;/p&gt;
&lt;h2 id=&#34;分片-shard-和故障转移&#34;&gt;&lt;a class=&#34;markdownIt-Anchor&#34; href=&#34;#分片-shard-和故障转移&#34;&gt;&lt;/a&gt; 分片 Shard 和故障转移&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;Shard 是物理空间概念，索引中的数据都分布在分片上，一个分片就是运行的一个 Lucene 的实例。&lt;/strong&gt; 主分片支持读写，副分片只读，主分片写入时要同步给副分片。相同的索引，主分片和副分片不能存在同一个 Data Node。&lt;/p&gt;
&lt;p&gt;主节点维护：Index 的信息、Shard 上的数据信息&lt;/p&gt;
&lt;p&gt;数据如何均匀分配到不同分片? 使用 Hash。&lt;/p&gt;
&lt;figure class=&#34;highlight bash&#34;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&#34;gutter&#34;&gt;&lt;pre&gt;&lt;span class=&#34;line&#34;&gt;1&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&#34;code&#34;&gt;&lt;pre&gt;&lt;span class=&#34;line&#34;&gt;shard = &lt;span class=&#34;built_in&#34;&gt;hash&lt;/span&gt;(_routing) % number_of_primary_shards&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/figure&gt;
&lt;p&gt;_routing 默认是文档的 Id。可以自己指定:&lt;/p&gt;
&lt;figure class=&#34;highlight plaintext&#34;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&#34;gutter&#34;&gt;&lt;pre&gt;&lt;span class=&#34;line&#34;&gt;1&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;2&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;3&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;4&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;5&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&#34;code&#34;&gt;&lt;pre&gt;&lt;span class=&#34;line&#34;&gt;//插入数据时，通过 routing 参数指定 _routing&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;PUT users/_doc/100?routing=china&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;&amp;#123;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;  &amp;quot;title&amp;quot;: &amp;quot;china&amp;quot;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;&amp;#125;&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/figure&gt;
&lt;p&gt;流程:&lt;/p&gt;
&lt;figure class=&#34;highlight plaintext&#34;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&#34;gutter&#34;&gt;&lt;pre&gt;&lt;span class=&#34;line&#34;&gt;1&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;2&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;3&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&#34;code&#34;&gt;&lt;pre&gt;&lt;span class=&#34;line&#34;&gt;1. 更新文档的请求先发送到某一个 Coordinating Node 上&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;2. Coordinating Node 根据 hash 算法计算该分配哪个分片上&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;3. 在对应的分片上先删除文档再新建文档&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/figure&gt;
&lt;h3 id=&#34;企业内部-es-流量梳理&#34;&gt;&lt;a class=&#34;markdownIt-Anchor&#34; href=&#34;#企业内部-es-流量梳理&#34;&gt;&lt;/a&gt; 企业内部 ES 流量梳理&lt;/h3&gt;
&lt;p&gt;HA → LVS(负载均衡) → ES集群 → ES主节点→ES数据节点（热、冷）&lt;/p&gt;
&lt;h3 id=&#34;es-分布式一致性协议&#34;&gt;&lt;a class=&#34;markdownIt-Anchor&#34; href=&#34;#es-分布式一致性协议&#34;&gt;&lt;/a&gt; ES 分布式一致性协议&lt;/h3&gt;
&lt;p&gt;6 → bully（zen协议）&lt;/p&gt;
&lt;p&gt;7以后 → raft 协议&lt;/p&gt;
&lt;h3 id=&#34;优化&#34;&gt;&lt;a class=&#34;markdownIt-Anchor&#34; href=&#34;#优化&#34;&gt;&lt;/a&gt; 优化&lt;/h3&gt;
&lt;p&gt;ES&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;mapping优化&lt;/li&gt;
&lt;li&gt;scroll&lt;/li&gt;
&lt;li&gt;filter&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;JVM&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;指针压缩&lt;/li&gt;
&lt;li&gt;GC线程数调整&lt;/li&gt;
&lt;li&gt;GC内存空间调整， 各个代际的调整&lt;/li&gt;
&lt;li&gt;heap外可用空间调整&lt;/li&gt;
&lt;/ul&gt;
</content>
        <category term="2023" />
        <updated>2023-10-16T16:08:00.000Z</updated>
    </entry>
    <entry>
        <id>https://blog.soulter.top/posts/start-opensource.html</id>
        <title>回想 / 我与开源</title>
        <link rel="alternate" href="https://blog.soulter.top/posts/start-opensource.html"/>
        <content type="html">&lt;h1 id=&#34;萌芽&#34;&gt;&lt;a class=&#34;markdownIt-Anchor&#34; href=&#34;#萌芽&#34;&gt;&lt;/a&gt; 萌芽&lt;/h1&gt;
&lt;p&gt;在我初二的那个时候，学校开了 C 语言的竞赛班，从此我就对编程产生了极大的兴趣。不知不觉间，快一年之后，我在百度贴吧上接触到了⌈ IAPP ⌋ 这个软件，在看了大触们的软件的分享之后，我开始萌生自己做一个手机软件的想法，但是，当时对这方面完全不熟悉呀。偶然间，我找到了一个关于聊天室（IM）的帖子，作者还公开了他的源代码！于是就直接 copy 下来，把代码中的中文、字体颜色全部换了个遍，做出了“属于自己的”第一个软件。兴致满满地在吧里发了一个帖子，希望有人能够下载“我的”软件，玩玩“我的”软件。（当然逃不过大佬们的火眼金睛，还是被发现了是抄袭的:) 当时连着好几天都没敢打开贴吧）&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://cf.s3.soulter.top/20231012005140963/20231012125236314.png&#34; alt=&#34;&#34; /&gt;&lt;/p&gt;
&lt;p&gt;到后来，这个软件也没有多少人使用。不过我却对此越来越痴迷：我渴求自己的作品能够被大家所发现、使用和讨论。&lt;/p&gt;
&lt;p&gt;到了高一上册，积累了一些 Java、Android、Python 的经验。这期间，我了解到了酷安，听说到这个应用市场上发布软件非常容易，在仔细调研过之后，发现确实有很多的开发者发布的“小玩具”，很多没有图标的、体积一个比一个小的 APP 就是一个很好的证明。说实话，这给了我很大的信心，让我认识到这些大平台也有很低的门槛。&lt;/p&gt;
&lt;p&gt;我曾想出过很多很多 Idea，其中的很大一部分都实现成了 Android 软件，比如《悬浮游戏》——一个以悬浮窗的形式运行 JS 游戏的软件，制作的原因是考虑到很多的用户在等待软件或者游戏加载时会觉得无聊，这个软件可以很好地消磨这个时光。再比如《SoPhoneInfo》——一个以悬浮窗的形式显示手机信息，比如内存占用、网络速度等信息的软件；《FloatingPicture》——一个以悬浮窗的形式显示图片的软件，支持调节透明度；……（类似的软件还有很多，想知道的朋友可以留言一波）不过这些软件最终都没有被我发布到酷安上，原因是自己不断认为自己的软件还做得不够好，在不断的软件改进中消磨了兴趣， 加上自己还是对发布软件保持谨慎和害怕，不断地在思考做这个软件的意义是什么，有没有其他的软件实现了这个功能……之后就没有再发布了，这些项目我至今都还保留着，他们像是纪念品一样，安静地躺在我的硬盘中的某处。在一个朋友的影响下，我了解到了 GitHub 这个平台（准确来说是开始真正使用，以前或多或少 Clone 过一些其中的仓库）。开发者将自己的作品以近乎无私的方式开放给所有的人，大家都可以下载这个仓库的代码来修改、运行。这不就是我所希望的吗！于是，我就将自己的所有的项目都开源到了 GitHub 上（当然当时写的代码基本都是屎山代码…献丑了 awa）。从此我就认识了开源。&lt;/p&gt;
&lt;h1 id=&#34;发展&#34;&gt;&lt;a class=&#34;markdownIt-Anchor&#34; href=&#34;#发展&#34;&gt;&lt;/a&gt; 发展&lt;/h1&gt;
&lt;p&gt;如今，我已步入大学3年，这期间做的一些项目被很多人使用：&lt;/p&gt;
&lt;p&gt;在 &lt;a href=&#34;https://github.com/Soulter/hugging-chat-api&#34;&gt;hugging-chat-api&lt;/a&gt; 仓库上我认识了许多的国际友人，他们为我的项目提供建议、提供解决方案。这个项目甚至受到了 HuggingFace CTO 的评论！尽管这个仓库的技术含量不高，只是逆向了一下 HuggingChat，但对于我来说，这份代码能够被如此大量的用户所使用，我已经非常满意了。&lt;/p&gt;
&lt;p&gt;我时常关注自己作品的使用量和大家的使用体验——这或许就是我持续维护开源项目的最大的动力。&lt;/p&gt;
&lt;p&gt;时常会感到非常迷茫。我平均每天都要花费 1-2 个小时的时间在开源项目的维护上，却没有什么经济上的等价收获（要恰饭的嘛）&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Where I realize my personal value&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;A proof of my faint existence&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;前些天，在 GitHub 上几个大佬的 Profile 上看到了这些话，翻译过来便是 “这是我实现个人价值的地方”、“我微小存在的证明”。&lt;/p&gt;
&lt;p&gt;我的收获就是帮到了、交到了如此多的来自世界各地的朋友，得到了大家的支持，最近每天 15+ 的 Issue 和 PR 邮件让我感觉我真正地活在了这个世界上。&lt;/p&gt;
&lt;p&gt;AstrBot（原来的QQChannelChatGPT）项目的部署和使用情况：&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://cf.s3.soulter.top/20231012005140963/20231012125301884.png&#34; alt=&#34;&#34; /&gt;&lt;/p&gt;
&lt;p&gt;国际友人们的 PR 之一（看看图右边的令人放心的 diff 计数！）：&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://cf.s3.soulter.top/20231012005140963/20231012125314622.png&#34; alt=&#34;&#34; /&gt;&lt;/p&gt;
&lt;p&gt;HuggingFace CTO 的评论🤗：&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://cf.s3.soulter.top/20231012005140963/20231012125322196.png&#34; alt=&#34;&#34; /&gt;&lt;/p&gt;
&lt;p&gt;hugging-chat-api 的下载量趋势&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://cf.s3.soulter.top/20231012005140963/20231012125400178.png&#34; alt=&#34;&#34; /&gt;&lt;/p&gt;
&lt;p&gt;我对热爱的定义是不计任何经济利益的持续付出，维持热爱还是需要有一定的正向反馈的。当然，我也无心谈论所谓的“我这是热爱还是不热爱”，只要我开心并且真正帮助到了大家就行。&lt;/p&gt;
&lt;p&gt;在如今多模态大模型横行、国内极度内卷的环境下，传统的内容发布行业势必会受到猛烈的冲击，UGC 中的很大一部分都将会转变为 AIGC，但是，仍然有不少人输出着原创内容。这其中不乏作家、画师、游戏开发者、软件开发者。他们冒着失业的风险仍做着自己热爱的事，他们将其当作自己的事业。我尊重每一个原创内容的创作者。&lt;/p&gt;
</content>
        <category term="2023" />
        <updated>2023-10-12T00:56:00.000Z</updated>
    </entry>
    <entry>
        <id>https://blog.soulter.top/posts/embbed-python.html</id>
        <title>Embeddable Python在工程实践上的使用</title>
        <link rel="alternate" href="https://blog.soulter.top/posts/embbed-python.html"/>
        <content type="html">&lt;p&gt;在维护 QQChannelChatGPT 项目时，发现很多人电脑上的 Python 版本与项目要求的 3.9+ 不一致，在一开始，我是写了一个启动脚本，自动在 &lt;a href=&#34;http://npm.taobao.org&#34;&gt;npm.taobao.org&lt;/a&gt; 下载合适的 Python 版本的 exe 文件。不过在最近越来越多的人反映中发现了一些问题：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Windows 下，安装了 Python 之后，由于 Windows 的一些机制，环境变量（PATH）需要重启终端或重启电脑之后才生效，一些人不知道这一点，也很难广泛科普。&lt;/li&gt;
&lt;li&gt;Windows下，一些人之前有安装过 Python，但是版本不合适，程序装了合适版本的 Python 后，又由于环境变量设置的问题，导致 &lt;code&gt;python&lt;/code&gt; 或 &lt;code&gt;python3&lt;/code&gt; 指令还是链接到原来的 Python 上。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;于是我就考虑开始使用 Embbed Python，内置一个 Python，这样就行了嘛orz。&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://cf.s3.soulter.top/20231002112107815/20231002112146200.png&#34; alt=&#34;&#34; /&gt;&lt;/p&gt;
&lt;p&gt;在 &lt;a href=&#34;http://python.org&#34;&gt;python.org&lt;/a&gt; 上，是有提供 Embeddable Python 的。并且 &lt;a href=&#34;http://npm.taobao.org&#34;&gt;npm.taobao.org&lt;/a&gt; 等国内镜像也有对应的文件，不用担心用户的梯子问题。由于下载下来是一个压缩包，需要写代码自行解压。只需要使用 python下的 &lt;code&gt;zipfile&lt;/code&gt; 库即可解压该文件。&lt;/p&gt;
&lt;p&gt;于是我就发现了问题：&lt;/p&gt;
&lt;figure class=&#34;highlight bash&#34;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&#34;gutter&#34;&gt;&lt;pre&gt;&lt;span class=&#34;line&#34;&gt;1&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;2&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;3&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&#34;code&#34;&gt;&lt;pre&gt;&lt;span class=&#34;line&#34;&gt;PS D:\Project\QQChatGPTLauncher\dist&amp;gt; .\python\python.exe -m pip&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;D:\Project\QQChatGPTLauncher\dist\python\python.exe: No module named pip&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;PS D:\Project\QQChatGPTLauncher\dist&amp;gt;&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/figure&gt;
&lt;p&gt;默认情况下是没有 pip 库的。这个倒是容易解决，于是我下载并使用 &lt;code&gt;get-pip.py&lt;/code&gt; 来安装。&lt;/p&gt;
&lt;figure class=&#34;highlight bash&#34;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&#34;gutter&#34;&gt;&lt;pre&gt;&lt;span class=&#34;line&#34;&gt;1&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;2&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;3&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;4&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&#34;code&#34;&gt;&lt;pre&gt;&lt;span class=&#34;line&#34;&gt;PS D:\Project\QQChatGPTLauncher\dist&amp;gt; .\python\python.exe get-pip.py&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;...(ignore)&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;PS D:\Project\QQChatGPTLauncher\dist&amp;gt; .\python\python.exe -m pip&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;D:\Project\QQChatGPTLauncher\dist\python\python.exe: No module named pip&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/figure&gt;
&lt;p&gt;安装成功后依然报错，&lt;/p&gt;
&lt;p&gt;在寻找了国内的网页后，没发现解决方案，于是转向国外搜索。在一篇相关的文章下，找到了这个：&lt;/p&gt;
&lt;figure class=&#34;highlight bash&#34;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&#34;gutter&#34;&gt;&lt;pre&gt;&lt;span class=&#34;line&#34;&gt;1&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;2&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;3&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;4&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;5&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;6&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;7&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;8&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;9&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;10&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&#34;code&#34;&gt;&lt;pre&gt;&lt;span class=&#34;line&#34;&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;Even &lt;span class=&#34;keyword&#34;&gt;if&lt;/span&gt; explicitly stated that the embeddable version of Python does not support Pip, it is possible with care. You need to:&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;Download and unzip Python embeddable zip file.&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;In the file python39._pth or similar, uncomment the import &lt;span class=&#34;built_in&#34;&gt;command&lt;/span&gt;. Result should look similar to this:&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;python39.zip&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;.&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;import site&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/figure&gt;
&lt;p&gt;于是我试着将 python310._pth 内的 &lt;code&gt;import site&lt;/code&gt; 的注释去掉，&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://cf.s3.soulter.top/20231002112107815/20231002112159905.png&#34; alt=&#34;&#34; /&gt;&lt;/p&gt;
&lt;p&gt;然后就可以用了。可喜可贺。&lt;/p&gt;
&lt;p&gt;接下来又发现了问题：&lt;/p&gt;
&lt;p&gt;使用了这个内置的Python之后，没办法 import 自己工程下的库。&lt;/p&gt;
&lt;p&gt;于是试着修改 python310._pth 文件，里面加上&lt;/p&gt;
&lt;figure class=&#34;highlight bash&#34;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&#34;gutter&#34;&gt;&lt;pre&gt;&lt;span class=&#34;line&#34;&gt;1&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&#34;code&#34;&gt;&lt;pre&gt;&lt;span class=&#34;line&#34;&gt;../QQChannelChatGPT&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/figure&gt;
&lt;p&gt;也就是以 python310._pth 所在目录下，以相对路径的方式把自己的工程项目放进去。&lt;/p&gt;
&lt;p&gt;再次重试，完美运行。&lt;/p&gt;
&lt;p&gt;那么， python310._pth 是什么呢？&lt;/p&gt;
&lt;p&gt;Python在遍历已知的库文件目录过程中，如果见到一个._pth 文件，&lt;strong&gt;就会将文件中所记录的路径加入到 sys.path 设置中&lt;/strong&gt;，于是 .pth 文件说指明的库也就可以被 Python 运行环境找到了。&lt;/p&gt;
&lt;p&gt;让我们来看看 &lt;code&gt;import site&lt;/code&gt; 会得到什么&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://cf.s3.soulter.top/20231002112107815/20231002112209274.png&#34; alt=&#34;&#34; /&gt;&lt;/p&gt;
&lt;p&gt;好吧，什么都没输出捏，还是看看源代码吧：&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&lt;code&gt;site&lt;/code&gt;&lt;/strong&gt; 模块会根据系统的配置和环境变量，修改 &lt;strong&gt;&lt;code&gt;sys.path&lt;/code&gt;&lt;/strong&gt;，即 Python 模块搜索路径。这可以确保 Python 解释器能够找到正确的模块和库。&lt;/p&gt;
</content>
        <category term="2023" />
        <updated>2023-10-02T11:02:00.000Z</updated>
    </entry>
    <entry>
        <id>https://blog.soulter.top/posts/about_git.html</id>
        <title>关于Git的一切（即将）</title>
        <link rel="alternate" href="https://blog.soulter.top/posts/about_git.html"/>
        <content type="html">&lt;blockquote&gt;
&lt;p&gt;创建于: 2023-09-18 19:02:00&lt;br /&gt;
最近一次更新: 2025-04-07 19:47:00&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h1 id=&#34;关于git的一切即将&#34;&gt;&lt;a class=&#34;markdownIt-Anchor&#34; href=&#34;#关于git的一切即将&#34;&gt;&lt;/a&gt; 关于Git的一切（即将）&lt;/h1&gt;
&lt;blockquote&gt;
&lt;p&gt;鉴于Git的工具属性，本博文记录我在项目开发上使用 Git 的一些心得和方法。本博文将以实际场景出发，列出大量场景并给出最好的解决方案（也许）。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id=&#34;将本地未提交的代码移动到另一个分支&#34;&gt;&lt;a class=&#34;markdownIt-Anchor&#34; href=&#34;#将本地未提交的代码移动到另一个分支&#34;&gt;&lt;/a&gt; 将本地&lt;strong&gt;未提交&lt;/strong&gt;的代码移动到另一个分支&lt;/h2&gt;
&lt;p&gt;在开发一个新功能时，如果没开发完，又接到另一个更加紧急的需求需要在这个分支上进行，此时需要将没开发完的代码移到另一个分支。&lt;/p&gt;
&lt;figure class=&#34;highlight bash&#34;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&#34;gutter&#34;&gt;&lt;pre&gt;&lt;span class=&#34;line&#34;&gt;1&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;2&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;3&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&#34;code&#34;&gt;&lt;pre&gt;&lt;span class=&#34;line&#34;&gt;git stash &lt;span class=&#34;comment&#34;&gt;# 暂存该分支未提交但已更改并保存的代码。&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;git checkout -b new-branch &lt;span class=&#34;comment&#34;&gt;# 以某一个分支为基础分出一个新分支&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;git stash pop &lt;span class=&#34;comment&#34;&gt;# 推出暂存的代码&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/figure&gt;
&lt;h2 id=&#34;本来打算取消某次-commit-来重新提交但是不小心误删drop-commit了如何恢复&#34;&gt;&lt;a class=&#34;markdownIt-Anchor&#34; href=&#34;#本来打算取消某次-commit-来重新提交但是不小心误删drop-commit了如何恢复&#34;&gt;&lt;/a&gt; 本来打算取消某次 commit 来重新提交，但是不小心误删（drop commit）了，如何恢复？&lt;/h2&gt;
&lt;figure class=&#34;highlight bash&#34;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&#34;gutter&#34;&gt;&lt;pre&gt;&lt;span class=&#34;line&#34;&gt;1&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&#34;code&#34;&gt;&lt;pre&gt;&lt;span class=&#34;line&#34;&gt;git reflog&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/figure&gt;
&lt;p&gt;此时你会找到你 drop 的 commit，把对应的 &lt;code&gt;commit hash&lt;/code&gt; 复制下来，然后执行：&lt;/p&gt;
&lt;figure class=&#34;highlight bash&#34;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&#34;gutter&#34;&gt;&lt;pre&gt;&lt;span class=&#34;line&#34;&gt;1&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&#34;code&#34;&gt;&lt;pre&gt;&lt;span class=&#34;line&#34;&gt;git reset --hard &amp;lt;commit_hash&amp;gt;&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/figure&gt;
&lt;p&gt;即可&lt;/p&gt;
&lt;h2 id=&#34;提交了的-commit-如何如何撤回&#34;&gt;&lt;a class=&#34;markdownIt-Anchor&#34; href=&#34;#提交了的-commit-如何如何撤回&#34;&gt;&lt;/a&gt; 提交了的 commit 如何如何撤回？&lt;/h2&gt;
&lt;p&gt;如果想保留提交的修改：&lt;/p&gt;
&lt;figure class=&#34;highlight bash&#34;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&#34;gutter&#34;&gt;&lt;pre&gt;&lt;span class=&#34;line&#34;&gt;1&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&#34;code&#34;&gt;&lt;pre&gt;&lt;span class=&#34;line&#34;&gt;git reset --soft HEAD^&amp;#123;n&amp;#125;&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/figure&gt;
&lt;p&gt;其中 &lt;code&gt;n&lt;/code&gt; 为你想要撤回的 commit 数量。前一次就是 &lt;code&gt;1&lt;/code&gt;。&lt;/p&gt;
&lt;p&gt;如果不想保留：&lt;/p&gt;
&lt;figure class=&#34;highlight bash&#34;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&#34;gutter&#34;&gt;&lt;pre&gt;&lt;span class=&#34;line&#34;&gt;1&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&#34;code&#34;&gt;&lt;pre&gt;&lt;span class=&#34;line&#34;&gt;git reset --hard HEAD^&amp;#123;n&amp;#125;&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/figure&gt;
&lt;h2 id=&#34;从其他分支上取一个-commit-到当前分支&#34;&gt;&lt;a class=&#34;markdownIt-Anchor&#34; href=&#34;#从其他分支上取一个-commit-到当前分支&#34;&gt;&lt;/a&gt; 从其他分支上取一个 Commit 到当前分支&lt;/h2&gt;
&lt;figure class=&#34;highlight bash&#34;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&#34;gutter&#34;&gt;&lt;pre&gt;&lt;span class=&#34;line&#34;&gt;1&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&#34;code&#34;&gt;&lt;pre&gt;&lt;span class=&#34;line&#34;&gt;git cherry-pick &amp;lt;commit_hash&amp;gt;&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/figure&gt;
&lt;h2 id=&#34;某个开发者交的-pr-的-commit-很多并且很乱需要将多个-commit-合并为一个&#34;&gt;&lt;a class=&#34;markdownIt-Anchor&#34; href=&#34;#某个开发者交的-pr-的-commit-很多并且很乱需要将多个-commit-合并为一个&#34;&gt;&lt;/a&gt; 某个开发者交的 PR 的 Commit 很多并且很乱，需要将多个 Commit 合并为一个&lt;/h2&gt;
&lt;figure class=&#34;highlight bash&#34;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&#34;gutter&#34;&gt;&lt;pre&gt;&lt;span class=&#34;line&#34;&gt;1&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&#34;code&#34;&gt;&lt;pre&gt;&lt;span class=&#34;line&#34;&gt;git rebase -i HEAD~n&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/figure&gt;
&lt;p&gt;其中 &lt;code&gt;n&lt;/code&gt; 为你想要合并的 commit 数量。前一次就是 &lt;code&gt;1&lt;/code&gt;。&lt;br /&gt;
在打开的编辑器中，将你想要合并的 commit 的 &lt;code&gt;pick&lt;/code&gt; 改为 &lt;code&gt;squash&lt;/code&gt;(或者 &lt;code&gt;s&lt;/code&gt;)，然后保存退出即可。&lt;/p&gt;
&lt;p&gt;保存后，会打开一个新的编辑器，显示你要合并的 commit 的信息。你可以选择保留所有的 commit 信息，或者只保留第一个 commit 的信息，或者完全自定义 commit 信息。&lt;/p&gt;
&lt;h2 id=&#34;有多个-author-在一个-commit&#34;&gt;&lt;a class=&#34;markdownIt-Anchor&#34; href=&#34;#有多个-author-在一个-commit&#34;&gt;&lt;/a&gt; 有多个 author 在一个 Commit&lt;/h2&gt;
&lt;figure class=&#34;highlight bash&#34;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&#34;gutter&#34;&gt;&lt;pre&gt;&lt;span class=&#34;line&#34;&gt;1&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;2&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;3&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&#34;code&#34;&gt;&lt;pre&gt;&lt;span class=&#34;line&#34;&gt;git commit -m &lt;span class=&#34;string&#34;&gt;&amp;quot;xxxxx&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;string&#34;&gt;&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;string&#34;&gt;Co-authored-by: &amp;lt;name&amp;gt; &amp;lt;email&amp;gt;&amp;quot;&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/figure&gt;
&lt;p&gt;可参考: &lt;a href=&#34;https://docs.github.com/en/pull-requests/committing-changes-to-your-project/creating-and-editing-commits/creating-a-commit-with-multiple-authors&#34;&gt;GitHub - Creating a commit with multiple authors&lt;/a&gt;&lt;/p&gt;
</content>
        <category term="2023" />
        <updated>2023-09-18T19:02:00.000Z</updated>
    </entry>
    <entry>
        <id>https://blog.soulter.top/posts/ceph.html</id>
        <title>Ceph架构、原理和集群搭建</title>
        <link rel="alternate" href="https://blog.soulter.top/posts/ceph.html"/>
        <content type="html">&lt;h1 id=&#34;ceph-介绍&#34;&gt;&lt;a class=&#34;markdownIt-Anchor&#34; href=&#34;#ceph-介绍&#34;&gt;&lt;/a&gt; Ceph 介绍&lt;/h1&gt;
&lt;h2 id=&#34;架构&#34;&gt;&lt;a class=&#34;markdownIt-Anchor&#34; href=&#34;#架构&#34;&gt;&lt;/a&gt; 架构&lt;/h2&gt;
&lt;p&gt;当有大量的小文件时，基于有中心架构的 HDFS 的 Name Node 会有很大的压力。&lt;/p&gt;
&lt;p&gt;Ceph 是无中心架构的典型，取消了 HDFS 那样的集中式元数据存储。客户端通过一套算法（Crush）在本地计算出写入数据的存储位置，直接与存储节点（数据节点）交互。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;仅 Ceph 块设备（RBD）和对象存储（RGW）没有元数据中心节点，文件存储（FS）还是有的，使用 MDS 服务集中存储元数据。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;img src=&#34;https://cf.s3.soulter.top/ceph-host/20231027081942998.png&#34; alt=&#34;&#34; /&gt;&lt;/p&gt;
&lt;p&gt;Ceph 中的核心组件包括：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Mon&lt;/strong&gt;(itor)：维护 Monitor Map、OSD Map、PG Map、CRUSH Map 等各种维护存储集群状态的图表。（这些图表保存着其各自的每一次状态变更，称为 Epoch）&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;OSD&lt;/strong&gt;：Object Storage Device。存储数据、管理磁盘、读写数据。OSD 服务处理数据的复制、恢复（Recovery）、回填（Backfilling）、再均衡（Rebalance）等任务。还会检测其他 OSD 的状态并打包上报给 &lt;strong&gt;Mon。&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;MDS&lt;/strong&gt;：Metadata Server。负责 CephFS 集群中文件和目录的管理，记录数据的属性，如文件存储位置、大小、存储时间等，同时负责文件查找、文件记录、存储位置记录、访问授权。（有主备机制，主 MDS 故障之后，其他 Standby 的 MDS 会顶上）&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;RADOS&lt;/strong&gt;：包含 &lt;strong&gt;Mon、OSD、MDS&lt;/strong&gt;。本质为一套分布式数据存储系统。Ceph存储中所有的数据都以对象形式存在，RADOS 负责保存这些对象。RADOS 层可以确保对象数据始终保持一致性。Ceph 其实是对 RADOS 的二次封装。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;RBD&lt;/strong&gt;：提供可靠的分布式、高性能块存储逻辑卷（Volume）给客户端使用。写入 RBD 设备的数据以&lt;strong&gt;条带化&lt;/strong&gt;的方式存储在 Ceph 集群的&lt;strong&gt;多个 OSD&lt;/strong&gt; 中。&lt;/li&gt;
&lt;/ol&gt;
&lt;blockquote&gt;
&lt;p&gt;什么是条带化？&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://cf.s3.soulter.top/ceph-host/20231027081959947.png&#34; alt=&#34;&#34; /&gt;&lt;/p&gt;
&lt;p&gt;基本思想是：以轮转方式将磁盘阵列的块分布在磁盘上。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;RGW&lt;/strong&gt;：RADOS Gateway。提供对象存储服务。支持 Amazon S3 的 API 调用方式。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;CephFS&lt;/strong&gt;：提供与 POSIX 兼容的文件系统。&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id=&#34;寻址方式&#34;&gt;&lt;a class=&#34;markdownIt-Anchor&#34; href=&#34;#寻址方式&#34;&gt;&lt;/a&gt; 寻址方式&lt;/h2&gt;
&lt;p&gt;&lt;img src=&#34;https://cf.s3.soulter.top/ceph-host/20231027082010489.png&#34; alt=&#34;&#34; /&gt;&lt;/p&gt;
&lt;p&gt;假设要存一个 1GiB 的文件。&lt;/p&gt;
&lt;p&gt;Ceph 客户端持有一个 Cluster Map（初始化时就会向 Moniter 服务获取最新的 Map，然后采用反向订阅机制，仅在 Cluster Map 变化时，Mon 会&lt;strong&gt;主动推送&lt;/strong&gt;）。&lt;/p&gt;
&lt;p&gt;只要根据这个 Map 和文件的一些信息（如文件名和文件大小）就能得到这个文件的每个 Object 所在的 OSD 的 ID，然后直接与其通信。&lt;/p&gt;
&lt;p&gt;一个文件对应一个唯一的 ino 。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;这个文件首先在 Ceph 中被切割成多个 Object，用 ino+ono 标识。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;然后通过对这个标识 hash 来分到不同的 PG 中，得到 PGID（每个 Object 分配一个 PG。一般来说，这样计算之后得到的 PGID 在大规模数据量看来会是均匀分布的）。&lt;/p&gt;
&lt;p&gt;PG是一个为方便管理 OSD（对象存储设备） 而设置的一个抽象概念，一个PG中有多个 OSD，一个 OSD 也会承载多个PG。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;得到 PGID 后，用 CRUSH 算法带入 PGID 得到多个 OSD 存入。&lt;/strong&gt;&lt;/p&gt;
&lt;h2 id=&#34;pg-pgp-和-osd&#34;&gt;&lt;a class=&#34;markdownIt-Anchor&#34; href=&#34;#pg-pgp-和-osd&#34;&gt;&lt;/a&gt; PG、PGP 和 OSD&lt;/h2&gt;
&lt;p&gt;由 PG 映射到数据存储的实际单元 OSD 中，该映射是由 CRUSH 算法来确定的。&lt;/p&gt;
&lt;p&gt;使用 CRUSH 算法相对于使用 Hash 算法的好处：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;CRUSH 具有可配置特性，可根据&lt;strong&gt;配置参数&lt;/strong&gt;决定OSD的物理位置映射策略；&lt;/li&gt;
&lt;li&gt;CRUSH具有特殊的“稳定性”，当系统中加入新的 OSD 导致系统规模增大时，大部分 PG 与 OSD 之间的映射关系不会发生改变，&lt;strong&gt;只是少部分&lt;/strong&gt; PG 的映射关系会发生变化并引发数据迁移（Straw 和 Straw2）&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;PG 是用来存放 Object 的，PGP 相当于是 PG 存放 OSD 的一种排列组合。一般来说应该将PG和PGP的数量设置为相等。&lt;/p&gt;
&lt;figure class=&#34;highlight plaintext&#34;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&#34;gutter&#34;&gt;&lt;pre&gt;&lt;span class=&#34;line&#34;&gt;1&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;2&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;3&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;4&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;5&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;6&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;7&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;8&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;9&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;10&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;11&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;12&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;13&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;14&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;15&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;16&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;17&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;18&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;19&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;20&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;21&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;22&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;23&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;24&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;25&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;26&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;27&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;28&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;29&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;30&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;31&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;32&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;33&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;34&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;35&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&#34;code&#34;&gt;&lt;pre&gt;&lt;span class=&#34;line&#34;&gt;&amp;gt; ceph osd pool create testpool 6 6&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;1.1 75 [3,6,0]&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;1.0 83 [7,0,6]&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;1.3 144 [4,1,2]&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;1.2 146 [7,4,1]&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;1.5 86 [4,6,3]&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;1.4 80 [3,0,4]&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;&amp;gt; ceph osd pool set testpool pg_num 12&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;1.1 37 [3,6,0]&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;1.9 38 [3,6,0]&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;1.0 41 [7,0,6]&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;1.8 42 [7,0,6]&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;1.3 48 [4,1,2]&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;1.b 48 [4,1,2]&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;1.7 48 [4,1,2]&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;1.2 48 [7,4,1]&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;1.6 49 [7,4,1]&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;1.a 49 [7,4,1]&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;1.5 86 [4,6,3]&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;1.4 80 [3,0,4]&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;&amp;gt; ceph osd pool set testpool pgp_num 12&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;1.a 49 [1,2,6]&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;1.b 48 [1,6,2]&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;1.1 37 [3,6,0]&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;1.0 41 [7,0,6]&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;1.3 48 [4,1,2]&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;1.2 48 [7,4,1]&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;1.5 86 [4,6,3]&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;1.4 80 [3,0,4]&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;1.7 48 [1,6,0]&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;1.6 49 [3,6,7]&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;1.9 38 [1,4,2]&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;1.8 42 [1,2,3]&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/figure&gt;
&lt;p&gt;可以看到:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;PG 是指定一个 Pool 中存储对象的”目录”有多少个, 而 PGP 指定 OSD 的排列组合有多少组&lt;/li&gt;
&lt;li&gt;⚠ PG 的增加会引起 PG 内维护对象的分裂, &lt;strong&gt;不会触发 Rebalance&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;⚠ PGP 的增加可能会改变一个 PG 所映射的 OSD 的组合，&lt;strong&gt;会导致 Rebalance&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id=&#34;pg-与-pool&#34;&gt;&lt;a class=&#34;markdownIt-Anchor&#34; href=&#34;#pg-与-pool&#34;&gt;&lt;/a&gt; PG 与 Pool&lt;/h2&gt;
&lt;p&gt;&lt;img src=&#34;https://cf.s3.soulter.top/ceph-host/20231027082037126.png&#34; alt=&#34;&#34; /&gt;&lt;/p&gt;
&lt;h2 id=&#34;crush&#34;&gt;&lt;a class=&#34;markdownIt-Anchor&#34; href=&#34;#crush&#34;&gt;&lt;/a&gt; CRUSH&lt;/h2&gt;
&lt;p&gt;CRUSH有两个关键参数：Cluster Map 和 Placement Rules。&lt;/p&gt;
&lt;h3 id=&#34;cluster-map&#34;&gt;&lt;a class=&#34;markdownIt-Anchor&#34; href=&#34;#cluster-map&#34;&gt;&lt;/a&gt; Cluster Map&lt;/h3&gt;
&lt;p&gt;反映整个 Ceph 存储系统层级（共 11 个层级）的&lt;strong&gt;物理拓扑结构&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;包含OSD守护进程的层级信息。&lt;/p&gt;
&lt;p&gt;由 Device（OSD）和 Bucket（存 OSD 的容器，可以是很多东西，如 Host，Rack 机架）这两个基本元素形成一整个结构体系。&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://cf.s3.soulter.top/ceph-host/20231027082048898.png&#34; alt=&#34;&#34; /&gt;&lt;/p&gt;
&lt;p&gt;Device 有权重概念。越高的权重在选 Device 时就会更多地选到这个 Device 上。Bucket 也有权重概念，其最终权重是它的权重和它所包含的 Device 权重的总和。&lt;br /&gt;
&lt;img src=&#34;https://cf.s3.soulter.top/ceph-host/20231027082058800.png&#34; alt=&#34;&#34; /&gt;&lt;/p&gt;
&lt;h3 id=&#34;placement-rules&#34;&gt;&lt;a class=&#34;markdownIt-Anchor&#34; href=&#34;#placement-rules&#34;&gt;&lt;/a&gt; Placement Rules&lt;/h3&gt;
&lt;p&gt;决定了一个 PG 如何选择 OSD。通过自定义 Placement Rules，用户可以设置副本在集群中的分布&lt;/p&gt;
&lt;p&gt;定义类似：&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://cf.s3.soulter.top/ceph-host/20231027082112327.png&#34; alt=&#34;&#34; /&gt;&lt;/p&gt;
&lt;h3 id=&#34;pg-选出-osd&#34;&gt;&lt;a class=&#34;markdownIt-Anchor&#34; href=&#34;#pg-选出-osd&#34;&gt;&lt;/a&gt; PG 选出 OSD&lt;/h3&gt;
&lt;p&gt;四种 CRUSH 算法：&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://cf.s3.soulter.top/ceph-host/20231027082123956.png&#34; alt=&#34;&#34; /&gt;&lt;br /&gt;
常用（默认）Straw2。&lt;/p&gt;
&lt;p&gt;Straw 算法过程：&lt;/p&gt;
&lt;figure class=&#34;highlight bash&#34;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&#34;gutter&#34;&gt;&lt;pre&gt;&lt;span class=&#34;line&#34;&gt;1&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;2&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;3&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;4&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;5&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;6&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;7&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;8&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;9&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&#34;code&#34;&gt;&lt;pre&gt;&lt;span class=&#34;line&#34;&gt;max_x = -1&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;max_item = -1&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;keyword&#34;&gt;for&lt;/span&gt; each item:&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;    x = random value from 0..65535&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;    x *= scaling &lt;span class=&#34;built_in&#34;&gt;factor&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;    &lt;span class=&#34;keyword&#34;&gt;if&lt;/span&gt; x &amp;gt; max_x:&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;       max_x = x&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;       max_item = item&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;built_in&#34;&gt;return&lt;/span&gt; item&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/figure&gt;
&lt;p&gt;（1）给出一个 PG_ID，作为 CRUSH_HASH 方法的输入；&lt;br /&gt;
（2）CRUSH_HASH（PG_ID，OSD_ID，r）方法调用之后，得出一个随机数；&lt;br /&gt;
（3）对于所有的 OSD，用它们的权重乘以每个 OSD_ID 对应的随机数，得到乘积；&lt;br /&gt;
（4）选出乘积最大的 OSD ；&lt;br /&gt;
（5）这个 PG 就会保存到这个 OSD 上。&lt;/p&gt;
&lt;p&gt;Straw2 算法过程：&lt;/p&gt;
&lt;figure class=&#34;highlight bash&#34;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&#34;gutter&#34;&gt;&lt;pre&gt;&lt;span class=&#34;line&#34;&gt;1&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;2&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;3&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;4&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;5&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;6&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;7&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;8&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;9&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&#34;code&#34;&gt;&lt;pre&gt;&lt;span class=&#34;line&#34;&gt;max_x = -1&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;max_item = -1&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;keyword&#34;&gt;for&lt;/span&gt; each item:&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;	  x = random value from 0..65535&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;	  x = &lt;span class=&#34;built_in&#34;&gt;ln&lt;/span&gt;(x / 65536) / weight&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;	  &lt;span class=&#34;keyword&#34;&gt;if&lt;/span&gt; x &amp;gt; max_x:&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;	     max_x = x&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;	     max_item = item&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;built_in&#34;&gt;return&lt;/span&gt; item&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/figure&gt;
&lt;p&gt;Straw 算法里面添加节点或者减少节点，其他服务器上的 OSD 之间会有PG 的流动（即数据的迁移）；Straw2 算法里面添加节点或者减少节点，只会有 PG 从变化的节点移出或者从其他点移入，其他不相干节点不会触发数据的迁移。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Ceph 的 Luminous 版本开始默认支持 Straw2 算法。&lt;/strong&gt;&lt;/p&gt;
&lt;h2 id=&#34;pg-状态机&#34;&gt;&lt;a class=&#34;markdownIt-Anchor&#34; href=&#34;#pg-状态机&#34;&gt;&lt;/a&gt; PG 状态机&lt;/h2&gt;
&lt;p&gt;下面列出常见的 PG 状态。&lt;/p&gt;
&lt;h3 id=&#34;peering&#34;&gt;&lt;a class=&#34;markdownIt-Anchor&#34; href=&#34;#peering&#34;&gt;&lt;/a&gt; Peering&lt;/h3&gt;
&lt;p&gt;等待 PG 包含的冗余组中所有对象达到一致性。I/O 阻塞。&lt;/p&gt;
&lt;h3 id=&#34;peered&#34;&gt;&lt;a class=&#34;markdownIt-Anchor&#34; href=&#34;#peered&#34;&gt;&lt;/a&gt; Peered&lt;/h3&gt;
&lt;p&gt;等待其他副本（OSD 守护进程）上线。I/O 阻塞。&lt;/p&gt;
&lt;h3 id=&#34;degraded&#34;&gt;&lt;a class=&#34;markdownIt-Anchor&#34; href=&#34;#degraded&#34;&gt;&lt;/a&gt; Degraded&lt;/h3&gt;
&lt;p&gt;PG 副本数 &amp;lt; 3&lt;/p&gt;
&lt;h3 id=&#34;recovery&#34;&gt;&lt;a class=&#34;markdownIt-Anchor&#34; href=&#34;#recovery&#34;&gt;&lt;/a&gt; Recovery&lt;/h3&gt;
&lt;p&gt;Recovery 指对应副本能够通过日志（PGLog1）进行恢复，即只需要修复该副本上与权威日志不同步的那部分对象，即可完成存储系统内数据的整体恢复。&lt;/p&gt;
&lt;p&gt;Recovery 有两种恢复方式：Pull（Primary 自身选择合适的副本拉取降级对象的权威日志），Push（主动）Primary 节点会先 Pull, 然后再 Push。&lt;/p&gt;
&lt;p&gt;客户端读请求，待访问的对象在一个或者多个副本上处于降级状态，对应的读请求可以直接在 Primary 上完成，对象仅仅在副本上降级，无任何影响。如果 Primary 上也处于降级状态，需要等 Primary 完成修复，才能继续。&lt;/p&gt;
&lt;p&gt;客户端写请求，待访问的对象在一个或者多个副本上处于降级状态，必须修复该对象上所有的降级副本之后才能继续处理写请求。最坏情况，需要先修复 Primary，再由 Primary 修复其他降级副本。&lt;/p&gt;
&lt;h2 id=&#34;rbd&#34;&gt;&lt;a class=&#34;markdownIt-Anchor&#34; href=&#34;#rbd&#34;&gt;&lt;/a&gt; RBD&lt;/h2&gt;
&lt;h3 id=&#34;映射&#34;&gt;&lt;a class=&#34;markdownIt-Anchor&#34; href=&#34;#映射&#34;&gt;&lt;/a&gt; 映射&lt;/h3&gt;
&lt;p&gt;通过 librbd、KRBD 等访问。一般选用前者。后者是 Kernel RBD，Linux 内部支持的，运行在操作系统内核态，需要部署在客户端节点的操作系统内核中。&lt;/p&gt;
&lt;p&gt;librbd 按照使用方式又可以分为 QEMU+librbd、SPDK+librbd 和 NBD+librbd。&lt;/p&gt;
&lt;h3 id=&#34;快照和克隆&#34;&gt;&lt;a class=&#34;markdownIt-Anchor&#34; href=&#34;#快照和克隆&#34;&gt;&lt;/a&gt; 快照和克隆&lt;/h3&gt;
&lt;p&gt;快照只读，克隆可读写，Ceph RBD 设备的快照和克隆操作存在相关性，即克隆操作一定要基于某一已创建的快照进行。&lt;/p&gt;
&lt;p&gt;Ceph RBD 的快照和克隆均采用 COW。这样也会有这个问题：当一个 RBD 上有较多层级的克隆卷时，对克隆卷进行读写时，可能会涉及较多层级的递归查询操作，会对克隆卷的性能产生不小的影响。（可解除克隆卷与原卷的依赖关系）&lt;/p&gt;
&lt;p&gt;下面的图中，场景是新数据写入到已经有快照的&lt;strong&gt;源卷的第 6 块上。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://cf.s3.soulter.top/ceph-host/20231027082146131.png&#34; alt=&#34;&#34; /&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;这种方式很好地节省了空间，但这也会造成写放大，因此创建多个快照之后，对 I/O 性能的劣化效果会越来越明显。因为在为快照向新的物理空间复制出一份数据之后，还要为所有已创建的快照修改数据块的地址指针。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;附 ROW 的机理图：&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://cf.s3.soulter.top/ceph-host/20231027082159794.png&#34; alt=&#34;&#34; /&gt;&lt;/p&gt;
&lt;p&gt;显然，ROW 不会造成写放大，因为新数据直接写到新块（7）上（假设新数据 I/O 落在原卷第 6 个块上）。但是这样源卷的存储物理空间发生了变化（原来的 6 现在指向 7）。&lt;/p&gt;
&lt;p&gt;会劣化源卷的顺序读写。&lt;/p&gt;
&lt;h3 id=&#34;rbd-cache&#34;&gt;&lt;a class=&#34;markdownIt-Anchor&#34; href=&#34;#rbd-cache&#34;&gt;&lt;/a&gt; RBD Cache&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;提供读缓存、写合并&lt;/li&gt;
&lt;li&gt;存在内存中，有一定的可自定义的策略定时 Write Back。&lt;/li&gt;
&lt;li&gt;支持 RWL，能大幅度提高IOPS，并且容灾性能++（需要特定硬件）&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&#34;qos&#34;&gt;&lt;a class=&#34;markdownIt-Anchor&#34; href=&#34;#qos&#34;&gt;&lt;/a&gt; QoS&lt;/h3&gt;
&lt;p&gt;提供针对不同用户或不同数据流采用不同优先级的 I/O 读写能力服务策略。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;mClock&lt;/li&gt;
&lt;li&gt;dmClock&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&#34;burst-io&#34;&gt;&lt;a class=&#34;markdownIt-Anchor&#34; href=&#34;#burst-io&#34;&gt;&lt;/a&gt; Burst I/O&lt;/h3&gt;
&lt;p&gt;Ceph RBD 设备突发能力的实现基于令牌桶。&lt;/p&gt;
&lt;h1 id=&#34;ceph测试集群搭建&#34;&gt;&lt;a class=&#34;markdownIt-Anchor&#34; href=&#34;#ceph测试集群搭建&#34;&gt;&lt;/a&gt; Ceph测试集群搭建&lt;/h1&gt;
&lt;p&gt;3个虚拟机IP：&lt;/p&gt;
&lt;figure class=&#34;highlight cpp&#34;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&#34;gutter&#34;&gt;&lt;pre&gt;&lt;span class=&#34;line&#34;&gt;1&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&#34;code&#34;&gt;&lt;pre&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;number&#34;&gt;10.2&lt;/span&gt;&lt;span class=&#34;number&#34;&gt;.217&lt;/span&gt;&lt;span class=&#34;number&#34;&gt;.231&lt;/span&gt;(master)，&lt;span class=&#34;number&#34;&gt;10.2&lt;/span&gt;&lt;span class=&#34;number&#34;&gt;.217&lt;/span&gt;&lt;span class=&#34;number&#34;&gt;.214&lt;/span&gt;，&lt;span class=&#34;number&#34;&gt;10.2&lt;/span&gt;&lt;span class=&#34;number&#34;&gt;.217&lt;/span&gt;&lt;span class=&#34;number&#34;&gt;.204&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/figure&gt;
&lt;p&gt;使用cephadm工具进行搭建。&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;修改主机名&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;figure class=&#34;highlight bash&#34;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&#34;gutter&#34;&gt;&lt;pre&gt;&lt;span class=&#34;line&#34;&gt;1&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&#34;code&#34;&gt;&lt;pre&gt;&lt;span class=&#34;line&#34;&gt;hostnamectl set-hostname cephtest1 &lt;span class=&#34;comment&#34;&gt;#其他2个同理&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/figure&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;修改/etc/hosts文件（3个都要改）&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;figure class=&#34;highlight plaintext&#34;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&#34;gutter&#34;&gt;&lt;pre&gt;&lt;span class=&#34;line&#34;&gt;1&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;2&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;3&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;4&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;5&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&#34;code&#34;&gt;&lt;pre&gt;&lt;span class=&#34;line&#34;&gt;...&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;10.2.217.204 cephtest1&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;10.2.217.231 cephtest2&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;10.2.217.214 cephtest3&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/figure&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;（可选）如果在你的公司内网部署，需配置yum repo源&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;figure class=&#34;highlight bash&#34;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&#34;gutter&#34;&gt;&lt;pre&gt;&lt;span class=&#34;line&#34;&gt;1&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;2&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;3&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;4&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;5&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&#34;code&#34;&gt;&lt;pre&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;comment&#34;&gt;#对于Redhat8，是这个文件，如果是CentOS，则是xxxCentOS.repo&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;vim /etc/yum.repos.d/8ASU7_Red.repo &lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;comment&#34;&gt;# 直接append就行。需要redhat87、docker、epel的源&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;comment&#34;&gt;# 加完后记得makecache：&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;yum makecache &lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/figure&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;关闭防火墙、开启时间同步&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;figure class=&#34;highlight bash&#34;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&#34;gutter&#34;&gt;&lt;pre&gt;&lt;span class=&#34;line&#34;&gt;1&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;2&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;3&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;4&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;5&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&#34;code&#34;&gt;&lt;pre&gt;&lt;span class=&#34;line&#34;&gt;systemctl &lt;span class=&#34;built_in&#34;&gt;disable&lt;/span&gt; --now firewalld&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;setenforce 0&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;sed -i &lt;span class=&#34;string&#34;&gt;&amp;#x27;s/^SELINUX=.*/SELINUX=disabled/&amp;#x27;&lt;/span&gt; /etc/selinux/config&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;yum install -y chrony&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;systemctl &lt;span class=&#34;built_in&#34;&gt;enable&lt;/span&gt; --now chronyd&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/figure&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;安装lvm2、python3、docker-ce&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;figure class=&#34;highlight bash&#34;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&#34;gutter&#34;&gt;&lt;pre&gt;&lt;span class=&#34;line&#34;&gt;1&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;2&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;3&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;4&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;5&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&#34;code&#34;&gt;&lt;pre&gt;&lt;span class=&#34;line&#34;&gt;yum install -y lvm2&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;yum install -y python3&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;comment&#34;&gt;# 配置软链接&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;built_in&#34;&gt;ln&lt;/span&gt; -s /usr/bin/python3 /usr/bin/python&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;yum install -y docker-ce&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/figure&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;cephadm安装(从这一步开始，只需要其中一台机器作为master来执行)&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;figure class=&#34;highlight bash&#34;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&#34;gutter&#34;&gt;&lt;pre&gt;&lt;span class=&#34;line&#34;&gt;1&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;2&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;3&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&#34;code&#34;&gt;&lt;pre&gt;&lt;span class=&#34;line&#34;&gt;curl https://raw.githubusercontent.com/ceph/ceph/v15.2.1/src/cephadm/cephadm -o cephadm&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;built_in&#34;&gt;chmod&lt;/span&gt; 777 cephadm &amp;amp;&amp;amp; ./cephadm add-repo --release octopus&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;./cephadm install&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/figure&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;cephadm加载&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;figure class=&#34;highlight bash&#34;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&#34;gutter&#34;&gt;&lt;pre&gt;&lt;span class=&#34;line&#34;&gt;1&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;2&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&#34;code&#34;&gt;&lt;pre&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;built_in&#34;&gt;mkdir&lt;/span&gt; -p /etc/ceph&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;cephadm bootstrap --mon-ip 10.2.217.231&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/figure&gt;
&lt;p&gt;稍等，就会出现如下信息：&lt;/p&gt;
&lt;figure class=&#34;highlight bash&#34;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&#34;gutter&#34;&gt;&lt;pre&gt;&lt;span class=&#34;line&#34;&gt;1&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;2&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;3&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;4&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;5&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;6&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;7&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;8&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;9&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;10&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;11&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;12&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;13&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;14&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;15&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;16&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;17&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&#34;code&#34;&gt;&lt;pre&gt;&lt;span class=&#34;line&#34;&gt;Ceph Dashboard is now available at:&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;             URL: https://cephtest1:8443/&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;            User: admin&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;        Password: f38lpz1yzj&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;You can access the Ceph CLI with:&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;        &lt;span class=&#34;built_in&#34;&gt;sudo&lt;/span&gt; /usr/sbin/cephadm shell --fsid e8aad788-418a-11ee-b97d-fa163e71b85a -c /etc/ceph/ceph.conf -k /etc/ceph/ceph.client.admin.keyring&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;Please consider enabling telemetry to &lt;span class=&#34;built_in&#34;&gt;help&lt;/span&gt; improve Ceph:&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;        ceph telemetry on&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;For more information see:&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;        https://docs.ceph.com/docs/master/mgr/telemetry/&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/figure&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;安装ceph-common&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;figure class=&#34;highlight bash&#34;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&#34;gutter&#34;&gt;&lt;pre&gt;&lt;span class=&#34;line&#34;&gt;1&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&#34;code&#34;&gt;&lt;pre&gt;&lt;span class=&#34;line&#34;&gt;cephadm install ceph-common&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/figure&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;加入其他机器到集群&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;figure class=&#34;highlight bash&#34;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&#34;gutter&#34;&gt;&lt;pre&gt;&lt;span class=&#34;line&#34;&gt;1&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;2&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;3&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;4&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;5&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;6&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;7&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;8&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;9&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;10&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;11&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&#34;code&#34;&gt;&lt;pre&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;comment&#34;&gt;# 其他机器重复这些操作&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;ssh-copy-id -f -i /etc/ceph/ceph.pub root@cephtest2&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;ceph orch host add cephtest2 10.2.217.214&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;comment&#34;&gt;# 加完之后输入ceph orch host ls，就可以看到刚刚加的机器。&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;[@cephtest1 ~]# ceph orch host &lt;span class=&#34;built_in&#34;&gt;ls&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;HOST       ADDR          LABELS  STATUS&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;cephtest1  cephtest1&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;cephtest2  10.2.217.231&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;cephtest3  10.2.217.214&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/figure&gt;
&lt;p&gt;输入&lt;code&gt;ceph orch ps&lt;/code&gt;可以看到运行着的容器。如果有error，说明部署操作有问题。&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://cf.s3.soulter.top/ceph-host/Untitled.png&#34; alt=&#34;Untitled&#34; /&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;NOTE：&lt;strong&gt;ceph所有服务都是在&lt;/strong&gt;docker&lt;/strong&gt;内运行的，有monitor、crash、osd、rgw等服务（所以很吃内存）。结合近期踩过的坑，可以了解到启动的脚本其实是放在/var/lib/ceph/xxxx[集群id]/xxx[服务名称]/unit.run下的。&lt;strong&gt;master&lt;/strong&gt;节点先通过ssh装载ceph相关服务，然后远程调用这个脚本来在这个节点上启动docker。&lt;/p&gt;
&lt;p&gt;&lt;em&gt;[@cephtest3 ~]# cd /var/lib/ceph/e8aad788-418a-11ee-b97d-fa163e71b85a/mon.cephtest3/&lt;br /&gt;
config           kv_backend       store.db/        unit.created     unit.poststop&lt;br /&gt;
keyring          min_mon_release  unit.configured  unit.image       unit.run&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;**NOTE2：&lt;strong&gt;如果见到哪个error了，直接在master上面通过诸如&lt;code&gt;**ceph orch daemon rm mon.cephtest2 —force&lt;/code&gt;的指令删除该daemon，然后再用类似的指令apply：ceph orch apply mon --placement=&amp;quot;cephtest1, cephtest2, cephtest3” （这里以重装monitor为例）&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;部署OSD&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;figure class=&#34;highlight bash&#34;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&#34;gutter&#34;&gt;&lt;pre&gt;&lt;span class=&#34;line&#34;&gt;1&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;2&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;3&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;4&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;5&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;6&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;7&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;8&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;9&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;10&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;11&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;12&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&#34;code&#34;&gt;&lt;pre&gt;&lt;span class=&#34;line&#34;&gt;ceph orch device &lt;span class=&#34;built_in&#34;&gt;ls&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;comment&#34;&gt;# 如果没发现任何输出，没有任何可用的磁盘。&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;comment&#34;&gt;# 需要满足：&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;comment&#34;&gt;# 设备没有分区&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;comment&#34;&gt;# 设备没有LVM状态&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;comment&#34;&gt;# 设备不含文件系统&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;comment&#34;&gt;# 设备不含Ceph BlueStore OSD&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;comment&#34;&gt;# 设备大于5G&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;comment&#34;&gt;# 不得安装设备&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;ceph orch apply osd --all-available-devices&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;ceph -s&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/figure&gt;
&lt;p&gt;如果发现所有设备均 avaliable no，可以使用&lt;/p&gt;
&lt;figure class=&#34;highlight plaintext&#34;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&#34;gutter&#34;&gt;&lt;pre&gt;&lt;span class=&#34;line&#34;&gt;1&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&#34;code&#34;&gt;&lt;pre&gt;&lt;span class=&#34;line&#34;&gt;ceph orch device ls --wide --refresh&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/figure&gt;
&lt;p&gt;来查看原因。&lt;/p&gt;
&lt;p&gt;osd: 6 osds: 6 up (since 29h), 6 in (since 29h)&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://cf.s3.soulter.top/ceph-host/Untitled1.png&#34; alt=&#34;Untitled&#34; /&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;部署MDS&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;figure class=&#34;highlight bash&#34;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&#34;gutter&#34;&gt;&lt;pre&gt;&lt;span class=&#34;line&#34;&gt;1&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;2&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;3&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;4&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;5&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&#34;code&#34;&gt;&lt;pre&gt;&lt;span class=&#34;line&#34;&gt;ceph osd pool create cephfs_data 64 64&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;ceph osd pool create cephfs_metadata 64 64&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;ceph fs new cephfs cephfs_metadata cephfs_data&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;ceph fs &lt;span class=&#34;built_in&#34;&gt;ls&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;ceph orch apply mds cephfs --placement=&lt;span class=&#34;string&#34;&gt;&amp;quot;cephtest1 cephtest2 cephtest3&amp;quot;&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/figure&gt;
&lt;p&gt;静待3分钟左右，然后输入docker ps | grep mds。&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://cf.s3.soulter.top/ceph-host/Untitled2.png&#34; alt=&#34;Untitled&#34; /&gt;&lt;/p&gt;
&lt;p&gt;这一步完成之后，我们就已经可以正常使用Ceph了。在用户侧，可以直接连接RADOS上传文件。以下是上传文件的代码：&lt;/p&gt;
&lt;figure class=&#34;highlight python&#34;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&#34;gutter&#34;&gt;&lt;pre&gt;&lt;span class=&#34;line&#34;&gt;1&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;2&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;3&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;4&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;5&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;6&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;7&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;8&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;9&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;10&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;11&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;12&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;13&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;14&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;15&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;16&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;17&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;18&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;19&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;20&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;21&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;22&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;23&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;24&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;25&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;26&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;27&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;28&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;29&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;30&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;31&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;32&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;33&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;34&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;35&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;36&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;37&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;38&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;39&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;40&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;41&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;42&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;43&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;44&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;45&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&#34;code&#34;&gt;&lt;pre&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;comment&#34;&gt;# 直连RADOS上传文件&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;keyword&#34;&gt;import&lt;/span&gt; rados&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;keyword&#34;&gt;import&lt;/span&gt; time&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;keyword&#34;&gt;import&lt;/span&gt; uuid&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;cluster = rados.Rados(conffile=&lt;span class=&#34;string&#34;&gt;&amp;#x27;/etc/ceph/ceph.conf&amp;#x27;&lt;/span&gt;)&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;cluster.connect()&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;ioctx = cluster.open_ioctx(&lt;span class=&#34;string&#34;&gt;&amp;#x27;cephfs_data&amp;#x27;&lt;/span&gt;)&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;comment&#34;&gt;# 写入文件到集群中&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;keyword&#34;&gt;def&lt;/span&gt; &lt;span class=&#34;title function_&#34;&gt;write_file&lt;/span&gt;(&lt;span class=&#34;params&#34;&gt;file_name&lt;/span&gt;):&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;    &lt;span class=&#34;comment&#34;&gt;# for i in range(1, 10):&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;    start = time.time()&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;    fn = file_name + &lt;span class=&#34;string&#34;&gt;&amp;quot;.&amp;quot;&lt;/span&gt; + &lt;span class=&#34;built_in&#34;&gt;str&lt;/span&gt;(uuid.uuid4())&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;    &lt;span class=&#34;built_in&#34;&gt;print&lt;/span&gt;(&lt;span class=&#34;string&#34;&gt;&amp;quot;Start writing &amp;quot;&lt;/span&gt; + fn)&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;    &lt;span class=&#34;keyword&#34;&gt;with&lt;/span&gt; &lt;span class=&#34;built_in&#34;&gt;open&lt;/span&gt;(file_name, &lt;span class=&#34;string&#34;&gt;&amp;#x27;rb&amp;#x27;&lt;/span&gt;) &lt;span class=&#34;keyword&#34;&gt;as&lt;/span&gt; f:&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;        data = f.read()&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;        ioctx.write_full(fn, data)&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;    end = time.time()&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;    &lt;span class=&#34;built_in&#34;&gt;print&lt;/span&gt;(fn + &lt;span class=&#34;string&#34;&gt;&amp;quot; is written. Time: &amp;quot;&lt;/span&gt; + &lt;span class=&#34;built_in&#34;&gt;str&lt;/span&gt;(end - start) + &lt;span class=&#34;string&#34;&gt;&amp;quot;s&amp;quot;&lt;/span&gt;)&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;    &lt;span class=&#34;built_in&#34;&gt;print&lt;/span&gt;(&lt;span class=&#34;string&#34;&gt;&amp;quot;Speed: &amp;quot;&lt;/span&gt; + &lt;span class=&#34;built_in&#34;&gt;str&lt;/span&gt;(&lt;span class=&#34;built_in&#34;&gt;len&lt;/span&gt;(data) / (end - start) / &lt;span class=&#34;number&#34;&gt;1024&lt;/span&gt; / &lt;span class=&#34;number&#34;&gt;1024&lt;/span&gt;) + &lt;span class=&#34;string&#34;&gt;&amp;quot;MB/s&amp;quot;&lt;/span&gt;)&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;comment&#34;&gt;# 并发测试：开10个线程，每个线程写一个文件&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;keyword&#34;&gt;import&lt;/span&gt; threading&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;threads = []&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;keyword&#34;&gt;for&lt;/span&gt; i &lt;span class=&#34;keyword&#34;&gt;in&lt;/span&gt; &lt;span class=&#34;built_in&#34;&gt;range&lt;/span&gt;(&lt;span class=&#34;number&#34;&gt;1&lt;/span&gt;, &lt;span class=&#34;number&#34;&gt;11&lt;/span&gt;):&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;    t = threading.Thread(target=write_file, args=(&lt;span class=&#34;string&#34;&gt;&amp;quot;OS.pdf&amp;quot;&lt;/span&gt;,))&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;    threads.append(t)&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;    t.start()&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;comment&#34;&gt;# 列出池中的所有文件名&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;comment&#34;&gt;# ioctx = cluster.open_ioctx(&amp;#x27;cephfs_data&amp;#x27;)&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;comment&#34;&gt;# objects = ioctx.list_objects()&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;comment&#34;&gt;# for obj in objects:&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;comment&#34;&gt;#     print(obj.key)&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;comment&#34;&gt;# ioctx.close()&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;comment&#34;&gt;# 读取文件&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;comment&#34;&gt;# ioctx = cluster.open_ioctx(&amp;#x27;cephfs_data&amp;#x27;)&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;comment&#34;&gt;# data = ioctx.read(&amp;quot;OS.pdf&amp;quot;)&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;comment&#34;&gt;# f_download = open(&amp;quot;OS_download.pdf&amp;quot;, &amp;quot;wb&amp;quot;)&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;comment&#34;&gt;# f_download.write(data)&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;comment&#34;&gt;# f_download.close()&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;comment&#34;&gt;# ioctx.close()&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/figure&gt;
&lt;p&gt;&lt;img src=&#34;https://cf.s3.soulter.top/ceph-host/Untitled3.png&#34; alt=&#34;Untitled&#34; /&gt;&lt;/p&gt;
&lt;p&gt;可以看到，osd存储量一直在上升，说明上传有效果。也可以通过指令查看一个pool存储的文件。&lt;/p&gt;
&lt;p&gt;不过直连RADOS是不太好的，测试发现，上传完毕之后&lt;strong&gt;并不会进行4MiB为单位的文件分片&lt;/strong&gt;。查询相关文档之后知道这样直连RADOS，分片阈值时128MiB。&lt;/p&gt;
&lt;p&gt;我们可以挂rbd(块存储)或者用s3(对象存储，需要部署对象网关RGWS并创建账号)来更好地使用ceph。&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://cf.s3.soulter.top/ceph-host/Untitled4.png&#34; alt=&#34;Untitled&#34; /&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;部署RGWS网关&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;如果你不需要使用S3对象存储，那么这一步可以先跳过。&lt;/p&gt;
&lt;figure class=&#34;highlight bash&#34;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&#34;gutter&#34;&gt;&lt;pre&gt;&lt;span class=&#34;line&#34;&gt;1&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;2&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;3&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;4&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&#34;code&#34;&gt;&lt;pre&gt;&lt;span class=&#34;line&#34;&gt;radosgw-admin realm create --rgw-realm=myorg --default&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;radosgw-admin zonegroup create --rgw-zonegroup=default --master --default&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;radosgw-admin zone create --rgw-zonegroup=default --rgw-zone=cn-east-1 --master --default&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;ceph orch apply rgw myorg cn-east-1 --placement=&lt;span class=&#34;string&#34;&gt;&amp;quot;cephtest1 cephtest2 cephtest3&amp;quot;&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/figure&gt;
&lt;p&gt;静待3分钟左右，然后输入docker ps | grep rgw。&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://cf.s3.soulter.top/ceph-host/Untitled5.png&#34; alt=&#34;Untitled&#34; /&gt;&lt;/p&gt;
&lt;p&gt;使用cephadm shell进入ceph，然后添加用户：&lt;/p&gt;
&lt;figure class=&#34;highlight bash&#34;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&#34;gutter&#34;&gt;&lt;pre&gt;&lt;span class=&#34;line&#34;&gt;1&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;2&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&#34;code&#34;&gt;&lt;pre&gt;&lt;span class=&#34;line&#34;&gt;radosgw-admin user create --uid=&lt;span class=&#34;string&#34;&gt;&amp;quot;ceph-rgw-testuser&amp;quot;&lt;/span&gt; --display-name=&lt;span class=&#34;string&#34;&gt;&amp;quot;Soulter&amp;quot;&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/figure&gt;
&lt;p&gt;&lt;img src=&#34;https://cf.s3.soulter.top/ceph-host/Untitled6.png&#34; alt=&#34;Untitled&#34; /&gt;&lt;/p&gt;
&lt;p&gt;可以看到，我们刚刚在&lt;strong&gt;对象网关&lt;/strong&gt;创建了一个新的用户。其实access_key和secret_key就已经类似于那些云厂商提供的key了。&lt;/p&gt;
&lt;p&gt;下面使用一下对象存储吧！&lt;/p&gt;
&lt;figure class=&#34;highlight bash&#34;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&#34;gutter&#34;&gt;&lt;pre&gt;&lt;span class=&#34;line&#34;&gt;1&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&#34;code&#34;&gt;&lt;pre&gt;&lt;span class=&#34;line&#34;&gt;pip install boto&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/figure&gt;
&lt;figure class=&#34;highlight python&#34;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&#34;gutter&#34;&gt;&lt;pre&gt;&lt;span class=&#34;line&#34;&gt;1&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;2&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;3&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;4&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;5&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;6&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;7&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;8&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;9&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;10&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;11&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;12&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;13&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;14&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;15&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;16&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&#34;code&#34;&gt;&lt;pre&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;keyword&#34;&gt;import&lt;/span&gt; boto&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;keyword&#34;&gt;import&lt;/span&gt; boto.s3.connection&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;access_key = &lt;span class=&#34;string&#34;&gt;&amp;#x27;V446LFHJPNG4Z0DH4MCR&amp;#x27;&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;secret_key = &lt;span class=&#34;string&#34;&gt;&amp;#x27;rdlvr8qDooUIRbQKJV9dtxhuTwuDByCXi3TPI1nr&amp;#x27;&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;conn = boto.connect_s3(&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;        aws_access_key_id = access_key,&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;        aws_secret_access_key = secret_key,&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;        host = &lt;span class=&#34;string&#34;&gt;&amp;#x27;cephtest1&amp;#x27;&lt;/span&gt;, port = &lt;span class=&#34;number&#34;&gt;80&lt;/span&gt;,&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;        is_secure=&lt;span class=&#34;literal&#34;&gt;False&lt;/span&gt;, calling_format = boto.s3.connection.OrdinaryCallingFormat(),&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;        )&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;comment&#34;&gt;# 创建Bucket&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;bucket = conn.create_bucket(&lt;span class=&#34;string&#34;&gt;&amp;#x27;ceph-s3-bucket&amp;#x27;&lt;/span&gt;)&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;keyword&#34;&gt;for&lt;/span&gt; bucket &lt;span class=&#34;keyword&#34;&gt;in&lt;/span&gt; conn.get_all_buckets():&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;        &lt;span class=&#34;built_in&#34;&gt;print&lt;/span&gt; (&lt;span class=&#34;string&#34;&gt;&amp;quot;&amp;#123;name&amp;#125;&amp;quot;&lt;/span&gt;.&lt;span class=&#34;built_in&#34;&gt;format&lt;/span&gt;(name = bucket.name))&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/figure&gt;
&lt;p&gt;&lt;img src=&#34;https://cf.s3.soulter.top/ceph-host/Untitled7.png&#34; alt=&#34;Untitled&#34; /&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;（到这里我们就可以开发出七牛云等云厂商的对象存储服务了）&lt;/strong&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;部署rbd&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;figure class=&#34;highlight python&#34;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&#34;gutter&#34;&gt;&lt;pre&gt;&lt;span class=&#34;line&#34;&gt;1&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;2&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;3&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;4&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;5&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;6&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;7&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;8&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;9&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;10&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;11&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;12&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;13&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;14&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&#34;code&#34;&gt;&lt;pre&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;comment&#34;&gt;# 创建pool&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;ceph osd pool create rbd &lt;span class=&#34;number&#34;&gt;32&lt;/span&gt; &lt;span class=&#34;number&#34;&gt;32&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;comment&#34;&gt;# 验证&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;ceph osd pool ls&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;comment&#34;&gt;# pool启用rbd&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;ceph osd pool application enable rbd rbd&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;comment&#34;&gt;# 初始化&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;rbd pool init -p rbd&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;comment&#34;&gt;# 创建img映像（可以多创）&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;rbd create rbd-data-img1 --size 1G --pool rbd --image-&lt;span class=&#34;built_in&#34;&gt;format&lt;/span&gt; &lt;span class=&#34;number&#34;&gt;2&lt;/span&gt; --image-feature layering&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;comment&#34;&gt;# 验证&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;rbd ls --pool rbd -l&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/figure&gt;
&lt;p&gt;客户端使用rbd：&lt;/p&gt;
&lt;figure class=&#34;highlight python&#34;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&#34;gutter&#34;&gt;&lt;pre&gt;&lt;span class=&#34;line&#34;&gt;1&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;2&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;3&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;4&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;5&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&#34;code&#34;&gt;&lt;pre&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;comment&#34;&gt;# 需要先安装ceph-common&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;comment&#34;&gt;# 前两条可选，如果你配置了epel源那就不需要了。&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;yum install epel-release&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;yum install https://mirrors.aliyun.com/ceph/rpm-octopus/el7/noarch/ceph-release-&lt;span class=&#34;number&#34;&gt;1&lt;/span&gt;-&lt;span class=&#34;number&#34;&gt;1.&lt;/span&gt;el7.noarch.rpm -y&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;yum install ceph-common&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/figure&gt;
&lt;p&gt;NOTE：&lt;strong&gt;客户端的/etc/ceph/文件夹下的&lt;code&gt;ceph.conf 和 ceph.client.admin.keyring&lt;/code&gt;需要和集群的相同。&lt;/strong&gt;&lt;/p&gt;
&lt;figure class=&#34;highlight python&#34;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&#34;gutter&#34;&gt;&lt;pre&gt;&lt;span class=&#34;line&#34;&gt;1&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&#34;code&#34;&gt;&lt;pre&gt;&lt;span class=&#34;line&#34;&gt;rbd -p rbd &lt;span class=&#34;built_in&#34;&gt;map&lt;/span&gt; rbd-data-img1&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/figure&gt;
&lt;p&gt;&lt;img src=&#34;https://cf.s3.soulter.top/ceph-host/Untitled8.png&#34; alt=&#34;Untitled&#34; /&gt;&lt;/p&gt;
&lt;p&gt;然后我们就可以用lsblk指令看到刚刚映射的盘了。&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://cf.s3.soulter.top/ceph-host/Untitled9.png&#34; alt=&#34;Untitled&#34; /&gt;&lt;/p&gt;
&lt;figure class=&#34;highlight bash&#34;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&#34;gutter&#34;&gt;&lt;pre&gt;&lt;span class=&#34;line&#34;&gt;1&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;2&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;3&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;4&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&#34;code&#34;&gt;&lt;pre&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;comment&#34;&gt;# 格式化磁盘并挂载&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;mkfs.xfs /dev/rbd0&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;built_in&#34;&gt;mkdir&lt;/span&gt; /data -p&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;mount /dev/rbd0 /data&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/figure&gt;
&lt;p&gt;&lt;img src=&#34;https://cf.s3.soulter.top/ceph-host/Untitled10.png&#34; alt=&#34;Untitled&#34; /&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://cf.s3.soulter.top/ceph-host/Untitled11.png&#34; alt=&#34;Untitled&#34; /&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;（到这里我们就可以开发出阿里网盘的挂载盘的功能了）&lt;/strong&gt;&lt;/p&gt;
&lt;h1 id=&#34;自动负载均衡脚本&#34;&gt;&lt;a class=&#34;markdownIt-Anchor&#34; href=&#34;#自动负载均衡脚本&#34;&gt;&lt;/a&gt; 自动负载均衡脚本&lt;/h1&gt;
&lt;p&gt;本脚本主要使用osdmaptool这一个工具来解决pg分布不均衡的问题，从外层看，解决的是磁盘占用率不均衡的问题。&lt;/p&gt;
&lt;p&gt;osdmaptool 工具通过一些参数来输出需要优化的信息。&lt;/p&gt;
&lt;figure class=&#34;highlight python&#34;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&#34;gutter&#34;&gt;&lt;pre&gt;&lt;span class=&#34;line&#34;&gt;1&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;2&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;3&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;4&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;5&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;6&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;7&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;8&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;9&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&#34;code&#34;&gt;&lt;pre&gt;&lt;span class=&#34;line&#34;&gt;命令&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;osdmaptool &amp;#123;osdmap_filename&amp;#125; --upmap out.txt [–upmap-pool ] [–upmap-&lt;span class=&#34;built_in&#34;&gt;max&lt;/span&gt; ] [–upmap-deviation ]&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;其中&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;upmap-pool ：指定需要优化均衡的存储池名&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;upmap-&lt;span class=&#34;built_in&#34;&gt;max&lt;/span&gt;： 指定一次优化的数据条目，默认&lt;span class=&#34;number&#34;&gt;100&lt;/span&gt;，可根据环境业务情况调整该值，一次调整的条目越多，数据迁移会越多，可能对环境业务造成影响。&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;built_in&#34;&gt;max&lt;/span&gt;-deviation：最大偏差值，默认为&lt;span class=&#34;number&#34;&gt;0.01&lt;/span&gt;（即&lt;span class=&#34;number&#34;&gt;1&lt;/span&gt;％）。如果OSD利用率与平均值之间的差异小于此值，则将被视为完美。&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/figure&gt;
&lt;p&gt;下面的代码主要实现了定时启动、决定何时调用osdmaptool。&lt;/p&gt;
&lt;figure class=&#34;highlight python&#34;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&#34;gutter&#34;&gt;&lt;pre&gt;&lt;span class=&#34;line&#34;&gt;1&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;2&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;3&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;4&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;5&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;6&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;7&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;8&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;9&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;10&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;11&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;12&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;13&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;14&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;15&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;16&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;17&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;18&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;19&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;20&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;21&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;22&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;23&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;24&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;25&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;26&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;27&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;28&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;29&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;30&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;31&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;32&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;33&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;34&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;35&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;36&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;37&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;38&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;39&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;40&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;41&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;42&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;43&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;44&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;45&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;46&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;47&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;48&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;49&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;50&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;51&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;52&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;53&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;54&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;55&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;56&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;57&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;58&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;59&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;60&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;61&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;62&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;63&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;64&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;65&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;66&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;67&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;68&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;69&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;70&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;71&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;72&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;73&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;74&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;75&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;76&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;77&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;78&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;79&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;80&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;81&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;82&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;83&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;84&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;85&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;86&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;87&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;88&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;89&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;90&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;91&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;92&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;93&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;94&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;95&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;96&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;97&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;98&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;99&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;100&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;101&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;102&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;103&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;104&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;105&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;106&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;107&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;108&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;109&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;110&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;111&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;112&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;113&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;114&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;115&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;116&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;117&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;118&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;119&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;120&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;121&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;122&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;123&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;124&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;125&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;126&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;127&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;128&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;129&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;130&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;131&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;132&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;133&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;134&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;135&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;136&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;137&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;138&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;139&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;140&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;141&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;142&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;143&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;144&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;145&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;146&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;147&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;148&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&#34;code&#34;&gt;&lt;pre&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;keyword&#34;&gt;import&lt;/span&gt; os&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;keyword&#34;&gt;import&lt;/span&gt; time&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;keyword&#34;&gt;from&lt;/span&gt; subprocess &lt;span class=&#34;keyword&#34;&gt;import&lt;/span&gt; Popen, TimeoutExpired, PIPE&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;keyword&#34;&gt;import&lt;/span&gt; datetime&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;UPMAP_MAX = &lt;span class=&#34;number&#34;&gt;100&lt;/span&gt; &lt;span class=&#34;comment&#34;&gt;# 最大优化指令行数&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;UPMAP_DEVIATION = &lt;span class=&#34;number&#34;&gt;1&lt;/span&gt; &lt;span class=&#34;comment&#34;&gt;# 最大偏差值，如果OSD利用率与平均值之间的差异小于此值，将被认为完美而不优化。&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;POOL = &lt;span class=&#34;string&#34;&gt;&amp;quot;cephfs_data&amp;quot;&lt;/span&gt; &lt;span class=&#34;comment&#34;&gt;# pool名称。后期可以做成轮询所有pool或指定的n个pool&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;ROUND_SECONDS = &lt;span class=&#34;number&#34;&gt;1000&lt;/span&gt; &lt;span class=&#34;comment&#34;&gt;# 脚本定时启动时间&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;FG_COLORS = &amp;#123;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;    &lt;span class=&#34;string&#34;&gt;&amp;quot;black&amp;quot;&lt;/span&gt;: &lt;span class=&#34;string&#34;&gt;&amp;quot;30&amp;quot;&lt;/span&gt;,&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;    &lt;span class=&#34;string&#34;&gt;&amp;quot;red&amp;quot;&lt;/span&gt;: &lt;span class=&#34;string&#34;&gt;&amp;quot;31&amp;quot;&lt;/span&gt;,&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;    &lt;span class=&#34;string&#34;&gt;&amp;quot;green&amp;quot;&lt;/span&gt;: &lt;span class=&#34;string&#34;&gt;&amp;quot;32&amp;quot;&lt;/span&gt;,&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;    &lt;span class=&#34;string&#34;&gt;&amp;quot;yellow&amp;quot;&lt;/span&gt;: &lt;span class=&#34;string&#34;&gt;&amp;quot;33&amp;quot;&lt;/span&gt;,&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;    &lt;span class=&#34;string&#34;&gt;&amp;quot;blue&amp;quot;&lt;/span&gt;: &lt;span class=&#34;string&#34;&gt;&amp;quot;34&amp;quot;&lt;/span&gt;,&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;    &lt;span class=&#34;string&#34;&gt;&amp;quot;purple&amp;quot;&lt;/span&gt;: &lt;span class=&#34;string&#34;&gt;&amp;quot;35&amp;quot;&lt;/span&gt;,&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;    &lt;span class=&#34;string&#34;&gt;&amp;quot;cyan&amp;quot;&lt;/span&gt;: &lt;span class=&#34;string&#34;&gt;&amp;quot;36&amp;quot;&lt;/span&gt;,&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;    &lt;span class=&#34;string&#34;&gt;&amp;quot;white&amp;quot;&lt;/span&gt;: &lt;span class=&#34;string&#34;&gt;&amp;quot;37&amp;quot;&lt;/span&gt;,&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;    &lt;span class=&#34;string&#34;&gt;&amp;quot;default&amp;quot;&lt;/span&gt;: &lt;span class=&#34;string&#34;&gt;&amp;quot;39&amp;quot;&lt;/span&gt;,&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;&amp;#125;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;BG_COLORS = &amp;#123;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;    &lt;span class=&#34;string&#34;&gt;&amp;quot;black&amp;quot;&lt;/span&gt;: &lt;span class=&#34;string&#34;&gt;&amp;quot;40&amp;quot;&lt;/span&gt;,&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;    &lt;span class=&#34;string&#34;&gt;&amp;quot;red&amp;quot;&lt;/span&gt;: &lt;span class=&#34;string&#34;&gt;&amp;quot;41&amp;quot;&lt;/span&gt;,&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;    &lt;span class=&#34;string&#34;&gt;&amp;quot;green&amp;quot;&lt;/span&gt;: &lt;span class=&#34;string&#34;&gt;&amp;quot;42&amp;quot;&lt;/span&gt;,&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;    &lt;span class=&#34;string&#34;&gt;&amp;quot;yellow&amp;quot;&lt;/span&gt;: &lt;span class=&#34;string&#34;&gt;&amp;quot;43&amp;quot;&lt;/span&gt;,&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;    &lt;span class=&#34;string&#34;&gt;&amp;quot;blue&amp;quot;&lt;/span&gt;: &lt;span class=&#34;string&#34;&gt;&amp;quot;44&amp;quot;&lt;/span&gt;,&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;    &lt;span class=&#34;string&#34;&gt;&amp;quot;purple&amp;quot;&lt;/span&gt;: &lt;span class=&#34;string&#34;&gt;&amp;quot;45&amp;quot;&lt;/span&gt;,&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;    &lt;span class=&#34;string&#34;&gt;&amp;quot;cyan&amp;quot;&lt;/span&gt;: &lt;span class=&#34;string&#34;&gt;&amp;quot;46&amp;quot;&lt;/span&gt;,&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;    &lt;span class=&#34;string&#34;&gt;&amp;quot;white&amp;quot;&lt;/span&gt;: &lt;span class=&#34;string&#34;&gt;&amp;quot;47&amp;quot;&lt;/span&gt;,&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;    &lt;span class=&#34;string&#34;&gt;&amp;quot;default&amp;quot;&lt;/span&gt;: &lt;span class=&#34;string&#34;&gt;&amp;quot;49&amp;quot;&lt;/span&gt;,&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;&amp;#125;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;LEVEL_INFO = &lt;span class=&#34;string&#34;&gt;&amp;quot;INFO&amp;quot;&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;LEVEL_WARNING = &lt;span class=&#34;string&#34;&gt;&amp;quot;WARNING&amp;quot;&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;LEVEL_ERROR = &lt;span class=&#34;string&#34;&gt;&amp;quot;ERROR&amp;quot;&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;LEVEL_CRITICAL = &lt;span class=&#34;string&#34;&gt;&amp;quot;CRITICAL&amp;quot;&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;level_colors = &amp;#123;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;    &lt;span class=&#34;string&#34;&gt;&amp;quot;INFO&amp;quot;&lt;/span&gt;: &lt;span class=&#34;string&#34;&gt;&amp;quot;green&amp;quot;&lt;/span&gt;,&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;    &lt;span class=&#34;string&#34;&gt;&amp;quot;WARNING&amp;quot;&lt;/span&gt;: &lt;span class=&#34;string&#34;&gt;&amp;quot;yellow&amp;quot;&lt;/span&gt;,&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;    &lt;span class=&#34;string&#34;&gt;&amp;quot;ERROR&amp;quot;&lt;/span&gt;: &lt;span class=&#34;string&#34;&gt;&amp;quot;red&amp;quot;&lt;/span&gt;,&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;    &lt;span class=&#34;string&#34;&gt;&amp;quot;CRITICAL&amp;quot;&lt;/span&gt;: &lt;span class=&#34;string&#34;&gt;&amp;quot;purple&amp;quot;&lt;/span&gt;,&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;&amp;#125;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;keyword&#34;&gt;def&lt;/span&gt; &lt;span class=&#34;title function_&#34;&gt;get_osd_df&lt;/span&gt;(&lt;span class=&#34;params&#34;&gt;pool&lt;/span&gt;):&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;    osd_df = os.popen(&lt;span class=&#34;string&#34;&gt;&amp;quot;cephadm shell ceph osd df&amp;quot;&lt;/span&gt;).read()&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;    osd_df = osd_df.split(&lt;span class=&#34;string&#34;&gt;&amp;quot;\n&amp;quot;&lt;/span&gt;)[&lt;span class=&#34;number&#34;&gt;1&lt;/span&gt;:]&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;    osd_df = [line.split(&lt;span class=&#34;string&#34;&gt;&amp;quot; &amp;quot;&lt;/span&gt;) &lt;span class=&#34;keyword&#34;&gt;for&lt;/span&gt; line &lt;span class=&#34;keyword&#34;&gt;in&lt;/span&gt; osd_df]&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;    osd_df = [[word &lt;span class=&#34;keyword&#34;&gt;for&lt;/span&gt; word &lt;span class=&#34;keyword&#34;&gt;in&lt;/span&gt; line &lt;span class=&#34;keyword&#34;&gt;if&lt;/span&gt; word != &lt;span class=&#34;string&#34;&gt;&amp;quot;&amp;quot;&lt;/span&gt;] &lt;span class=&#34;keyword&#34;&gt;for&lt;/span&gt; line &lt;span class=&#34;keyword&#34;&gt;in&lt;/span&gt; osd_df]&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;    osd_df = osd_df[:-&lt;span class=&#34;number&#34;&gt;3&lt;/span&gt;]&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;    &lt;span class=&#34;comment&#34;&gt;# print(&amp;quot;osd_df:&amp;quot;)&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;    &lt;span class=&#34;comment&#34;&gt;# print(osd_df)&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;    ave_used = &lt;span class=&#34;number&#34;&gt;0&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;    &lt;span class=&#34;keyword&#34;&gt;for&lt;/span&gt; osd &lt;span class=&#34;keyword&#34;&gt;in&lt;/span&gt; osd_df:&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;        ave_used += &lt;span class=&#34;built_in&#34;&gt;float&lt;/span&gt;(osd[&lt;span class=&#34;number&#34;&gt;16&lt;/span&gt;])&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;    ave_used /= &lt;span class=&#34;built_in&#34;&gt;len&lt;/span&gt;(osd_df)&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;    log(&lt;span class=&#34;string&#34;&gt;f&amp;quot;pool &lt;span class=&#34;subst&#34;&gt;&amp;#123;pool&amp;#125;&lt;/span&gt; OSD Average used: &amp;quot;&lt;/span&gt; + &lt;span class=&#34;built_in&#34;&gt;str&lt;/span&gt;(ave_used), level=LEVEL_INFO)&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;    &lt;span class=&#34;comment&#34;&gt;#print(&amp;quot;Average used: &amp;quot; + str(ave_used))&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;    osd_to_upmap = []&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;    &lt;span class=&#34;keyword&#34;&gt;for&lt;/span&gt; osd &lt;span class=&#34;keyword&#34;&gt;in&lt;/span&gt; osd_df:&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;        &lt;span class=&#34;keyword&#34;&gt;if&lt;/span&gt; osd[&lt;span class=&#34;number&#34;&gt;19&lt;/span&gt;] != &lt;span class=&#34;string&#34;&gt;&amp;quot;UP&amp;quot;&lt;/span&gt;:&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;            &lt;span class=&#34;keyword&#34;&gt;continue&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;        &lt;span class=&#34;comment&#34;&gt;# print(&amp;quot;osd: &amp;quot; + str(osd))&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;        &lt;span class=&#34;keyword&#34;&gt;if&lt;/span&gt; &lt;span class=&#34;built_in&#34;&gt;abs&lt;/span&gt;(&lt;span class=&#34;built_in&#34;&gt;float&lt;/span&gt;(osd[&lt;span class=&#34;number&#34;&gt;16&lt;/span&gt;]) - ave_used) &amp;gt; UPMAP_DEVIATION:&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;            osd_to_upmap.append(osd[&lt;span class=&#34;number&#34;&gt;0&lt;/span&gt;])&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;   &lt;span class=&#34;comment&#34;&gt;# print(&amp;quot;OSD to upmap: &amp;quot; + str(osd_to_upmap))&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;    &lt;span class=&#34;keyword&#34;&gt;if&lt;/span&gt; &lt;span class=&#34;built_in&#34;&gt;len&lt;/span&gt;(osd_to_upmap) &amp;gt; &lt;span class=&#34;number&#34;&gt;0&lt;/span&gt;:&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;        log(&lt;span class=&#34;string&#34;&gt;f&amp;quot;Found unbalanced OSD in pool &lt;span class=&#34;subst&#34;&gt;&amp;#123;pool&amp;#125;&lt;/span&gt;: &amp;quot;&lt;/span&gt; + &lt;span class=&#34;built_in&#34;&gt;str&lt;/span&gt;(osd_to_upmap), level=LEVEL_INFO)&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;        &lt;span class=&#34;comment&#34;&gt;# os.popen(&amp;quot;cephadm shell ceph osd getmap -o osd.map&amp;quot;).read()&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;        &lt;span class=&#34;comment&#34;&gt;# p = Popen([&amp;quot;cephadm&amp;quot;, &amp;quot;shell&amp;quot;], stdin=PIPE, stdout=PIPE, stderr=PIPE)&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;        &lt;span class=&#34;comment&#34;&gt;# p.stdin.write(b&amp;quot;ceph osd getmap -o osd.map\n&amp;quot;)&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;        &lt;span class=&#34;comment&#34;&gt;# p.stdin.write(b&amp;quot;osdmaptool osd.map --upmap &amp;quot; + b&amp;quot; &amp;quot;.join(osd_to_upmap) + b&amp;quot; --upmap-pool &amp;quot; + bytes(POOL, encoding=&amp;quot;utf-8&amp;quot;) + b&amp;quot; --upmap-max &amp;quot; + bytes(str(UPMAP_MAX), encoding=&amp;quot;utf-8&amp;quot;) + b&amp;quot; --upmap-deviation &amp;quot; + bytes(str(UPMAP_DEVIATION), encoding=&amp;quot;utf-8&amp;quot;) + b&amp;quot;\n&amp;quot;)&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;        &lt;span class=&#34;comment&#34;&gt;# p.stdin.write(b&amp;quot;exit\n&amp;quot;)&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;        &lt;span class=&#34;comment&#34;&gt;# p.stdin.close()&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;        &lt;span class=&#34;comment&#34;&gt;# print(p.stdout.read().decode(&amp;quot;utf-8&amp;quot;))&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;        &lt;span class=&#34;comment&#34;&gt;# print(p.stderr.read().decode(&amp;quot;utf-8&amp;quot;))&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;        t = os.popen(&lt;span class=&#34;string&#34;&gt;f&amp;quot;cephadm shell -- bash -c &amp;#x27;ceph osd getmap -o osd.map; osdmaptool osd.map --upmap t.out --upmap-pool &lt;span class=&#34;subst&#34;&gt;&amp;#123;pool&amp;#125;&lt;/span&gt; --upmap-max &lt;span class=&#34;subst&#34;&gt;&amp;#123;UPMAP_MAX&amp;#125;&lt;/span&gt; --upmap-deviation &lt;span class=&#34;subst&#34;&gt;&amp;#123;UPMAP_DEVIATION&amp;#125;&lt;/span&gt;; cat t.out&amp;#x27;&amp;quot;&lt;/span&gt;).read()&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;        upmap_cmds = []&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;        l = t.split(&lt;span class=&#34;string&#34;&gt;&amp;quot;\n&amp;quot;&lt;/span&gt;)&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;        &lt;span class=&#34;keyword&#34;&gt;for&lt;/span&gt; line &lt;span class=&#34;keyword&#34;&gt;in&lt;/span&gt; l:&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;            &lt;span class=&#34;keyword&#34;&gt;if&lt;/span&gt; line.startswith(&lt;span class=&#34;string&#34;&gt;&amp;quot;ceph osd pg-upmap-item&amp;quot;&lt;/span&gt;):&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;                &lt;span class=&#34;comment&#34;&gt;# print(line)&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;                upmap_cmds.append(line)&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;        log(&lt;span class=&#34;string&#34;&gt;f&amp;quot;Upmap commands: &amp;quot;&lt;/span&gt; + &lt;span class=&#34;built_in&#34;&gt;str&lt;/span&gt;(upmap_cmds), level=LEVEL_INFO)&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;        f_name = &lt;span class=&#34;string&#34;&gt;f&amp;quot;osd_upmap_&lt;span class=&#34;subst&#34;&gt;&amp;#123;time.time()&amp;#125;&lt;/span&gt;&amp;quot;&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;        &lt;span class=&#34;keyword&#34;&gt;if&lt;/span&gt; &lt;span class=&#34;built_in&#34;&gt;len&lt;/span&gt;(upmap_cmds) &amp;gt; &lt;span class=&#34;number&#34;&gt;0&lt;/span&gt;:&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;            &lt;span class=&#34;keyword&#34;&gt;with&lt;/span&gt; &lt;span class=&#34;built_in&#34;&gt;open&lt;/span&gt;(f_name, &lt;span class=&#34;string&#34;&gt;&amp;quot;w&amp;quot;&lt;/span&gt;) &lt;span class=&#34;keyword&#34;&gt;as&lt;/span&gt; f:&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;                f.write(&lt;span class=&#34;string&#34;&gt;&amp;quot;\n&amp;quot;&lt;/span&gt;.join(upmap_cmds))&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;            &lt;span class=&#34;comment&#34;&gt;# 执行&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;            t = os.popen(&lt;span class=&#34;string&#34;&gt;f&amp;quot;chmod +x &lt;span class=&#34;subst&#34;&gt;&amp;#123;f_name&amp;#125;&lt;/span&gt; &amp;amp;&amp;amp; ./&lt;span class=&#34;subst&#34;&gt;&amp;#123;f_name&amp;#125;&lt;/span&gt;&amp;quot;&lt;/span&gt;).read()&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;            log(&lt;span class=&#34;string&#34;&gt;f&amp;quot;Upmap done. Result: &amp;quot;&lt;/span&gt; + &lt;span class=&#34;built_in&#34;&gt;str&lt;/span&gt;(t), level=LEVEL_INFO)&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;        &lt;span class=&#34;keyword&#34;&gt;else&lt;/span&gt;:&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;            log(&lt;span class=&#34;string&#34;&gt;f&amp;quot;No upmap commands generated, skip.&amp;quot;&lt;/span&gt;, level=LEVEL_INFO)&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;comment&#34;&gt;# 这里可以不用理解。这只是一个打印log的函数。&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;keyword&#34;&gt;def&lt;/span&gt; &lt;span class=&#34;title function_&#34;&gt;log&lt;/span&gt;(&lt;span class=&#34;params&#34;&gt;&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;params&#34;&gt;        msg: &lt;span class=&#34;built_in&#34;&gt;str&lt;/span&gt;,&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;params&#34;&gt;        level: &lt;span class=&#34;built_in&#34;&gt;str&lt;/span&gt; = &lt;span class=&#34;string&#34;&gt;&amp;quot;INFO&amp;quot;&lt;/span&gt;,&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;params&#34;&gt;        tag: &lt;span class=&#34;built_in&#34;&gt;str&lt;/span&gt; = &lt;span class=&#34;string&#34;&gt;&amp;quot;System&amp;quot;&lt;/span&gt;,&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;params&#34;&gt;        fg: &lt;span class=&#34;built_in&#34;&gt;str&lt;/span&gt; = &lt;span class=&#34;literal&#34;&gt;None&lt;/span&gt;,&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;params&#34;&gt;        bg: &lt;span class=&#34;built_in&#34;&gt;str&lt;/span&gt; = &lt;span class=&#34;literal&#34;&gt;None&lt;/span&gt;,&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;params&#34;&gt;        max_len: &lt;span class=&#34;built_in&#34;&gt;int&lt;/span&gt; = &lt;span class=&#34;number&#34;&gt;10000&lt;/span&gt;&lt;/span&gt;):&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;    &lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;    &lt;span class=&#34;keyword&#34;&gt;if&lt;/span&gt; &lt;span class=&#34;built_in&#34;&gt;len&lt;/span&gt;(msg) &amp;gt; max_len:&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;        msg = msg[:max_len] + &lt;span class=&#34;string&#34;&gt;&amp;quot;...&amp;quot;&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;    now = datetime.datetime.now().strftime(&lt;span class=&#34;string&#34;&gt;&amp;quot;%m-%d %H:%M:%S&amp;quot;&lt;/span&gt;)&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;    pre = &lt;span class=&#34;string&#34;&gt;f&amp;quot;[&lt;span class=&#34;subst&#34;&gt;&amp;#123;now&amp;#125;&lt;/span&gt;] [&lt;span class=&#34;subst&#34;&gt;&amp;#123;level&amp;#125;&lt;/span&gt;] [&lt;span class=&#34;subst&#34;&gt;&amp;#123;tag&amp;#125;&lt;/span&gt;]: &lt;span class=&#34;subst&#34;&gt;&amp;#123;msg&amp;#125;&lt;/span&gt;&amp;quot;&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;    &lt;span class=&#34;keyword&#34;&gt;if&lt;/span&gt; level == &lt;span class=&#34;string&#34;&gt;&amp;quot;INFO&amp;quot;&lt;/span&gt;:&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;        &lt;span class=&#34;keyword&#34;&gt;if&lt;/span&gt; fg &lt;span class=&#34;keyword&#34;&gt;is&lt;/span&gt; &lt;span class=&#34;literal&#34;&gt;None&lt;/span&gt;:&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;            fg = FG_COLORS[&lt;span class=&#34;string&#34;&gt;&amp;quot;green&amp;quot;&lt;/span&gt;]&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;        &lt;span class=&#34;keyword&#34;&gt;if&lt;/span&gt; bg &lt;span class=&#34;keyword&#34;&gt;is&lt;/span&gt; &lt;span class=&#34;literal&#34;&gt;None&lt;/span&gt;:&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;            bg = BG_COLORS[&lt;span class=&#34;string&#34;&gt;&amp;quot;default&amp;quot;&lt;/span&gt;]&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;    &lt;span class=&#34;keyword&#34;&gt;elif&lt;/span&gt; level == &lt;span class=&#34;string&#34;&gt;&amp;quot;WARNING&amp;quot;&lt;/span&gt;:&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;        &lt;span class=&#34;keyword&#34;&gt;if&lt;/span&gt; fg &lt;span class=&#34;keyword&#34;&gt;is&lt;/span&gt; &lt;span class=&#34;literal&#34;&gt;None&lt;/span&gt;:&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;            fg = FG_COLORS[&lt;span class=&#34;string&#34;&gt;&amp;quot;yellow&amp;quot;&lt;/span&gt;]&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;        &lt;span class=&#34;keyword&#34;&gt;if&lt;/span&gt; bg &lt;span class=&#34;keyword&#34;&gt;is&lt;/span&gt; &lt;span class=&#34;literal&#34;&gt;None&lt;/span&gt;:&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;            bg = BG_COLORS[&lt;span class=&#34;string&#34;&gt;&amp;quot;default&amp;quot;&lt;/span&gt;]&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;    &lt;span class=&#34;keyword&#34;&gt;elif&lt;/span&gt; level == &lt;span class=&#34;string&#34;&gt;&amp;quot;ERROR&amp;quot;&lt;/span&gt;:&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;        &lt;span class=&#34;keyword&#34;&gt;if&lt;/span&gt; fg &lt;span class=&#34;keyword&#34;&gt;is&lt;/span&gt; &lt;span class=&#34;literal&#34;&gt;None&lt;/span&gt;:&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;            fg = FG_COLORS[&lt;span class=&#34;string&#34;&gt;&amp;quot;red&amp;quot;&lt;/span&gt;]&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;        &lt;span class=&#34;keyword&#34;&gt;if&lt;/span&gt; bg &lt;span class=&#34;keyword&#34;&gt;is&lt;/span&gt; &lt;span class=&#34;literal&#34;&gt;None&lt;/span&gt;:&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;            bg = BG_COLORS[&lt;span class=&#34;string&#34;&gt;&amp;quot;default&amp;quot;&lt;/span&gt;]&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;    &lt;span class=&#34;keyword&#34;&gt;elif&lt;/span&gt; level == &lt;span class=&#34;string&#34;&gt;&amp;quot;CRITICAL&amp;quot;&lt;/span&gt;:&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;        &lt;span class=&#34;keyword&#34;&gt;if&lt;/span&gt; fg &lt;span class=&#34;keyword&#34;&gt;is&lt;/span&gt; &lt;span class=&#34;literal&#34;&gt;None&lt;/span&gt;:&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;            fg = FG_COLORS[&lt;span class=&#34;string&#34;&gt;&amp;quot;purple&amp;quot;&lt;/span&gt;]&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;        &lt;span class=&#34;keyword&#34;&gt;if&lt;/span&gt; bg &lt;span class=&#34;keyword&#34;&gt;is&lt;/span&gt; &lt;span class=&#34;literal&#34;&gt;None&lt;/span&gt;:&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;            bg = BG_COLORS[&lt;span class=&#34;string&#34;&gt;&amp;quot;default&amp;quot;&lt;/span&gt;]&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;    &lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;    &lt;span class=&#34;built_in&#34;&gt;print&lt;/span&gt;(&lt;span class=&#34;string&#34;&gt;f&amp;quot;\033[&lt;span class=&#34;subst&#34;&gt;&amp;#123;fg&amp;#125;&lt;/span&gt;;&lt;span class=&#34;subst&#34;&gt;&amp;#123;bg&amp;#125;&lt;/span&gt;m&lt;span class=&#34;subst&#34;&gt;&amp;#123;pre&amp;#125;&lt;/span&gt;\033[0m&amp;quot;&lt;/span&gt;)&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;    &lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;keyword&#34;&gt;if&lt;/span&gt; __name__ == &lt;span class=&#34;string&#34;&gt;&amp;quot;__main__&amp;quot;&lt;/span&gt;:&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;    &lt;span class=&#34;keyword&#34;&gt;while&lt;/span&gt; &lt;span class=&#34;literal&#34;&gt;True&lt;/span&gt;:&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;        log(&lt;span class=&#34;string&#34;&gt;&amp;quot;Balance check start&amp;quot;&lt;/span&gt;, level=LEVEL_INFO, bg=BG_COLORS[&lt;span class=&#34;string&#34;&gt;&amp;quot;yellow&amp;quot;&lt;/span&gt;])&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;        get_osd_df(pool=POOL)&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;        log(&lt;span class=&#34;string&#34;&gt;&amp;quot;Balance check end&amp;quot;&lt;/span&gt;, level=LEVEL_INFO, bg=BG_COLORS[&lt;span class=&#34;string&#34;&gt;&amp;quot;yellow&amp;quot;&lt;/span&gt;])&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;        time.sleep(ROUND_SECONDS)&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/figure&gt;
&lt;p&gt;效果（由于测试集群负载不大，因此效果可能不太显著，但是是倾向于有效果的。）：&lt;/p&gt;
&lt;p&gt;原来：&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://cf.s3.soulter.top/ceph-host/Untitled12.png&#34; alt=&#34;Untitled&#34; /&gt;&lt;/p&gt;
&lt;p&gt;优化后：&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://cf.s3.soulter.top/ceph-host/Untitled13.png&#34; alt=&#34;Untitled&#34; /&gt;&lt;/p&gt;
&lt;h1 id=&#34;整理一些资源&#34;&gt;&lt;a class=&#34;markdownIt-Anchor&#34; href=&#34;#整理一些资源&#34;&gt;&lt;/a&gt; 整理一些资源&lt;/h1&gt;
&lt;p&gt;&lt;a href=&#34;https://www.jianshu.com/p/afb6277dbfd6&#34;&gt;Ceph Luminous手动解决pg分布不均衡问题 - 简书 (jianshu.com)&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href=&#34;https://blog.csdn.net/baidu_39240038/article/details/115255018&#34;&gt;ceph 数据均衡(balance)_ceph balancer_菜猿猿的博客-CSDN博客&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href=&#34;https://github.com/0voice/kernel_awsome_feature/blob/main/Ceph/Ceph%E5%88%86%E5%B1%82%E5%AD%98%E5%82%A8%E4%BC%98%E5%8C%96%E7%AD%96%E7%95%A5%E7%A0%94%E7%A9%B6%E4%B8%8E%E5%AE%9E%E7%8E%B0.pdf&#34;&gt;kernel_awsome_feature/Ceph/Ceph分层存储优化策略研究与实现.pdf at main · 0voice/kernel_awsome_feature (github.com)&lt;/a&gt;&lt;a href=&#34;https://www.cnblogs.com/sammyliu/p/5066895.html&#34;&gt;理解 QEMU/KVM 和 Ceph（1）：QEMU-KVM 和 Ceph RBD 的 缓存机制总结 - SammyLiu - 博客园 (cnblogs.com)&lt;/a&gt;&lt;a href=&#34;https://www.infoq.cn/article/B5VpI6e66leBUbIISap4&#34;&gt;Ceph 发展十年的教训：文件系统不适合作为分布式存储后端_软件工程_Murat Demirbas_InfoQ精选文章&lt;/a&gt;&lt;a href=&#34;https://juejin.cn/post/7174241949543563271&#34;&gt;cephAdm部署ceph集群 - 掘金 (juejin.cn)&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href=&#34;https://access.redhat.com/documentation/zh-cn/red_hat_ceph_storage/6/html/developer_guide/the-ceph-restful-api-specifications&#34;&gt;https://access.redhat.com/documentation/zh-cn/red_hat_ceph_storage/6/html/developer_guide/the-ceph-restful-api-specifications&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href=&#34;https://www.cnblogs.com/wangjq19920210/p/12160064.html#:~:text=%E4%B8%8A%E4%BC%A0%E6%96%87%E4%BB%B6%20%23%20%E8%BF%9E%E6%8E%A5%E5%88%B0test%E6%B1%A0%20ioctx%20%3D%20cluster.open_ioctx%20%28%27test%27%29%20file_name,%28%29%20%23%20%E5%B0%86%E6%96%87%E4%BB%B6%E5%86%99%E5%85%A5%E6%B1%A0%20ioctx.write_full%20%28file_name%2C%20file_content%29%20ioctx.close%20%28%29&#34;&gt;Ceph的Python接口 - salami_china - 博客园 (cnblogs.com)&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href=&#34;https://www.cnblogs.com/eliwsj/p/15177033.html&#34;&gt;ceph 对象存储查询对象数据 - 殇™ - 博客园 (cnblogs.com)&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href=&#34;https://www.cnblogs.com/sunbines/p/15535895.html&#34;&gt;https://www.cnblogs.com/sunbines/p/15535895.html&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href=&#34;https://www.dazhuanlan.com/xqwang/topics/1661592&#34;&gt;Ceph笔记 · 大专栏 (dazhuanlan.com)&lt;/a&gt;&lt;/p&gt;
</content>
        <category term="2023" />
        <updated>2023-08-25T16:08:00.000Z</updated>
    </entry>
    <entry>
        <id>https://blog.soulter.top/posts/start-programming.html</id>
        <title>回想 / 从 0 到 1</title>
        <link rel="alternate" href="https://blog.soulter.top/posts/start-programming.html"/>
        <content type="html">&lt;blockquote&gt;
&lt;p&gt;年代久远，可能未涉及全部内容，后续会慢慢补充。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;小时候，我的家庭条件不是很好，当然也不算特别差，中规中矩的水平。&lt;/p&gt;
&lt;p&gt;小学四五年级左右，我迷上了 Minecraft，我迷恋它近乎无限的地图和极高的创造性。起初，我只是每天放学后和家里隔壁饭店和我年纪差不多大的朋友一起去他家玩电脑版的MC，后来，我了解到了MC也有手机版，因此就开始拿着家里人的手机下载便携版的MC之后沉迷到游戏中。逐渐地，我了解到了优酷的籽岷（当时岷叔还主要活跃在优酷上）、多玩盒子、百度贴吧（当时的百度贴吧比现在的风气好。噢，倒不如说当时的所有社交平台风气都比现在的好。）、各种服务器（神话），在服务器上认识了至今还有联系但素未谋面的网友、蛤蟆吃…&lt;/p&gt;
&lt;p&gt;我第一次感到一个游戏原来有如此多的花样，而不单单只是一个游戏本身，我第一次对一个游戏充满了无限的热爱。慢慢地，我开始仿照岷叔，在优酷上发布了第一个MC实况视频，我不在意是否有人看，仅仅因为喜欢。&lt;br /&gt;
&lt;img src=&#34;https://cf.s3.soulter.top/20230723013138636/20230723022519066.png&#34; alt=&#34;&#34; /&gt;&lt;br /&gt;
越发不可收拾，3年过去了，我在优酷上上传了一百多个视频。也就是在这期间，对计算机的热爱在我的心里逐渐燃烧：局域网联机、蛤蟆吃联机、修改MC配置文件…&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;可以说Minecraft间接地带我走进了编程的世界。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;我开始了解到编程这个事物，并对编程感兴趣大概是从2016年的初二上学期开始的。学校公告栏上张贴了一份奥林匹克信息学竞赛培训的通知，当时顿时来了兴趣，对另一个好友说：“要不参加试试看吧！”我不擅长主动邀请别人，因为怕被拒绝。令我些许惊讶的是，他很快就答应了。我们开始投入C语言的怀抱。&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://cf.s3.soulter.top/20230723013138636/20230723032119218.png&#34; alt=&#34;&#34; /&gt;&lt;/p&gt;
&lt;p&gt;很快，我们参加了培训。老师人很好，耐心地教我们什么是include，什么是基本类型。我们了解到了如何用C制作一个helloworld程序，了解到了循环、条件语句；排序、递归、贪心、模拟算法。&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://cf.s3.soulter.top/20230723013138636/20230723032240377.png&#34; alt=&#34;&#34; /&gt;&lt;br /&gt;
Mar 19, 2017&lt;/p&gt;
&lt;p&gt;还记得当时老师教我们如何写死循环，然后终端上无限打印出字符串时我们的喜悦，还记得在了解C4droid之后，制作了一个无限创建进程的手机软件，然后骄傲地发到班群里面以为同学会中招然后佩服得五体投地，结果被QQ检测到病毒禁止下载后的沮丧，还记得第一次做noip的递归题，在草稿纸上花了一大张调用栈，最后解出题的轻松…为此，我们还专门成立了一个组织——梦倾天下，专门讨论编程和科技有关的话题。然后经常写一些当时觉得很厉害的代码，往群里发。&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://cf.s3.soulter.top/20230723013138636/20230723032521960.png&#34; alt=&#34;&#34; /&gt;&lt;/p&gt;
&lt;p&gt;初二下学期末，老师给了我们一个任务：给一年后的自己写一段话，我是这样写的：&lt;br /&gt;
&lt;img src=&#34;https://cf.s3.soulter.top/20230723013138636/20230723032828503.png&#34; alt=&#34;&#34; /&gt;&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;当时的我，由于接触手机比较多，尤其对手机软件感兴趣。偶然间，认识了iapp这个软件，它是一个能在手机上通过使用“裕语言”来开发手机软件的一个APP。来了兴趣，疯狂折腾，搞出了各种聊天室、工具箱、浏览器等手机应用。&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://cf.s3.soulter.top/20230723013138636/20230723024957848.png&#34; alt=&#34;&#34; /&gt;&lt;/p&gt;
&lt;p&gt;更离谱的是，由于当时法律意识浅薄，我甚至做了一个手机病毒，可以引导别人通过短信的方式发送账密到我的手机号上。也不知怎么传播的，真有人把账密发到了我手机上！将近10个人！（不过纯真的我并没有登录别人的账号，被吓尿了qaq）&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://cf.s3.soulter.top/20230723013138636/20230723025727373.png&#34; alt=&#34;&#34; /&gt;&lt;/p&gt;
&lt;p&gt;慢慢的，我了解到开发手机软件不是这么开发的！是要用一种叫Java的语言。我开始使用AIDE（一个手机的java ide），不过没用多长就被这蹩脚的操作整放弃了（Java，尤其是Android SDK中那长长的方法名、变量名对手机用户很不友好QAQ）。误打误撞，看到了B站上的一个Google开放的Android公开课。&lt;br /&gt;
&lt;img src=&#34;https://cf.s3.soulter.top/20230723013138636/20230723030206928.png&#34; alt=&#34;&#34; /&gt;&lt;br /&gt;
当时正值初二升初三的暑假，于是我有充足的时间去学习。这时，我甚至完全没有学过Java这门语言。好在老师们非常幽默（图中也看得出来hahaha，这也是我对Google产生好感的开始），我非常喜欢这个老师以至于进阶教程换老师之后就有点不想看了。老师从Coffee这个主题来引入，从工具、视图再到逻辑，富有条理且生动地给我们讲了安卓开发。&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://cf.s3.soulter.top/20230723013138636/20230723030749966.png&#34; alt=&#34;&#34; /&gt;&lt;br /&gt;
第一个真正的任务是做一个类似于上图的&lt;code&gt;Just Java&lt;/code&gt;软件。我承认，从C的思维跳转到XML+JAVA混合开发的思维花了我不少的时间。刚开始，我甚至连public是啥都不知道。庆幸的是，除了一些所谓的模板代码之外，大体的逻辑都能够明白。之后就是OOP了，我又花了大量的时间去理解OOP，不过当时并没有完全摸透，只是给我留下了对OOP的一些刻板印象——匿名内部类！多文件！盒子封装！&lt;/p&gt;
&lt;p&gt;在学完基本的开发之后，我便迫不及待地想做一些自己想做的事，比如说——为自己家的饭店开发一个点餐软件。&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;待续（三点半了睡了睡了）&lt;/p&gt;
</content>
        <category term="2023" />
        <updated>2023-07-23T01:32:00.000Z</updated>
    </entry>
    <entry>
        <id>https://blog.soulter.top/posts/windows-terminal-auto.html</id>
        <title>实现自动化，仅需一键！- 基于Windows Terminal的捷径</title>
        <link rel="alternate" href="https://blog.soulter.top/posts/windows-terminal-auto.html"/>
        <content type="html">&lt;h1 id=&#34;起因&#34;&gt;&lt;a class=&#34;markdownIt-Anchor&#34; href=&#34;#起因&#34;&gt;&lt;/a&gt; 起因&lt;/h1&gt;
&lt;p&gt;在连接到服务器时，不想操作太麻烦，在网上搜到了可以配置免密登录：&lt;/p&gt;
&lt;p&gt;Windows下，使用&lt;/p&gt;
&lt;figure class=&#34;highlight bash&#34;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&#34;gutter&#34;&gt;&lt;pre&gt;&lt;span class=&#34;line&#34;&gt;1&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&#34;code&#34;&gt;&lt;pre&gt;&lt;span class=&#34;line&#34;&gt;ssh-keygen&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/figure&gt;
&lt;p&gt;生成公钥&lt;code&gt;id_rsa.pub&lt;/code&gt;和私钥&lt;code&gt;id_rsa&lt;/code&gt;，存放在&lt;code&gt;C:\Users\用户\.ssh&lt;/code&gt;中。&lt;/p&gt;
&lt;p&gt;进入Linux中的&lt;code&gt;.ssh&lt;/code&gt;目录下，将公钥信息追加在&lt;code&gt;authorized_keys&lt;/code&gt;中。&lt;/p&gt;
&lt;p&gt;然后就可以实现Windows免密登录了。&lt;/p&gt;
&lt;p&gt;但是，还是需要先输入&lt;code&gt;ssh xxx@xx.xx.xx.xx&lt;/code&gt;，非常不elegant。&lt;/p&gt;
&lt;p&gt;查了网上也没查到简化这一步的方法（现在想来，其实用一个bat或者sh脚本就能实现(雾- -)）。&lt;/p&gt;
&lt;h1 id=&#34;修改-windows-terminal-设置&#34;&gt;&lt;a class=&#34;markdownIt-Anchor&#34; href=&#34;#修改-windows-terminal-设置&#34;&gt;&lt;/a&gt; 修改 Windows Terminal 设置&lt;/h1&gt;
&lt;p&gt;翻了以下Windows Terminal的设置，发现左下角可以自行配置设置。&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://cf.s3.soulter.top/windows-terminal-auto/20230522022233611.png&#34; alt=&#34;&#34; /&gt;&lt;/p&gt;
&lt;p&gt;打开之后，就是一个json格式的设置信息(嗯，很有Microsoft的风格)&lt;/p&gt;
&lt;p&gt;分析了一下之后，发现可以通过更改这里面的配置文件来实现类似于这种的快捷指令：&lt;br /&gt;
&lt;img src=&#34;https://cf.s3.soulter.top/windows-terminal-auto/20230522022449529.png&#34; alt=&#34;&#34; /&gt;&lt;/p&gt;
&lt;p&gt;首先找到&lt;code&gt;profiles&lt;/code&gt;字段，这个字段下有一个名为&lt;code&gt;list&lt;/code&gt;的数组，存放的就是上面提到的快捷指令。可以看到，有Powershell, Git, CMD等常见的工具。&lt;/p&gt;
&lt;figure class=&#34;highlight plaintext&#34;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&#34;gutter&#34;&gt;&lt;pre&gt;&lt;span class=&#34;line&#34;&gt;1&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;2&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;3&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;4&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;5&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;6&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;7&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;8&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;9&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;10&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;11&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;12&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;13&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;14&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;15&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;16&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;17&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;18&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;19&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;20&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;21&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;22&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;23&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;24&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;25&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;26&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;27&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;28&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;29&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;30&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;31&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;32&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;33&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;34&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;35&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;36&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;37&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;38&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;39&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;40&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;41&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;42&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;43&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;44&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;45&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;46&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;47&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;48&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;49&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;50&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;51&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;52&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;53&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;54&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;55&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;56&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;57&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;58&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;59&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;60&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&#34;code&#34;&gt;&lt;pre&gt;&lt;span class=&#34;line&#34;&gt;&amp;quot;profiles&amp;quot;: &lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;    &amp;#123;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;        &amp;quot;defaults&amp;quot;: &lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;        &amp;#123;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;            &amp;quot;opacity&amp;quot;: 54,&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;            &amp;quot;useAcrylic&amp;quot;: true&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;        &amp;#125;,&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;        &amp;quot;list&amp;quot;: &lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;        [&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;            &amp;#123;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;                &amp;quot;commandline&amp;quot;: &amp;quot;%SystemRoot%\\System32\\WindowsPowerShell\\v1.0\\powershell.exe&amp;quot;,&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;                &amp;quot;guid&amp;quot;: &amp;quot;&amp;#123;61c54bbd-c2c6-5271-96e7-009a87ff44bf&amp;#125;&amp;quot;,&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;                &amp;quot;hidden&amp;quot;: false,&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;                &amp;quot;historySize&amp;quot;: 9999,&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;                &amp;quot;name&amp;quot;: &amp;quot;Windows PowerShell&amp;quot;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;            &amp;#125;,&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;            &amp;#123;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;                &amp;quot;commandline&amp;quot;: &amp;quot;%SystemRoot%\\System32\\cmd.exe&amp;quot;,&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;                &amp;quot;guid&amp;quot;: &amp;quot;&amp;#123;0caa0dad-35be-5f56-a8ff-afceeeaa6101&amp;#125;&amp;quot;,&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;                &amp;quot;hidden&amp;quot;: false,&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;                &amp;quot;name&amp;quot;: &amp;quot;\u547d\u4ee4\u63d0\u793a\u7b26&amp;quot;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;            &amp;#125;,&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;            &amp;#123;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;                &amp;quot;guid&amp;quot;: &amp;quot;&amp;#123;b453ae62-4e3d-5e58-b989-0a998ec441b8&amp;#125;&amp;quot;,&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;                &amp;quot;hidden&amp;quot;: false,&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;                &amp;quot;name&amp;quot;: &amp;quot;Azure Cloud Shell&amp;quot;,&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;                &amp;quot;source&amp;quot;: &amp;quot;Windows.Terminal.Azure&amp;quot;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;            &amp;#125;,&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;            &amp;#123;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;                &amp;quot;guid&amp;quot;: &amp;quot;&amp;#123;2ece5bfe-50ed-5f3a-ab87-5cd4baafed2b&amp;#125;&amp;quot;,&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;                &amp;quot;hidden&amp;quot;: false,&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;                &amp;quot;name&amp;quot;: &amp;quot;Git Bash&amp;quot;,&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;                &amp;quot;source&amp;quot;: &amp;quot;Git&amp;quot;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;            &amp;#125;,&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;            &amp;#123;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;                &amp;quot;guid&amp;quot;: &amp;quot;&amp;#123;07b52e3e-de2c-5db4-bd2d-ba144ed6c273&amp;#125;&amp;quot;,&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;                &amp;quot;hidden&amp;quot;: true,&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;                &amp;quot;name&amp;quot;: &amp;quot;Ubuntu-20.04&amp;quot;,&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;                &amp;quot;source&amp;quot;: &amp;quot;Windows.Terminal.Wsl&amp;quot;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;            &amp;#125;,&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;            &amp;#123;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;                &amp;quot;guid&amp;quot;: &amp;quot;&amp;#123;4dd1e689-b517-5f39-947d-78e8a8bdf958&amp;#125;&amp;quot;,&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;                &amp;quot;hidden&amp;quot;: false,&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;                &amp;quot;name&amp;quot;: &amp;quot;Ubuntu 20.04.6 LTS&amp;quot;,&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;                &amp;quot;source&amp;quot;: &amp;quot;CanonicalGroupLimited.Ubuntu20.04LTS_79rhkp1fndgsc&amp;quot;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;            &amp;#125;,&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;            &amp;#123;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;                &amp;quot;guid&amp;quot;: &amp;quot;&amp;#123;b354a6cc-8ad4-5c18-aa33-9e9c396d8937&amp;#125;&amp;quot;,&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;                &amp;quot;hidden&amp;quot;: false,&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;                &amp;quot;name&amp;quot;: &amp;quot;Developer Command Prompt for VS 2022&amp;quot;,&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;                &amp;quot;source&amp;quot;: &amp;quot;Windows.Terminal.VisualStudio&amp;quot;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;            &amp;#125;,&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;            &amp;#123;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;                &amp;quot;guid&amp;quot;: &amp;quot;&amp;#123;d2597fe3-a09a-5f6e-bcb8-687179944f24&amp;#125;&amp;quot;,&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;                &amp;quot;hidden&amp;quot;: false,&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;                &amp;quot;name&amp;quot;: &amp;quot;Developer PowerShell for VS 2022&amp;quot;,&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;                &amp;quot;source&amp;quot;: &amp;quot;Windows.Terminal.VisualStudio&amp;quot;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;            &amp;#125;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;        ]&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;&amp;#125;,&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/figure&gt;
&lt;p&gt;那怎么实现免输入ssh指令一键登录呢？&lt;/p&gt;
&lt;p&gt;简单，只需要在list数组那里加一个元素：&lt;/p&gt;
&lt;figure class=&#34;highlight plaintext&#34;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&#34;gutter&#34;&gt;&lt;pre&gt;&lt;span class=&#34;line&#34;&gt;1&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;2&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;3&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;4&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;5&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;6&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&#34;code&#34;&gt;&lt;pre&gt;&lt;span class=&#34;line&#34;&gt;&amp;#123;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;    &amp;quot;commandline&amp;quot;: &amp;quot;%SystemRoot%\\System32\\WindowsPowerShell\\v1.0\\powershell.exe ssh root@xx.xx.xx.xx&amp;quot;,&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;    &amp;quot;hidden&amp;quot;: false,&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;    &amp;quot;historySize&amp;quot;: 9999,&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;    &amp;quot;name&amp;quot;: &amp;quot;My Linux Server&amp;quot;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;&amp;#125;,&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/figure&gt;
&lt;p&gt;其中，&lt;code&gt;commandline&lt;/code&gt;内容的后面记得把&lt;code&gt;xx.xx.xx.xx&lt;/code&gt;改成你的服务器IP地址。被省略的&lt;code&gt;guid&lt;/code&gt;不需要加上。&lt;code&gt;name&lt;/code&gt;可以改成其它的名字。&lt;/p&gt;
&lt;p&gt;保存之后，重新打开&lt;code&gt;Windows Terminal&lt;/code&gt;，就可以在下拉列表那里看到&lt;code&gt;My Linux Server&lt;/code&gt;啦！&lt;/p&gt;
&lt;div align=&#34;center&#34;&gt;
&lt;img width=200 src=&#34;https://cf.s3.soulter.top/windows-terminal-auto/111.gif&#34;&gt;
&lt;/img&gt;
&lt;/div&gt;
&lt;p&gt;借此，我们可以简化&lt;strong&gt;大量重复的操作&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;比如，在发布这篇博客时，我就用了上述方法：&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://cf.s3.soulter.top/windows-terminal-auto/image.png&#34; alt=&#34;alt text&#34; /&gt;&lt;/p&gt;
</content>
        <category term="2023" />
        <updated>2023-05-22T14:15:00.000Z</updated>
    </entry>
    <entry>
        <id>https://blog.soulter.top/posts/2023-2-monthly-record.html</id>
        <title>2月 / 寒假有感</title>
        <link rel="alternate" href="https://blog.soulter.top/posts/2023-2-monthly-record.html"/>
        <content type="html">&lt;div class=&#34;hbe hbe-container&#34; id=&#34;hexo-blog-encrypt&#34; data-wpm=&#34;Oh, this is an invalid password. Check and try again, please.&#34; data-whm=&#34;OOPS, these decrypted content may changed, but you can still have a look.&#34;&gt;
  &lt;script id=&#34;hbeData&#34; type=&#34;hbeData&#34; data-hmacdigest=&#34;c401c1ff41db21b1cbec9082e93028ecf792ddee46320171b85d509625159dfb&#34;&gt;57a93903e798fb80b7d87b9f0d7fccf3e7054c3d96c59bf4acfe8d6c9b96f7fbb4f8e87f011c63769aee7df9a99283a7d8845f6ec6e49ed0b551df9b4523061bffa1fdacc78e3306e62a7620703dbb34ad0cb9d01eb8e60d6b45fa6def0c22a4ceb58f04f8f745bd415af660cc124376ae7aaad3779d0b67f52bb5966a713dfe157720496232648ddbd47923a009a5076ee422b43ad0c688e404a7393931fbf61099f2e5bf1819800bb9be581cb8b1f36fa661195dfe1fdd509000a3cf175c8fd33a5c7197eeac9872a395940a01a5bdae4362e2813000bc8e47b880af8aace58332205bb1f99c5bb2eba2f6ef09d75d4c1494bcc4bccfa329f89a42e4a5028c2e0cdbe39fbaabe06a95379f4397fb1f0a5b20b1ea818a3ca097b630eaac79d37e6a55bd86def1a2ed38b0b033c00d990639406f2b0bbb9678f061c4f33a2f078c69a75a0a9375d6bcc4b9af561d841b6484421f525bc0dc933a40a09b20bfefeb003798b70c4f06f7fbc96f26bef4d7054e93669ed8b635b38c888199b51768985b54f5ec70ec0cfe27e8f9212010b658f130c127fc02bce553eda45dc4a810662cde6709b05d7f94b4951237ce2e5a015916c78fb960767b53218ebb431452543ad96c22a0a4ac43f2861eebd9f6dde0185a52c65674aba35e22d2228f89366cd8adf8f8a6870e2c4f4c710a94a132bd7183f688b1b02490f5336b23337870583ff3f01138dbc6b94074ab981e06025c2d3ad5049cd4b33db9a29536a560a198a40c1e7100dc2943250d393d5e9fe97eda3a8014c0ad0cc26549670b2f6dbba75aae655d82d2964c611a2bfe1ff006e81a5581cdc8f45733744574da903204591f5822439afc1fec1c78421d25cdfc050fffabc68eaf2e41406b5f8923fdea166cb96a51cce318f264485ab62c87265dfae0cace8e24b4866f69354a4ffd4721a91de2a6b93de752ef76f989828a15cc4c7346eebcb9573dfd37ce27ef3868f0036c15664b4af91a17376eb3348f1c2e53dd5ca70281cce2c25a541546ea1adc0c0bc644cfccde5a77ffd912e9860ce710f6cee7e60304159c4defad573b7b7c43c7a9472508fa09b4e371d9f5faa7b24daed06d9cac2116e18f4b8b3e428acc240ef50915f274cfea89ff35b71b08df6fb576ec14d2a7ce86428bc7af5c1aa1f40ae2d0f257ddc2018a3507f82451698dea411486f227dafad07280829372fb340151b9b733f7b3fb2897db1a1fdae747bbf0138d4e0b9123a7c9fdecdd0e2429239a72076372af980cbb47605ceaf53f3cef662cfceb5933d1b2041a2eeccbc73600fa455538bc6104f596fd15e607c3c978e29a1141b1bb690ed9c60d4d2841d63f74a668e21c53442edd8a609cff9a49d1c899f3ea68270d35f77cf80fb3c1201af9d8b94262948d7c30a2c1d160a9fc3448e834aaa359ba214d7fa884cc9ef4b39bcac38533cb6939627163e2521af03a3a309e712ee9f59212ae7e0a46f36a33f756a1121e782e4f1bc360fbbfc63552fb3558fe5d245728d47837adfe26f1c35994a82d3263ddf7bdd2c7d198ec1e2949a8bf12fbd5b16d17b591a9af9973eb9f6a84f1ded25b80ba7b2e7b89591152f302a49548fc9f921e1aab81c12ec2192d77c6f5bf736870dfe5a7eee582144d6bf3334528a59ada30b1f04f1d3ed2a7d7cc1635019cb277a6f1cc4253ce452ee252b525bb8302472b4a938648f73411235b427d7555c9b1814306737c069ccef50418a803e9895cd38a3e7b45f687b4f4eb654694281a098d94157bff375f6e21ce87cfdb7047b561f3732c050568760fec0c22b2daec405d289f0217147b07ad351b9cfe27f4cbf857fb5995db1aa314a4965ca0ff7a984fac8022c1d0bb56591944ebdb8285a76448a025927ed4342b269e10928210c9688488544c00156665478f487f5569a7da949569e212004716d48f67f569f23a3bcf6a9b77800faa3c9ad8c8a3406bfc0bfeb7fa0ab76098df841208c7ae53efc164cec6c8e050b9236c2c75c65d013990b91e5ce5aaf1559489192ae211424b3919ef06f8e8f7ac3e0fd73ba4df592c90e241cd6364c7ee45cf76bf8556ffb60c6525234e4b70b38601deb3d83149a6be581bec423d000acd8825f6b1d7cb99b6233197a6cc417cadd0f5e21ec7a47555da97af37743d5e51a570435776ef0a27aeb457bd9a7bf8f036540548034bfe2b7ec8f79913072a185573235c7af7ef0113c037d6804dd8919f6e2f5d81e9d87f4e6cac645c1bb86bb5894d231d024ee62d16dd50ac9f59427fa310d9260d46bcf5b584ee58901631391b50c3b9978d242dfe84398472cfff476b439a7c95404ba052ef3b83f3a2d8e410ab28e90fcd57ca1b75ff9696922ee6dad42c7b9551b1d2b88c5eb7ca4a968fb16a9edfa77deee6a23321c3e6df1925d3f57917b5427a57a9c60c566c3fdad7e877b3cca2f3af973d9a0a7e880518083c64780e8f0195dff1d4a3ca52e7dd9081be8f16d6e5f780890cfa8323dfbe47ec9e0dcaa63a9b445e18b9758da172322165aaea97f297a7a04fb306adb5ffb56ceb6df4d31a52ebdd2f9a2dfc709ee7b02487d5d9cb0f0a18ebd2608d40ae25373ee3d9eff47b3975f2470fd5dfeead471213f79186e2c71c609072805091cf3ca5c1f2e439b21056121ffaaecdbfe01d5b971bb49aef1de18506e43440295bae12a7a8a821f0eabd6519ed51092ef7dc0f773f90be0f47efee46e1371eab02ad8e3d49fa4fb43b17b49a9d4f6408d9e3023289dcca71bab65567dceda66429dd794d8843e490ac7cb410e68511129e08d7aa4afda411d85632d2f8e3dba4acfc3fb995c0076d8915646ddc14fb589e52be697c501ada634586e458ddf9b08febc9e20a0513b207fb401c5ab86337fd6f9a5af0dba7164799bdd41b97bb11eb13818a3384eac8002ddf1dbe21dd7135a8cef11772f7152cf2c1c1a978ce667d0874b9989260212fc42c03f766b412dd136db45c5017a473ca67fe285a2461a8ed2afdeaf02c886ddc42b6b535d20ab5ca751f8dfedfd2e132bdb2dac598a0cc61ff9db159fc45701a15e97348291928a30addc1d345b3413850c7034defb9c4333a04bc312c0393a9ea080cfd307f0b51c2f6ae7626ebf4d380aae68dc2e92446dccee310791f1909c16004a30d65f2062be911607accaf6a3cbf5172f5dded12fc6644811eecb9cc97bc5594b3c46dcaae2fdd264275b28f60c6880a6a8985c3b69b46de3204782746746968acfd9f6a290b22909ccaf22126c33819d45a7461cc935fa17167728a885dd36534898f96f76c0bd23aac4a0f6e443ce36dc6b16ba49fc2611b11a221c3631e62f1cf322e9779bac3ff3181884849c4a497f64c3a0174867f21522d684e396cb5802f7284f495e1f0ac082fb7d5b3db9bb4b9aa09846376a60c55ca7ffcfffb349de0a985794634191fd87f43913a7985ca2aaa896ef39b99e5dd704e87ab7a68e3d86171b67fbc6ef1cb574f45df642c5f5a8a86897b76e85e020097822b86ce0da6fc4d6f936586b6081222a9b09da84f73587b80d5768137ab20d6ab3b93af91b56d3a920c32ea9977652e306c04bc09eabefb1d57351a216270ced2f90b99ab1bb10ce86ec234fab3ae74b186ffed6bf7519ea259183b2d8d05a3e257b3eb72c681116d94ba2cb3e7ee556415aeb02125fd09b82fad7db7fabb16387240cf632e993f7bcfcc8ae7fdf62c978fbff96a0613c332ed20022913711fcb10fc7337a2298c8993d2273adbf7df7c8073294f37748cbd3ddd9cdeb6895854&lt;/script&gt;
  &lt;div class=&#34;hbe hbe-content&#34;&gt;
    &lt;div class=&#34;hbe hbe-input hbe-input-default&#34;&gt;
      &lt;input class=&#34;hbe hbe-input-field hbe-input-field-default&#34; type=&#34;password&#34; id=&#34;hbePass&#34;&gt;
      &lt;label class=&#34;hbe hbe-input-label hbe-input-label-default&#34; for=&#34;hbePass&#34;&gt;
        &lt;span class=&#34;hbe hbe-input-label-content hbe-input-label-content-default&#34;&gt;已被作者加密。&lt;/span&gt;
      &lt;/label&gt;
    &lt;/div&gt;
  &lt;/div&gt;
&lt;/div&gt;
&lt;script data-pjax src=&#34;/lib/hbe.js&#34;&gt;&lt;/script&gt;&lt;link href=&#34;/css/hbe.style.css&#34; rel=&#34;stylesheet&#34; type=&#34;text/css&#34;&gt;</content>
        <category term="2023" />
        <updated>2023-02-13T17:08:42.000Z</updated>
    </entry>
    <entry>
        <id>https://blog.soulter.top/posts/deploy-qq-llm-bot.html</id>
        <title>在 QQ 上部署 LLM 聊天机器人</title>
        <link rel="alternate" href="https://blog.soulter.top/posts/deploy-qq-llm-bot.html"/>
        <content type="html">&lt;blockquote&gt;
&lt;p&gt;⚠️ 提醒：本教程已彻底过时，请前往 &lt;a href=&#34;https://astrbot.soulter.top/&#34;&gt;https://astrbot.soulter.top/&lt;/a&gt; 。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;本文可以帮助你：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;获得一个OpenAI账户的Key（不再推荐）&lt;/li&gt;
&lt;li&gt;使用newbing模型&lt;/li&gt;
&lt;li&gt;使用逆向ChatGPT模型&lt;/li&gt;
&lt;li&gt;注册QQ机器人&lt;/li&gt;
&lt;li&gt;部署QQ频道GPT机器人&lt;/li&gt;
&lt;li&gt;部署QQ机器人&lt;/li&gt;
&lt;/ul&gt;
&lt;h1 id=&#34;一-前置条件&#34;&gt;&lt;a class=&#34;markdownIt-Anchor&#34; href=&#34;#一-前置条件&#34;&gt;&lt;/a&gt; 一、前置条件&lt;/h1&gt;
&lt;blockquote&gt;
&lt;p&gt;一台Linux/Windows云服务器或Windows电脑&lt;/p&gt;
&lt;p&gt;科学上网（用于获取OpenAI key）&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h1 id=&#34;二-步骤&#34;&gt;&lt;a class=&#34;markdownIt-Anchor&#34; href=&#34;#二-步骤&#34;&gt;&lt;/a&gt; 二、步骤&lt;/h1&gt;
&lt;h2 id=&#34;0-安装python和git&#34;&gt;&lt;a class=&#34;markdownIt-Anchor&#34; href=&#34;#0-安装python和git&#34;&gt;&lt;/a&gt; 0. 安装Python和Git&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;Python版本需要大于等于3.9&lt;/p&gt;
&lt;p&gt;在Windows下使用本项目时，都需要设置环境变量&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3 id=&#34;windows&#34;&gt;&lt;a class=&#34;markdownIt-Anchor&#34; href=&#34;#windows&#34;&gt;&lt;/a&gt; Windows&lt;/h3&gt;
&lt;p&gt;Windows如何设置环境变量？&lt;/p&gt;
&lt;p&gt;对于Python：安装时, 请务必勾选“Add Python to PATH”选项。&lt;/p&gt;
&lt;p&gt;对于Git：Git安装时, 请务必勾选“Use Git from the Windows Command Prompt”选项。&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://cf.s3.soulter.top/%E9%83%A8%E7%BD%B2QQ%E9%A2%91%E9%81%93GPT%E6%9C%BA%E5%99%A8%E4%BA%BA/20230410091732253.png&#34; alt=&#34;&#34; /&gt;&lt;/p&gt;
&lt;figure class=&#34;highlight plaintext&#34;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&#34;gutter&#34;&gt;&lt;pre&gt;&lt;span class=&#34;line&#34;&gt;1&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;2&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&#34;code&#34;&gt;&lt;pre&gt;&lt;span class=&#34;line&#34;&gt;Python下载地址: https://npm.taobao.org/mirrors/python/3.9.7/python-3.9.7-amd64.exe&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;Git下载地址: https://registry.npmmirror.com/-/binary/git-for-windows/v2.39.2.windows.1/Git-2.39.2-64-bit.exe&amp;quot;&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/figure&gt;
&lt;h3 id=&#34;linux&#34;&gt;&lt;a class=&#34;markdownIt-Anchor&#34; href=&#34;#linux&#34;&gt;&lt;/a&gt; Linux&lt;/h3&gt;
&lt;p&gt;如果没有安装git和python，请自行上网搜索如何安装。注意Python版本需要大于等于3.9&lt;/p&gt;
&lt;h2 id=&#34;1获取项目&#34;&gt;&lt;a class=&#34;markdownIt-Anchor&#34; href=&#34;#1获取项目&#34;&gt;&lt;/a&gt; 1.获取项目&lt;/h2&gt;
&lt;p&gt;本项目有Windows端的一键安装器，如果你使用Windows部署本项目，那么请直接看下文的Windows部分。&lt;/p&gt;
&lt;h3 id=&#34;linux-2&#34;&gt;&lt;a class=&#34;markdownIt-Anchor&#34; href=&#34;#linux-2&#34;&gt;&lt;/a&gt; LINUX&lt;/h3&gt;
&lt;p&gt;本教程使用开源项目QQChannelChatGPT，项目地址为&lt;a href=&#34;https://github.com/Soulter/QQChannelChatGPT&#34;&gt;https://github.com/Soulter/QQChannelChatGPT&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;在Linux终端上输入以下命令：&lt;/p&gt;
&lt;figure class=&#34;highlight plaintext&#34;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&#34;gutter&#34;&gt;&lt;pre&gt;&lt;span class=&#34;line&#34;&gt;1&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;2&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&#34;code&#34;&gt;&lt;pre&gt;&lt;span class=&#34;line&#34;&gt;mkdir qqchangpt&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;git clone https://github.com/Soulter/QQChannelChatGPT.git qqchangpt&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/figure&gt;
&lt;h3 id=&#34;windows-2&#34;&gt;&lt;a class=&#34;markdownIt-Anchor&#34; href=&#34;#windows-2&#34;&gt;&lt;/a&gt; Windows&lt;/h3&gt;
&lt;p&gt;前往页面&lt;a href=&#34;https://github.com/Soulter/QQChatGPTLauncher/releases&#34;&gt;https://github.com/Soulter/QQChatGPTLauncher/releases&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;下载最新版本的安装器然后运行，会自动拉取项目。&lt;/p&gt;
&lt;h2 id=&#34;2-获取语言模型如chatgptnewbing&#34;&gt;&lt;a class=&#34;markdownIt-Anchor&#34; href=&#34;#2-获取语言模型如chatgptnewbing&#34;&gt;&lt;/a&gt; 2. 获取语言模型，如ChatGPT,NewBing&lt;/h2&gt;
&lt;p&gt;只要有一个方式可以使用就行。当然全部都可以使用也行，机器人运行成功后可以自由切换语言模型。&lt;/p&gt;
&lt;h3 id=&#34;方式一申请openai-key&#34;&gt;&lt;a class=&#34;markdownIt-Anchor&#34; href=&#34;#方式一申请openai-key&#34;&gt;&lt;/a&gt; 方式一：申请OpenAI Key&lt;/h3&gt;
&lt;p&gt;前往此处注册OpenAI账号：&lt;a href=&#34;https://beta.openai.com/signup&#34;&gt;https://beta.openai.com/signup&lt;/a&gt;&lt;br /&gt;
邮箱使用临时邮箱，&lt;a href=&#34;https://www.emailnator.com/&#34;&gt;https://www.emailnator.com/&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;也可以点击输入框下面的的&lt;code&gt;Continue with Google&lt;/code&gt;（前提是你有谷歌账号）&lt;/p&gt;
&lt;p&gt;邮箱验证成功之后，会让你输入电话号码，注意，中国(+86)的电话号码全部不能注册，需要花钱去申请一个虚拟手机号，这里给出几个虚拟手机号申请网站&lt;/p&gt;
&lt;blockquote&gt;
&lt;ol&gt;
&lt;li&gt;&lt;a href=&#34;https://onlinesim.io/v2/numbers/&#34;&gt;https://onlinesim.io/v2/numbers/&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://sms-activate.org/cn/getNumber&#34;&gt;https://sms-activate.org/cn/getNumber&lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;如果觉得麻烦可以使用逆向库模型：完全免费&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;将买好的手机号输入到OpenAI的注册页面上，发送SMS验证码，等一会就可以在上面的网站看到验证码。&lt;/p&gt;
&lt;p&gt;注册成功后，点击右上角的头像，找到“API Keys”部分，并点击“Create API Key”按钮。&lt;/p&gt;
&lt;p&gt;然后就会生成一个独一无二的Key，记得点击右边的复制~&lt;/p&gt;
&lt;p&gt;至此，你获得了使用OpenAI GPT等模型的api的密钥。&lt;/p&gt;
&lt;p&gt;到Key之后，回到第一步中解压后得到的项目文件夹，找到项目的configs目录，编辑此目录下的&lt;code&gt;config.yaml&lt;/code&gt;文件&lt;br /&gt;
将key填到此处：&lt;br /&gt;
&lt;img src=&#34;https://cf.s3.soulter.top/%E9%83%A8%E7%BD%B2QQ%E9%A2%91%E9%81%93GPT%E6%9C%BA%E5%99%A8%E4%BA%BA/20230126121035369.png&#34; alt=&#34;&#34; /&gt;&lt;br /&gt;
注意！格式要&lt;strong&gt;严格按照&lt;/strong&gt;上图的示例。当然也可以使用**/key**指令。&lt;/p&gt;
&lt;h3 id=&#34;方式2使用new-bing&#34;&gt;&lt;a class=&#34;markdownIt-Anchor&#34; href=&#34;#方式2使用new-bing&#34;&gt;&lt;/a&gt; 方式2：使用New Bing&lt;/h3&gt;
&lt;blockquote&gt;
&lt;p&gt;前提是你有newbing的账号&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;使用梯子打开: &lt;code&gt;https://www.bing.com&lt;/code&gt;&lt;br /&gt;
然后使用浏览器插件：&lt;br /&gt;
&lt;img src=&#34;https://cf.s3.soulter.top/%E9%83%A8%E7%BD%B2QQ%E9%A2%91%E9%81%93GPT%E6%9C%BA%E5%99%A8%E4%BA%BA/20230405110433179.png&#34; alt=&#34;&#34; /&gt;&lt;/p&gt;
&lt;p&gt;导出cookies，然后在项目根目录下创建cookies.json，打开，粘贴内容进去。&lt;/p&gt;
&lt;p&gt;然后在配置文件&lt;code&gt;config.yaml&lt;/code&gt;中启用bing能力。&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://cf.s3.soulter.top/%E9%83%A8%E7%BD%B2QQ%E9%A2%91%E9%81%93GPT%E6%9C%BA%E5%99%A8%E4%BA%BA/20230410094728310.png&#34; alt=&#34;&#34; /&gt;&lt;/p&gt;
&lt;h3 id=&#34;方式3使用逆向chatgpt库&#34;&gt;&lt;a class=&#34;markdownIt-Anchor&#34; href=&#34;#方式3使用逆向chatgpt库&#34;&gt;&lt;/a&gt; 方式3：使用逆向ChatGPT库&lt;/h3&gt;
&lt;blockquote&gt;
&lt;p&gt;前提是你有openai的账号&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;登录之后，打开&lt;a href=&#34;https://chat.openai.com/api/auth/session&#34;&gt;https://chat.openai.com/api/auth/session&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;复制access_token一段&lt;/p&gt;
&lt;p&gt;在配置文件中这样填写：&lt;br /&gt;
&lt;img src=&#34;https://cf.s3.soulter.top/%E9%83%A8%E7%BD%B2QQ%E9%A2%91%E9%81%93GPT%E6%9C%BA%E5%99%A8%E4%BA%BA/20230410095138074.png&#34; alt=&#34;&#34; /&gt;&lt;/p&gt;
&lt;p&gt;即可！&lt;/p&gt;
&lt;h2 id=&#34;部署&#34;&gt;&lt;a class=&#34;markdownIt-Anchor&#34; href=&#34;#部署&#34;&gt;&lt;/a&gt; 部署&lt;/h2&gt;
&lt;p&gt;现在支持部署到QQ频道和QQ。&lt;strong&gt;一次部署，同时使用。&lt;/strong&gt;&lt;/p&gt;
&lt;h3 id=&#34;方式1部署到qq频道&#34;&gt;&lt;a class=&#34;markdownIt-Anchor&#34; href=&#34;#方式1部署到qq频道&#34;&gt;&lt;/a&gt; 方式1：部署到QQ频道&lt;/h3&gt;
&lt;p&gt;前往QQ官方开放平台：&lt;a href=&#34;https://q.qq.com/&#34;&gt;https://q.qq.com/&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;注册账号。可能需要人脸识别认证~&lt;/p&gt;
&lt;p&gt;注册完毕后，点击创建机器人。如果这个按钮是灰色的，请刷新几下页面，如果还不行，可能就是认证出问题了，可以找官方解决。&lt;br /&gt;
&lt;img src=&#34;https://cf.s3.soulter.top/%E9%83%A8%E7%BD%B2QQ%E9%A2%91%E9%81%93GPT%E6%9C%BA%E5%99%A8%E4%BA%BA/20230126121352304.png&#34; alt=&#34;&#34; /&gt;&lt;br /&gt;
填写相关机器人资料，然后创建。&lt;/p&gt;
&lt;p&gt;创建成功后，点击开发设置，然后将&lt;code&gt;BotAppID&lt;/code&gt;和&lt;code&gt;机器人令牌&lt;/code&gt;复制下来&lt;br /&gt;
&lt;img src=&#34;https://cf.s3.soulter.top/%E9%83%A8%E7%BD%B2QQ%E9%A2%91%E9%81%93GPT%E6%9C%BA%E5%99%A8%E4%BA%BA/20230126121633938.png&#34; alt=&#34;&#34; /&gt;&lt;/p&gt;
&lt;p&gt;分别放在&lt;code&gt;config.yaml&lt;/code&gt;文件的appid、token处：&lt;br /&gt;
&lt;img src=&#34;https://cf.s3.soulter.top/%E9%83%A8%E7%BD%B2QQ%E9%A2%91%E9%81%93GPT%E6%9C%BA%E5%99%A8%E4%BA%BA/20230126121800391.png&#34; alt=&#34;&#34; /&gt;&lt;/p&gt;
&lt;h4 id=&#34;添加机器人到频道&#34;&gt;&lt;a class=&#34;markdownIt-Anchor&#34; href=&#34;#添加机器人到频道&#34;&gt;&lt;/a&gt; 添加机器人到频道&lt;/h4&gt;
&lt;h5 id=&#34;频道人数20人&#34;&gt;&lt;a class=&#34;markdownIt-Anchor&#34; href=&#34;#频道人数20人&#34;&gt;&lt;/a&gt; 频道人数&amp;lt;=20人&lt;/h5&gt;
&lt;p&gt;如果你的频道&amp;lt;=20人，那么恭喜你，添加会很方便，只需要在QQ开放平台将沙箱频道设为你的频道，然后在你的手机上切换到你的频道，在右上角的设置按钮那里添加机器人即可。不需要经过上线、审核等的流程。&lt;/p&gt;
&lt;h5 id=&#34;频道人数20人-2&#34;&gt;&lt;a class=&#34;markdownIt-Anchor&#34; href=&#34;#频道人数20人-2&#34;&gt;&lt;/a&gt; 频道人数&amp;gt;20人&lt;/h5&gt;
&lt;p&gt;如果你的频道&amp;gt;20人，需要上线、提审机器人，不过只需要撰写测试报告，且测试报告官方给了模板，你只需要在模板上按照格式填写相关信息即可。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;注意：测试报告最好不要有ChatGPT、智能聊天等字眼。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3 id=&#34;方式2部署到qq&#34;&gt;&lt;a class=&#34;markdownIt-Anchor&#34; href=&#34;#方式2部署到qq&#34;&gt;&lt;/a&gt; 方式2：部署到QQ&lt;/h3&gt;
&lt;p&gt;需要安装GO-CQHTTP配合使用。&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;安装教程：&lt;a href=&#34;https://docs.go-cqhttp.org/guide/quick_start.html#%E5%9F%BA%E7%A1%80%E6%95%99%E7%A8%8B&#34;&gt;https://docs.go-cqhttp.org/guide/quick_start.html#基础教程&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;在安装的时候，会让你选择用什么方式，如HTTP\WS等，请随便选。&lt;/li&gt;
&lt;li&gt;安装成功后，目录下会生成一个config.yaml文件，请打开并编辑此文件，&lt;strong&gt;将QQ和QQ密码输入到对应地方&lt;/strong&gt;，然后：&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;strong&gt;请将go-cqhttp的配置文件最后面的sever部分粘贴为以下内容并去除注释，否则无法使用。&lt;/strong&gt;&lt;/p&gt;
&lt;figure class=&#34;highlight yaml&#34;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&#34;gutter&#34;&gt;&lt;pre&gt;&lt;span class=&#34;line&#34;&gt;1&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;2&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;3&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;4&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;5&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;6&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;7&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;8&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;9&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;10&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&#34;code&#34;&gt;&lt;pre&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;attr&#34;&gt;servers:&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;  &lt;span class=&#34;bullet&#34;&gt;-&lt;/span&gt; &lt;span class=&#34;attr&#34;&gt;http:&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;      &lt;span class=&#34;attr&#34;&gt;host:&lt;/span&gt; &lt;span class=&#34;number&#34;&gt;127.0&lt;/span&gt;&lt;span class=&#34;number&#34;&gt;.0&lt;/span&gt;&lt;span class=&#34;number&#34;&gt;.1&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;      &lt;span class=&#34;attr&#34;&gt;version:&lt;/span&gt; &lt;span class=&#34;number&#34;&gt;0&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;      &lt;span class=&#34;attr&#34;&gt;port:&lt;/span&gt; &lt;span class=&#34;number&#34;&gt;5700&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;      &lt;span class=&#34;attr&#34;&gt;timeout:&lt;/span&gt; &lt;span class=&#34;number&#34;&gt;5&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;  &lt;span class=&#34;bullet&#34;&gt;-&lt;/span&gt; &lt;span class=&#34;attr&#34;&gt;ws:&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;      &lt;span class=&#34;attr&#34;&gt;address:&lt;/span&gt; &lt;span class=&#34;number&#34;&gt;127.0&lt;/span&gt;&lt;span class=&#34;number&#34;&gt;.0&lt;/span&gt;&lt;span class=&#34;number&#34;&gt;.1&lt;/span&gt;&lt;span class=&#34;string&#34;&gt;:6700&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;      &lt;span class=&#34;attr&#34;&gt;middlewares:&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;        &lt;span class=&#34;string&#34;&gt;&amp;lt;&amp;lt;:&lt;/span&gt; &lt;span class=&#34;meta&#34;&gt;*default&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/figure&gt;
&lt;p&gt;填写完毕并保存后，请启动go-cqhttp，确保无误后，进入下一步。&lt;/p&gt;
&lt;h2 id=&#34;4-运行&#34;&gt;&lt;a class=&#34;markdownIt-Anchor&#34; href=&#34;#4-运行&#34;&gt;&lt;/a&gt; 4. 运行&lt;/h2&gt;
&lt;h3 id=&#34;linux-3&#34;&gt;&lt;a class=&#34;markdownIt-Anchor&#34; href=&#34;#linux-3&#34;&gt;&lt;/a&gt; Linux&lt;/h3&gt;
&lt;p&gt;cd到之前第一步创建的qqchangpt目录下，输入以下指令：&lt;/p&gt;
&lt;figure class=&#34;highlight plaintext&#34;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&#34;gutter&#34;&gt;&lt;pre&gt;&lt;span class=&#34;line&#34;&gt;1&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&#34;code&#34;&gt;&lt;pre&gt;&lt;span class=&#34;line&#34;&gt;python3 main.py&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/figure&gt;
&lt;blockquote&gt;
&lt;p&gt;如果映射了python3为python或者pip3为pip，那么只需要用对应的指令就行。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;即可。&lt;/p&gt;
&lt;h3 id=&#34;windows-3&#34;&gt;&lt;a class=&#34;markdownIt-Anchor&#34; href=&#34;#windows-3&#34;&gt;&lt;/a&gt; Windows&lt;/h3&gt;
&lt;p&gt;启动launcher.exe即可。&lt;/p&gt;
&lt;p&gt;有解决不了的问题请在下方评论区留言或者在QQ频道上讨论。&lt;br /&gt;
我的QQ：905617992&lt;/p&gt;
&lt;h1 id=&#34;help-me&#34;&gt;&lt;a class=&#34;markdownIt-Anchor&#34; href=&#34;#help-me&#34;&gt;&lt;/a&gt; Help Me&lt;/h1&gt;
&lt;p&gt;&lt;strong&gt;如果对你帮助，请Star项目：&lt;a href=&#34;https://github.com/Soulter/QQChannelChatGPT&#34;&gt;https://github.com/Soulter/QQChannelChatGPT&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;有问题请在&lt;a href=&#34;https://github.com/Soulter/QQChannelChatGPT&#34;&gt;https://github.com/Soulter/QQChannelChatGPT&lt;/a&gt;&lt;br /&gt;
提交issue&lt;/p&gt;
&lt;p&gt;做了好久，拉一波赞助QAQ&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://cf.s3.soulter.top/%E9%83%A8%E7%BD%B2QQ%E9%A2%91%E9%81%93GPT%E6%9C%BA%E5%99%A8%E4%BA%BA/20230219124347628.png&#34; alt=&#34;&#34; /&gt;&lt;br /&gt;
&lt;img src=&#34;https://soulter.top/helpme.jpg&#34; alt=&#34;Help Me&#34; /&gt;&lt;/p&gt;
</content>
        <category term="2023" />
        <category term="ChatGPT" />
        <category term="QQ机器人" />
        <updated>2023-01-26T11:31:20.000Z</updated>
    </entry>
    <entry>
        <id>https://blog.soulter.top/posts/vue-cors.html</id>
        <title>前后端交互时 Vue 不返回 Cookie</title>
        <link rel="alternate" href="https://blog.soulter.top/posts/vue-cors.html"/>
        <content type="html">&lt;p&gt;最近在帮学校某个部门开发一款资产管理系统时，前端出现不能返回Cookie的问题。这是跨域的问题。&lt;/p&gt;
&lt;h1 id=&#34;什么是同源和跨域&#34;&gt;&lt;a class=&#34;markdownIt-Anchor&#34; href=&#34;#什么是同源和跨域&#34;&gt;&lt;/a&gt; 什么是同源和跨域？&lt;/h1&gt;
&lt;p&gt;狭义的同源就是指，域名、协议、端口均为相同。&lt;br /&gt;
跨域，是指浏览器不能执行其他网站的脚本。它是由浏览器的同源策略造成的，是浏览器对JavaScript实施的安全限制。 这里说明一下，无法跨域是浏览器对于用户安全的考虑，如果自己写个没有同源策略的浏览器，完全不用考虑跨域问题了。&lt;/p&gt;
&lt;p&gt;同源策略限制了以下行为： Cookie、LocalStorage 和 IndexDB 无法读取，DOM 和 JS 对象无法获取，Ajax请求发送不出去&lt;/p&gt;
&lt;p&gt;允许跨域的标签&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;img&lt;/li&gt;
&lt;li&gt;link&lt;/li&gt;
&lt;li&gt;script&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src=&#34;https://cf.s3.soulter.top/vue-cors/20230412121820583.png&#34; alt=&#34;&#34; /&gt;&lt;/p&gt;
&lt;h1 id=&#34;解决方式&#34;&gt;&lt;a class=&#34;markdownIt-Anchor&#34; href=&#34;#解决方式&#34;&gt;&lt;/a&gt; 解决方式&lt;/h1&gt;
&lt;h2 id=&#34;jsonp&#34;&gt;&lt;a class=&#34;markdownIt-Anchor&#34; href=&#34;#jsonp&#34;&gt;&lt;/a&gt; jsonp&lt;/h2&gt;
&lt;p&gt;利用script允许跨域&lt;/p&gt;
&lt;h2 id=&#34;cors&#34;&gt;&lt;a class=&#34;markdownIt-Anchor&#34; href=&#34;#cors&#34;&gt;&lt;/a&gt; CORS&lt;/h2&gt;
&lt;p&gt;CORS（Cross-Origin Resource Sharing）,跨域资源共享&lt;/p&gt;
&lt;p&gt;当使用XMLHttpRequest发送请求时，如果浏览器发现违反了同源策略就会自动加上一个请求头 origin；&lt;/p&gt;
&lt;p&gt;后端在接受到请求后确定响应后会在 Response Headers 中加入一个属性 Access-Control-Allow-Origin；&lt;/p&gt;
&lt;p&gt;浏览器判断响应中的 Access-Control-Allow-Origin 值是否和当前的地址相同，匹配成功后才继续响应处理，否则报错&lt;/p&gt;
&lt;p&gt;缺点：忽略 cookie，浏览器版本有一定要求&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://cf.s3.soulter.top/vue-cors/20230412121018880.png&#34; alt=&#34;&#34; /&gt;&lt;/p&gt;
&lt;h2 id=&#34;proxy&#34;&gt;&lt;a class=&#34;markdownIt-Anchor&#34; href=&#34;#proxy&#34;&gt;&lt;/a&gt; Proxy&lt;/h2&gt;
&lt;h1 id=&#34;solution&#34;&gt;&lt;a class=&#34;markdownIt-Anchor&#34; href=&#34;#solution&#34;&gt;&lt;/a&gt; Solution&lt;/h1&gt;
&lt;p&gt;当时琢磨了很久，在网上查阅相关资料后，发现没有在axios加上这行配置：&lt;/p&gt;
&lt;figure class=&#34;highlight plaintext&#34;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&#34;gutter&#34;&gt;&lt;pre&gt;&lt;span class=&#34;line&#34;&gt;1&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&#34;code&#34;&gt;&lt;pre&gt;&lt;span class=&#34;line&#34;&gt;axios.defaults.withCredentials = true；&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/figure&gt;
&lt;p&gt;然而加上之后问题依旧没有解决。&lt;br /&gt;
看了一下F12中的Cookie，也没有对应的字段&lt;code&gt;satoken&lt;/code&gt;，然后我自定义了一个cookie，测试之后，仍然没有发送出去。&lt;/p&gt;
&lt;p&gt;最后研究了一下请求头和响应头发现，Host是localhost:8080，而请求的url不是localhost（前后端分离，当时后端运行在云服务器上），为跨域请求。因此Chomium没有保存Cookie。&lt;/p&gt;
&lt;p&gt;解决方案：&lt;br /&gt;
在vue.config.js中配置Proxy：&lt;/p&gt;
&lt;figure class=&#34;highlight plaintext&#34;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&#34;gutter&#34;&gt;&lt;pre&gt;&lt;span class=&#34;line&#34;&gt;1&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;2&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;3&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;4&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;5&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;6&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;7&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;8&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;9&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;10&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;11&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&#34;code&#34;&gt;&lt;pre&gt;&lt;span class=&#34;line&#34;&gt;proxy: &amp;#123;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;            &amp;#x27;/&amp;#x27;: &amp;#123;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;                target: &amp;#x27;http://[URL:Port]/&amp;#x27;, // 代理目标，这里的地址会代替axios中设置的baseURL&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;                changeOrigin: true, // 跨域时要用&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;                ws: false,&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;                pathRewrite: &amp;#123;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;                    &amp;#x27;^/&amp;#x27;: &amp;#x27;&amp;#x27;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;                &amp;#125;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;            &amp;#125;,&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;        &amp;#125;&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/figure&gt;
&lt;p&gt;注意，axios需要配置baseurl：&lt;/p&gt;
&lt;figure class=&#34;highlight plaintext&#34;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&#34;gutter&#34;&gt;&lt;pre&gt;&lt;span class=&#34;line&#34;&gt;1&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&#34;code&#34;&gt;&lt;pre&gt;&lt;span class=&#34;line&#34;&gt;axios.defaults.baseURL = &amp;quot;/[接口前缀]&amp;quot;&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/figure&gt;
&lt;p&gt;此时，axios的请求url就只用带[接口前缀]之后的信息了。&lt;/p&gt;
&lt;h1 id=&#34;参考链接&#34;&gt;&lt;a class=&#34;markdownIt-Anchor&#34; href=&#34;#参考链接&#34;&gt;&lt;/a&gt; 参考链接&lt;/h1&gt;
&lt;p&gt;&lt;a href=&#34;https://xz.aliyun.com/t/2745&#34;&gt;https://xz.aliyun.com/t/2745&lt;/a&gt;&lt;/p&gt;
</content>
        <category term="2022" />
        <updated>2022-09-24T21:26:42.000Z</updated>
    </entry>
    <entry>
        <id>https://blog.soulter.top/posts/%E6%90%AD%E5%BB%BA%E8%87%AA%E5%B7%B1%E7%9A%84Git%E4%BB%93%E5%BA%93.html</id>
        <title>搭建自己的 Git 仓库</title>
        <link rel="alternate" href="https://blog.soulter.top/posts/%E6%90%AD%E5%BB%BA%E8%87%AA%E5%B7%B1%E7%9A%84Git%E4%BB%93%E5%BA%93.html"/>
        <content type="html">&lt;p&gt;最近将博客从 Typecho 搬至 Hexo，考虑到国内访问 Github Page 那众所周知的速度，因此我决定在自己的小水管上建一个私有 git 仓库，然后将 hexo 文件同时 deliver 到两个仓库上。安全 + 速度，岂不美哉- v -&lt;/p&gt;
&lt;h1 id=&#34;环境&#34;&gt;&lt;a class=&#34;markdownIt-Anchor&#34; href=&#34;#环境&#34;&gt;&lt;/a&gt; 环境&lt;/h1&gt;
&lt;figure class=&#34;highlight plaintext&#34;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&#34;gutter&#34;&gt;&lt;pre&gt;&lt;span class=&#34;line&#34;&gt;1&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&#34;code&#34;&gt;&lt;pre&gt;&lt;span class=&#34;line&#34;&gt;CentOS&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/figure&gt;
&lt;hr /&gt;
&lt;h1 id=&#34;1-创建用户&#34;&gt;&lt;a class=&#34;markdownIt-Anchor&#34; href=&#34;#1-创建用户&#34;&gt;&lt;/a&gt; 1. 创建用户&lt;/h1&gt;
&lt;figure class=&#34;highlight plaintext&#34;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&#34;gutter&#34;&gt;&lt;pre&gt;&lt;span class=&#34;line&#34;&gt;1&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;2&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;3&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&#34;code&#34;&gt;&lt;pre&gt;&lt;span class=&#34;line&#34;&gt;sudo adduser git&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;sudo addpw git&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;# 然后输入两遍密码。&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/figure&gt;
&lt;hr /&gt;
&lt;h1 id=&#34;2-建立git仓库&#34;&gt;&lt;a class=&#34;markdownIt-Anchor&#34; href=&#34;#2-建立git仓库&#34;&gt;&lt;/a&gt; 2. 建立Git仓库&lt;/h1&gt;
&lt;figure class=&#34;highlight plaintext&#34;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&#34;gutter&#34;&gt;&lt;pre&gt;&lt;span class=&#34;line&#34;&gt;1&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;2&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;3&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;4&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;5&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;6&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;7&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;8&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&#34;code&#34;&gt;&lt;pre&gt;&lt;span class=&#34;line&#34;&gt;# 切换git用户&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;sudo su git&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;# 切换环境为/home/git内 &lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;cd&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;# 创建文件夹 &lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;mkdir soulterBlog&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;cd soulterBlog&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;sudo git init --bare myBlog.git&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/figure&gt;
&lt;p&gt;到这一步，可能会出现git用户使用sudo导致的错误，因为git用户无权使用sudo命令。&lt;br /&gt;
下面是解决方案↓&lt;/p&gt;
&lt;figure class=&#34;highlight plaintext&#34;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&#34;gutter&#34;&gt;&lt;pre&gt;&lt;span class=&#34;line&#34;&gt;1&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;2&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;3&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&#34;code&#34;&gt;&lt;pre&gt;&lt;span class=&#34;line&#34;&gt;su root&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;chmod u+w /etc/sudoers&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;vi /etc/sudoers&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/figure&gt;
&lt;p&gt;在文件末尾，添加&lt;/p&gt;
&lt;figure class=&#34;highlight plaintext&#34;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&#34;gutter&#34;&gt;&lt;pre&gt;&lt;span class=&#34;line&#34;&gt;1&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&#34;code&#34;&gt;&lt;pre&gt;&lt;span class=&#34;line&#34;&gt;git ALL=(ALL) ALL&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/figure&gt;
&lt;p&gt;保存退出，再&lt;/p&gt;
&lt;figure class=&#34;highlight plaintext&#34;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&#34;gutter&#34;&gt;&lt;pre&gt;&lt;span class=&#34;line&#34;&gt;1&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&#34;code&#34;&gt;&lt;pre&gt;&lt;span class=&#34;line&#34;&gt;sudo git init --bare myBlog.git&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/figure&gt;
&lt;p&gt;即可。&lt;/p&gt;
&lt;p&gt;至此创建了裸仓库。当push到这个仓库后，会得到一个pack文件，但是此时还看不到具体的工作文件。&lt;/p&gt;
&lt;figure class=&#34;highlight plaintext&#34;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&#34;gutter&#34;&gt;&lt;pre&gt;&lt;span class=&#34;line&#34;&gt;1&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;2&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;3&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;4&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&#34;code&#34;&gt;&lt;pre&gt;&lt;span class=&#34;line&#34;&gt;# 当前在服务器裸仓库内&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;cd hooks&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;# 创建打开post-receive&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;vim post-receive&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/figure&gt;
&lt;p&gt;输入&lt;/p&gt;
&lt;figure class=&#34;highlight plaintext&#34;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&#34;gutter&#34;&gt;&lt;pre&gt;&lt;span class=&#34;line&#34;&gt;1&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;2&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&#34;code&#34;&gt;&lt;pre&gt;&lt;span class=&#34;line&#34;&gt;#!/bin/sh&lt;/span&gt;&lt;br&gt;&lt;span class=&#34;line&#34;&gt;git --work-tree=[工作空间地址] --git-dir=[git仓库地址] checkout -f&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/figure&gt;
&lt;p&gt;保存退出。&lt;/p&gt;
&lt;p&gt;再push之后，理论上就可以在工作空间地址看到push上来的文件了。&lt;br /&gt;
然鹅，push时也有可能报错…&lt;br /&gt;
such as：&lt;/p&gt;
&lt;figure class=&#34;highlight plaintext&#34;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&#34;gutter&#34;&gt;&lt;pre&gt;&lt;span class=&#34;line&#34;&gt;1&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&#34;code&#34;&gt;&lt;pre&gt;&lt;span class=&#34;line&#34;&gt;......failed to unpack&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/figure&gt;
&lt;p&gt;很大原因是git目录或者工作空间目录没有足够权限。挂一个777权限就行。&lt;/p&gt;
</content>
        <category term="2022" />
        <updated>2022-08-29T00:58:05.000Z</updated>
    </entry>
    <entry>
        <id>https://blog.soulter.top/posts/anime-comment-violet-2.html</id>
        <title>漫谈 / 紫罗兰永恒花园 二</title>
        <link rel="alternate" href="https://blog.soulter.top/posts/anime-comment-violet-2.html"/>
        <content type="html">&lt;blockquote&gt;
&lt;p&gt;本文带有剧透内容。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;经过大一最后一场考试周的最后一场考试后，打开了 《紫罗兰永恒花园 剧场版》。&lt;br /&gt;
不由得感叹，京阿尼在这部剧中注入了多少的精力和情感。&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;整部剧采用的是双线并进的叙述方式。开头便是薇尔莉特后三代的时代。在女孩（姑且叫“寻迹者”）的祖母——也是第一季中委托薇尔莉特代笔了五十篇信的母亲的女儿——的葬礼上，寻迹者了解到了过去的时代盛行书信文化，且由于文化水平不高，人们经常委托自动手记人偶帮忙代笔。也许是被自己的祖母的五十篇信的事情所感动，寻迹者也开始了寻找那个时代文化的旅程。&lt;br /&gt;
&lt;img src=&#34;https://soulter.top/usr/uploads/2022/07/4183456467.png#vwid=1916&amp;amp;vhei=798&#34; alt=&#34;23.png&#34; /&gt;&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;第一季故事内容结束之后若干年。&lt;/p&gt;
&lt;p&gt;镜头转向了CH邮政公司，从公司职员的谈话间可以了解到，当时镇上第一座信号塔即将竣工，也暗示了书信时代的即将逝去。&lt;/p&gt;
&lt;p&gt;薇尔莉特在假日接受了一份特殊的委托。&lt;br /&gt;
那是一个病重的小男孩尤里斯，他希望在自己还能在人世之前给操心自己的父母和弟弟送上一封自己亲手写的信，希望给在世的父母活下去的动力。&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://cf.s3.soulter.top/anime-comment-violet-2/image-1.png&#34; alt=&#34;&#34; /&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://cf.s3.soulter.top/anime-comment-violet-2/image-2.png&#34; alt=&#34;&#34; /&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;“薇尔莉特，你的手真冰…等到了冬天，我的身体应该会和你的手一样冰冰的吧。”&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;这也凸显出了书信相比于现代化的即时通讯讯息最大的优点——承载的情感更多。拿到对方精心挑选的信封、邮票、信纸，看到对方亲笔写下的字迹，个中透露出来的情感一定比冰冷冷的电码、字节好很多吧。&lt;/p&gt;
&lt;p&gt;偶然之间，薇尔莉特得知了心中的少佐基尔伯特没死。&lt;/p&gt;
&lt;p&gt;在听到了自己心中的支柱、第一个认可自己、教会自己说话、带自己认识世间美好的人，自己苦苦寻觅的人还活着时，薇尔莉特的内心情感可想而知。&lt;/p&gt;
&lt;h2 id=&#34;&#34;&gt;&lt;a class=&#34;markdownIt-Anchor&#34; href=&#34;#&#34;&gt;&lt;/a&gt; &lt;img src=&#34;https://cf.s3.soulter.top/anime-comment-violet-2/image-3.png&#34; alt=&#34;&#34; /&gt;&lt;/h2&gt;
&lt;p&gt;怀着无比激动的情绪，薇尔莉特和社长来到了距离莱顿三天车程的基尔伯特所在的卡尔特岛。随后二人了解到基尔伯特在岛上成为了一名乡村教师。事情到这里没有结束（进度条只刚过半= =），基尔伯特强烈地在过去的自己与现在的自己之间划分了界限，并表示不希望再见到二人。随后薇尔莉特的哭诉也没有让基尔伯特为他们开门。&lt;/p&gt;
&lt;p&gt;当是看到这，我自己是生气得不行。为什么不愿意去见薇尔莉特？更何况屋外还下着大雨啊。&lt;/p&gt;
&lt;p&gt;现在想想，基尔伯特确有不对之处，但是也在于PTSD。看着自己心爱的人因为自己而弄断了双手、遭遇了惨绝人寰一刻，也许会很内疚吧。&lt;/p&gt;
</content>
        <category term="2022" />
        <category term="动漫" />
        <category term="紫罗兰永恒花园" />
        <updated>2022-07-02T23:23:00.000Z</updated>
    </entry>
    <entry>
        <id>https://blog.soulter.top/posts/anime-comment-violet-1.html</id>
        <title>漫谈 / 紫罗兰永恒花园 一</title>
        <link rel="alternate" href="https://blog.soulter.top/posts/anime-comment-violet-1.html"/>
        <content type="html">&lt;iframe frameborder=&#34;no&#34; border=&#34;0&#34; marginwidth=&#34;0&#34; marginheight=&#34;0&#34; width=330 height=86 src=&#34;//music.163.com/outchain/player?type=2&amp;id=534065323&amp;auto=0&amp;height=66&#34;&gt;&lt;/iframe&gt;
&lt;p&gt;记得前几年《紫罗兰永恒花园》刚上映之时，就有朋友向我强烈推荐起它。也许是当时更偏向看“黑暗向”等含有相对偏激内容的动漫，看了几分钟后已然提不起兴趣，之后也就一直搁置在旁。&lt;/p&gt;
&lt;p&gt;…&lt;/p&gt;
&lt;p&gt;几年后的今天，当我又重新打开了这部番剧，发现一切都不是自己曾经想的那样。&lt;br /&gt;
举目无亲的孤儿、没有名字、没接受过教育、被迫从军、在军营沦为只会服从命令的⌈工具⌋。&lt;br /&gt;
该绝望吗？不，她甚至连绝望都不知道是什么。&lt;br /&gt;
与少佐的相遇，便是她的人生转折点。&lt;/p&gt;
&lt;p&gt;少佐赋予了她名字、给了她教育，教她如何识字、看书，教她什么是快乐、什么是痛苦、什么是爱。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;“你将不再是道具，而是成为人如其名的人。”&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;又是一年年的战争。这场战争打断了她的双臂。当然，还有在战前给她买了人生中第一个礼物的少佐。&lt;/p&gt;
&lt;p&gt;再次睁开眼之时，绝望的她，怀着少佐没有死的心进入了少佐好友的公司，成为了一名⌈自动手记人偶⌋，其工作是帮助客户撰文、写信。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;“只要客人有意向，不论身在何处，都能上门服务。”&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;一次次说出这句自动手记人偶的口号后，她也一次次地尝到了人生、社会的百态。&lt;/p&gt;
&lt;p&gt;在帮助公主给异国的王子写信时，她了解到了原来爱也能突破距离；原来一把手把你带大的那个你已经厌恶的人，你在与她相别之时，是有多么的不舍；原来，得到多少，也会感到失去多少。公主终于满怀着对王子的爱情，离别了她一直敬爱的乳母。&lt;/p&gt;
&lt;p&gt;在帮助少年撰文时，她了解到了少年战时被双亲所抛弃，但是一直心存希望，因此一直待在父亲的工作地等待双亲。少年在与和自己经历相似但是远比自己经历还要惨重的少女的谈话中，知道了自己原来是多么的“固步自封”。最后，少年敞开了心扉。选择了离别这份遥不可及的亲情。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;“我决定了，我也要向你一样，跑遍整个大陆。”&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;在帮助作家打字时，她了解到了失去母亲，而后女儿也因血友病去世的作家，选择了将这份痛埋藏在内心的最深处。&lt;br /&gt;
不敢直面现实的作家，因为薇尔莉特偶然打起了女儿珍爱的伞而气急败坏。&lt;br /&gt;
不敢直面现实的作家，在薇尔莉特实现了女儿临终前的梦想之后，也慢慢敞开了心扉。&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://cf.s3.soulter.top/anime-comment-violet-1/image.png&#34; alt=&#34;&#34; /&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;“真希望你活着，把你养大。”&lt;br /&gt;
薇尔莉特成为了作家女儿的精神延续。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;在帮助一个女儿的病重的母亲写信时，由于要花上7天，女儿非常生气，因为她认为薇尔莉特抢走了她与母亲最后的时间。&lt;br /&gt;
在一天写信时，女儿看到母亲病发咳血的情形之后，终于忍不住了。&lt;br /&gt;
“你骗人，你根本就没有好转，如果今后就只剩我一个人了，就别写信了，多陪陪我啊。”&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://cf.s3.soulter.top/anime-comment-violet-1/image-1.png&#34; alt=&#34;&#34; /&gt;&lt;/p&gt;
&lt;p&gt;可她并不知道，母亲写信给的那个人，正是女儿自己。&lt;br /&gt;
临终前，母亲委托薇尔莉特将信按年份逐年送给女儿。&lt;br /&gt;
母亲去世之后，女儿从此，到老年，每年的生日都得到了一份母亲写给她的信。&lt;br /&gt;
女儿即使无法在当时理解为什么母亲不愿意多花一点时间在她身上，也终将在未来的50年内领悟这无法割舍的爱。&lt;/p&gt;
&lt;p&gt;多么爱女儿的母亲，多么伟大的母亲，用自己最后的一口气，完成了女儿的心愿。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;“心爱的人，永远守护着你。”&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr /&gt;
&lt;blockquote&gt;
&lt;p&gt;突然忆起在网易云看到的一段文字：&lt;br /&gt;
“瀑布的水逆流而上，&lt;br /&gt;
蒲公英种子从远处飘回，聚成伞的模样，&lt;br /&gt;
太阳从西边升起，落向东方。&lt;/p&gt;
&lt;p&gt;子弹退回枪膛，&lt;br /&gt;
运动员回到起跑线上，&lt;br /&gt;
我交回录取通知书，忘了十年寒窗。&lt;/p&gt;
&lt;p&gt;厨房里飘来饭菜的香，&lt;br /&gt;
你把我的卷子签好名字，&lt;br /&gt;
关掉电视，帮我把书包背上。&lt;/p&gt;
&lt;p&gt;你还在我身旁。​”&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr /&gt;
&lt;p&gt;当她偶然得知并在社长口中得到确认——自己心中的支柱——少佐已经被确认牺牲的消息之后。她开始变得沉沦、丧气。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;“要是少佐不在了，我还有什么意义活下去？”&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;面对同事嘉德丽雅的质问，社长的回答是“现在的薇尔莉特大概能接受这个事实了”。&lt;br /&gt;
是啊，经历了多少人间烟火、世间百态，薇尔莉特早就懂得了什么是爱、什么是孤独、什么是离别、离别意味着什么。&lt;br /&gt;
她早已练就接受离别的勇气。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;“我还爱着你，也知道你曾爱过我。”&lt;br /&gt;
“虽然不舍，但还是永别了。”&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;我们这一生，又会如同薇尔莉特一样经历多少次离别的场景呢？尽管大多数的我们确不如她的经历之惨重，但对我们而言，也是一时间之内的切肤之痛罢。与初中同学的离别、与高中同学的分别、与恋人的分别…&lt;br /&gt;
但，只要敢于正视、敢于面对，又有还会有什么痛呢？&lt;/p&gt;
&lt;p&gt;花无凋零之时，爱无传达之期，爱情亘古不变，紫罗兰永世长存。&lt;br /&gt;
你将不再是杀戮的道具，而是人如其名的人。&lt;/p&gt;
&lt;p&gt;文|Soulter 时|2022/6/21 22:22&lt;/p&gt;
</content>
        <category term="2022" />
        <category term="动漫" />
        <category term="紫罗兰永恒花园" />
        <updated>2022-06-21T19:06:00.000Z</updated>
    </entry>
    <entry>
        <id>https://blog.soulter.top/posts/awtrix.html</id>
        <title>Awtrix像素灯：让关心的信息显示在身边</title>
        <link rel="alternate" href="https://blog.soulter.top/posts/awtrix.html"/>
        <content type="html">&lt;p&gt;最近学习压力稍大，也正好打算装扮一下自己的桌面*（不是Windows的）*，无意中看到了 &lt;a href=&#34;https://awtrixdocs.blueforcer.de/#/en-en/&#34;&gt;Awtrix&lt;/a&gt; 这个项目，心潮澎湃，说干就干！&lt;/p&gt;
&lt;h1 id=&#34;成品展示&#34;&gt;&lt;a class=&#34;markdownIt-Anchor&#34; href=&#34;#成品展示&#34;&gt;&lt;/a&gt; 成品展示&lt;/h1&gt;
&lt;blockquote&gt;
&lt;p&gt;未经过柔光处理&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;img src=&#34;https://blog.soulter.top/usr/uploads/2022/04/1808455155.jpg#vwid=1080&amp;amp;vhei=576&#34; alt=&#34;qq_pic_merged_1649128893326.jpg&#34; /&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://blog.soulter.top/usr/uploads/2022/04/2676241875.jpg#vwid=1080&amp;amp;vhei=607&#34; alt=&#34;IMG20220405112222.jpg&#34; /&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://blog.soulter.top/usr/uploads/2022/04/720494994.jpg#vwid=1080&amp;amp;vhei=585&#34; alt=&#34;qq_pic_merged_1649128961142.jpg&#34; /&gt;&lt;/p&gt;
&lt;hr /&gt;
&lt;h1 id=&#34;需要什么&#34;&gt;&lt;a class=&#34;markdownIt-Anchor&#34; href=&#34;#需要什么&#34;&gt;&lt;/a&gt; 需要什么?&lt;/h1&gt;
&lt;h2 id=&#34;必需品&#34;&gt;&lt;a class=&#34;markdownIt-Anchor&#34; href=&#34;#必需品&#34;&gt;&lt;/a&gt; 必需品&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Awtrix 官方提供的pcb开发板&lt;/li&gt;
&lt;li&gt;Ws2812 灯带矩阵 32cm*8cm&lt;/li&gt;
&lt;li&gt;5v 数据线（手机的线就行）&lt;/li&gt;
&lt;li&gt;ESP8266 dpmini&lt;/li&gt;
&lt;li&gt;一台电脑&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&#34;可选&#34;&gt;&lt;a class=&#34;markdownIt-Anchor&#34; href=&#34;#可选&#34;&gt;&lt;/a&gt; 可选&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;一个 32*8cm 的可以装载上面的器件的盒子。&lt;/li&gt;
&lt;li&gt;灯栅格&lt;/li&gt;
&lt;li&gt;柔光纸&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;你可能觉得过于繁琐(当然我也觉得)，如果有条件，你可以去闲鱼上购买卖家帮你焊接好的 PCB 电路板和一些必须的元器件然后自己组装！很方便！我就是这么干的。不过强烈不建议购买&lt;strong&gt;成品&lt;/strong&gt;。这样就失去的 DIY 的价值。这种东西要自己做出来才有成就感嘛。&lt;/p&gt;
&lt;h1 id=&#34;开始&#34;&gt;&lt;a class=&#34;markdownIt-Anchor&#34; href=&#34;#开始&#34;&gt;&lt;/a&gt; 开始！&lt;/h1&gt;
&lt;h2 id=&#34;1-烧录程序&#34;&gt;&lt;a class=&#34;markdownIt-Anchor&#34; href=&#34;#1-烧录程序&#34;&gt;&lt;/a&gt; 1. 烧录程序&lt;/h2&gt;
&lt;p&gt;大概 5 分钟即可完成。&lt;br /&gt;
此时你手里应该有：&lt;strong&gt;ESP8266 dpmini版, 数据线&lt;/strong&gt;&lt;/p&gt;
&lt;h3 id=&#34;为什么要这一步&#34;&gt;&lt;a class=&#34;markdownIt-Anchor&#34; href=&#34;#为什么要这一步&#34;&gt;&lt;/a&gt; 为什么要这一步？&lt;/h3&gt;
&lt;p&gt;因为刚到手的 ESP8266 dpmini 版 里面还没有任何驱动程序， 他就只是一具【没有灵魂的空壳】。我们得装载程序进去。&lt;/p&gt;
&lt;p&gt;‎1.‎‎ ‎‎&lt;a href=&#34;https://blueforcer.de/downloads/ESP8266Flasher.exe&#34;&gt;在此处‎‎&lt;/a&gt;下载烧录用的工具‎&lt;/p&gt;
&lt;p&gt;‎2.‎‎ &lt;a href=&#34;https://blueforcer.de/awtrix/stable/firmware.bin&#34;&gt;‎‎在此处‎‎&lt;/a&gt;下载最新固件。‎&lt;/p&gt;
&lt;p&gt;‎3.‎‎ 启动&lt;strong&gt;第一步下载好的程序&lt;/strong&gt;‎‎并在 “Config” 选项卡中打开固件（单击齿轮选择固件）‎&lt;/p&gt;
&lt;ol start=&#34;4&#34;&gt;
&lt;li&gt;连接 ESP 8266 板子到电脑上&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;‎5.‎‎ 返回 “Operation” 选项卡，如果未自动检测到正确的 Com-Port，请设置正确的 Com-Port。‎&lt;/p&gt;
&lt;p&gt;‎‎6. 单击 “Flash” ，然后等待该过程完成，左下角出现一个绿色的标记。‎&lt;/p&gt;
&lt;p&gt;‎6.‎‎ 重新启动控制器。‎&lt;/p&gt;
&lt;p&gt;至此，你就已经装载好 Awtrix 要用的程序了！&lt;/p&gt;
&lt;h2 id=&#34;2-连接灯带&#34;&gt;&lt;a class=&#34;markdownIt-Anchor&#34; href=&#34;#2-连接灯带&#34;&gt;&lt;/a&gt; 2. 连接灯带！&lt;/h2&gt;
&lt;p&gt;买下来的灯带只需要用到中间的 Gnd，5V，data 那三个接口 将其接到 PCB 开发板对应的接口上（板子背面有提示）就行。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;注意，一定要接对，否则短路！！&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id=&#34;3-连接-esp8266&#34;&gt;&lt;a class=&#34;markdownIt-Anchor&#34; href=&#34;#3-连接-esp8266&#34;&gt;&lt;/a&gt; 3. 连接 ESP8266&lt;/h2&gt;
&lt;p&gt;将其接到 PCB 开发板对应的接口上（板子背面有提示）就行。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;注意，一定要接对，否则短路！！&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;此时，给板子连接 5V 电源，正常情况下会发现灯显示蓝色的 BOOT 字样，代表你已成功搭建。&lt;br /&gt;
之后会显示 HOTSPOT, 此时 ESP8266 会打开一个 WiFi 热点，你需要用手机连接到该热点，然后在弹出的网页中填写设置（设置需要填写服务器地址，因此建议先&lt;strong&gt;搭建服务器&lt;/strong&gt;）。&lt;/p&gt;
&lt;h2 id=&#34;4-服务器搭建&#34;&gt;&lt;a class=&#34;markdownIt-Anchor&#34; href=&#34;#4-服务器搭建&#34;&gt;&lt;/a&gt; 4. 服务器搭建&lt;/h2&gt;
&lt;p&gt;下载 &lt;a href=&#34;https://blueforcer.de/awtrix/stable/awtrix.jar&#34;&gt;Awtrix&lt;/a&gt; 程序到你的服务器上，&lt;br /&gt;
Linux &amp;amp; MacOS：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;sudo java -jar awtrix.jar
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Windows：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;java -jar awtrix.jar
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;如果你是 Linux，只需要运行这一段&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;wget -N https://blueforcer.de/awtrix/awtrix.sh ; sudo sh awtrix.sh
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;启动后不久，可以通过 &lt;code&gt;http://你的服务器IP地址:7000&lt;/code&gt; 访问 Awtrix Host 端。&lt;/p&gt;
&lt;h2 id=&#34;5-完成&#34;&gt;&lt;a class=&#34;markdownIt-Anchor&#34; href=&#34;#5-完成&#34;&gt;&lt;/a&gt; 5. 完成！&lt;/h2&gt;
&lt;p&gt;Enjoy it！&lt;/p&gt;
</content>
        <category term="2022" />
        <category term="DIY" />
        <category term="Awtrix" />
        <category term="物联网" />
        <updated>2022-04-05T11:08:00.000Z</updated>
    </entry>
    <entry>
        <id>https://blog.soulter.top/posts/%E5%A4%8F%E5%A4%A9%E3%80%81%E5%86%8D%E6%AC%A1%E5%90%AF%E7%A8%8B.html</id>
        <title>夏天、再次启程</title>
        <link rel="alternate" href="https://blog.soulter.top/posts/%E5%A4%8F%E5%A4%A9%E3%80%81%E5%86%8D%E6%AC%A1%E5%90%AF%E7%A8%8B.html"/>
        <content type="html">&lt;p&gt;前两个博客因为资金和时间的一些问题都挂掉了。&lt;br /&gt;
现在大学了，想着可以真正做一个稳定的博客了。&lt;br /&gt;
也许是永久的呢。&lt;/p&gt;
&lt;h1 id=&#34;first-blog-record&#34;&gt;&lt;a class=&#34;markdownIt-Anchor&#34; href=&#34;#first-blog-record&#34;&gt;&lt;/a&gt; First Blog Record&lt;/h1&gt;
&lt;p&gt;&lt;img src=&#34;http://101.43.189.33:1024/usr/uploads/2022/04/2297034680.jpg#vwid=1080&amp;amp;vhei=1920&#34; alt=&#34;get_thumbnail.jpg&#34; /&gt;&lt;/p&gt;
</content>
        <updated>2022-04-04T00:23:00.000Z</updated>
    </entry>
    <entry>
        <id>https://blog.soulter.top/posts/dark-net-experience.html</id>
        <title>去世界另一面逛了一圈</title>
        <link rel="alternate" href="https://blog.soulter.top/posts/dark-net-experience.html"/>
        <content type="html">&lt;hr /&gt;
&lt;h3 id=&#34;寒暄&#34;&gt;&lt;a class=&#34;markdownIt-Anchor&#34; href=&#34;#寒暄&#34;&gt;&lt;/a&gt; 寒暄&lt;/h3&gt;
&lt;p&gt;暗网这个东西有好有坏,好的一面呢除了隐蔽性之外,和普通网站就没区别了。可是坏的一面，能让你彻底改变世界观QAQ(发誓再也不进了&lt;/p&gt;
&lt;h3 id=&#34;android手机端进入方法&#34;&gt;&lt;a class=&#34;markdownIt-Anchor&#34; href=&#34;#android手机端进入方法&#34;&gt;&lt;/a&gt; Android手机端进入方法&lt;/h3&gt;
&lt;h4 id=&#34;工具&#34;&gt;&lt;a class=&#34;markdownIt-Anchor&#34; href=&#34;#工具&#34;&gt;&lt;/a&gt; 工具:&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;VPN
Orbot, 匿名工具
Orfox, 进暗网的浏览器，当然普通浏览器也行
&lt;/code&gt;&lt;/pre&gt;
&lt;h4 id=&#34;流程&#34;&gt;&lt;a class=&#34;markdownIt-Anchor&#34; href=&#34;#流程&#34;&gt;&lt;/a&gt; 流程:&lt;/h4&gt;
&lt;p&gt;首先先上梯子。嗯我用的是ss,推荐一下SnapVPN，以前没vps的时候就用的这个&lt;br /&gt;
然后打开Orbot。刚进去是纯英文的,点右上角的三个小点进入设置可以选择语言(不过选择了中文也没有反应啊!)&lt;br /&gt;
点击中间的圆形按钮启动匿名服务。&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://blog.soulter.top/usr/uploads/2022/04/2820878371.png#vwid=325&amp;amp;vhei=476&#34; alt=&#34;2022-04-04T02:09:44.png&#34; /&gt;&lt;/p&gt;
&lt;p&gt;最后打开Orfox,进入检查网页，它将检查你是否真正启动了Orbot&lt;br /&gt;
如果通过检查,你就可以进暗网了!&lt;br /&gt;
在这里推荐几个网址…还是算了大家自己去找吧!!&lt;br /&gt;
最后再说一句，这个不是正常人能够承受的地方，它完全就是人性的另一面，里面诸如drug, gun, 色情的东西无处不在。还是少上为好!(回归社会主义的拥抱_(:3)∠)_)&lt;/p&gt;
&lt;p&gt;如果出现了什么问题可以评论…&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;本文由Soulter创作，采用知识共享署名4.0国际许可。本站文章除注明转载/出处外,均为本站原创或翻译。&lt;br /&gt;
最后编辑时间为:Oct 4,2018 at 09:18 pm&lt;br /&gt;
⚠️此博文为归档博文，由Soulter自&lt;em&gt;&lt;strong&gt;Oct 4, 2018&lt;/strong&gt;&lt;/em&gt;发布, 为了纪念，作者不会对此博文进行任何修改。如有错误，将会在评论区具体说明。&lt;/p&gt;
&lt;h2 id=&#34;以下为博文残片&#34;&gt;&lt;a class=&#34;markdownIt-Anchor&#34; href=&#34;#以下为博文残片&#34;&gt;&lt;/a&gt; 以下为⌈博文残片⌋&lt;/h2&gt;
&lt;p&gt;&lt;img src=&#34;https://blog.soulter.top/usr/uploads/2022/04/2348188380.jpg#vwid=540&amp;amp;vhei=2965&#34; alt=&#34;-1914380539_1129040108(1).jpg&#34; /&gt;&lt;/p&gt;
</content>
        <updated>2018-10-04T09:18:00.000Z</updated>
    </entry>
</feed>
