<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>AixCoder (hongnian.liu)</title>
    <link>https://ruby-china.org/AixCoder</link>
    <description/>
    <language>en-us</language>
    <item>
      <title>11 英寸 MacBook 的第二春：从零开始配置 Ruby on Rails 开发环境</title>
      <description>&lt;h2 id="🌸 旧 MacBook 的第二春：从零配置 Ruby on Rails 开发环境"&gt;🌸 旧 MacBook 的第二春：从零配置 Ruby on Rails 开发环境&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;"最好的设备是你手里那台。"&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;img src="https://l.ruby-china.com/photo/AixCoder/e414fcae-f822-45b1-87bd-c387a696803c.png!large" title="" alt=""&gt;&lt;/p&gt;

&lt;p&gt;很多同学手里都有一台旧的 MacBook Air，可能是当年为了写论文买的。现在用起来有点卡，打开 App 要转圈圈，似乎只能沦为追剧专用机。&lt;/p&gt;

&lt;p&gt;但别急着放弃它。&lt;/p&gt;

&lt;p&gt;MacBook 的硬件做工一向扎实，老机器的问题往往不是性能不够，而是&lt;strong&gt;系统里堆积了太多垃圾，以及没有装对工具&lt;/strong&gt;。就像一间堆满杂物的房间，不是房子太小，而是需要重新整理。&lt;/p&gt;

&lt;p&gt;给这台旧电脑&lt;strong&gt;重装系统 → 安装 Homebrew → 配置 Ruby → 搭建 Rails&lt;/strong&gt;，它就能从追剧神器变身成一台趁手的&lt;strong&gt;编程学习机、写作工具，甚至是一台随身携带的代码终端&lt;/strong&gt;。&lt;/p&gt;

&lt;hr&gt;
&lt;h2 id="📑 目录"&gt;📑 目录&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="#%E5%AE%89%E8%A3%85%E5%89%8D%E7%9A%84%E5%87%86%E5%A4%87%E9%87%8D%E8%A3%85%E7%B3%BB%E7%BB%9F" title=""&gt;安装前的准备：重装系统&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#%E7%AC%AC%E4%B8%80%E6%AD%A5%E5%AE%89%E8%A3%85-homebrew" title=""&gt;第一步：安装 Homebrew&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#%E7%AC%AC%E4%BA%8C%E6%AD%A5%E5%AE%89%E8%A3%85-rbenv" title=""&gt;第二步：安装 rbenv&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#%E7%AC%AC%E4%B8%89%E6%AD%A5%E5%AE%89%E8%A3%85-ruby" title=""&gt;第三步：安装 Ruby&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#%E7%AC%AC%E5%9B%9B%E6%AD%A5%E5%AE%89%E8%A3%85-rails" title=""&gt;第四步：安装 Rails&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#%E7%AC%AC%E4%BA%94%E6%AD%A5%E5%88%9B%E5%BB%BA%E5%B9%B6%E5%90%AF%E5%8A%A8%E4%BD%A0%E7%9A%84%E7%AC%AC%E4%B8%80%E4%B8%AA%E9%A1%B9%E7%9B%AE" title=""&gt;第五步：创建并启动你的第一个项目&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#%E5%86%99%E5%9C%A8%E6%9C%80%E5%90%8E" title=""&gt;写在最后&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;hr&gt;
&lt;h2 id="安装前的准备：重装系统"&gt;安装前的准备：重装系统&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;强烈推荐先重装系统。&lt;/strong&gt; 如果这台 MacBook 从买来到现在就没重装过，系统里可能积累了各种残留文件。&lt;/p&gt;

&lt;p&gt;操作很简单：&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;备份&lt;/strong&gt;重要文件到 移动盘或云盘&lt;/li&gt;
&lt;li&gt;开机时按住 &lt;code&gt;Option + Command + R&lt;/code&gt;，进入在线恢复模式&lt;/li&gt;
&lt;li&gt;选择重新安装 macOS，把系统恢复到最干净的状态&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;重装后的系统就像刚搬进的空房子，接下来我们往里面添置需要的家具。&lt;/p&gt;

&lt;hr&gt;
&lt;h2 id="第一步：安装 Homebrew"&gt;第一步：安装 Homebrew&lt;/h2&gt;&lt;h3 id="Homebrew 是什么？"&gt;Homebrew 是什么？&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;Homebrew 就像 Mac 中隐藏版的应用商店，但比 App Store 强大得多。&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;想象你想在新家里添置家具：&lt;/p&gt;
&lt;table class="table table-bordered table-striped"&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;th&gt;App Store&lt;/th&gt;
&lt;th&gt;Homebrew&lt;/th&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;像大型商场，卖的都是精装成品（爱奇艺）&lt;/td&gt;
&lt;td&gt;像万能仓库，程序员需要的各种工具、语言、软件，一条命令就能自动下载、安装、配置好&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;买不到专业工具，或者版本很旧&lt;/td&gt;
&lt;td&gt;永远是最新版，不需要你到处找安装包、点下一步、输密码&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;&lt;h3 id="安装命令"&gt;安装命令&lt;/h3&gt;
&lt;p&gt;打开终端，复制粘贴下面这行命令：&lt;/p&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;/bin/bash &lt;span class="nt"&gt;-c&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;curl &lt;span class="nt"&gt;-fsSL&lt;/span&gt; https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;系统会提示你输入开机密码（输入时不会显示字符，这是正常的，输完直接回车）
安装过程可能需要几分钟，取决于网速。(建议安装的过程中全局科学上网)&lt;/p&gt;

&lt;p&gt;&lt;img src="https://l.ruby-china.com/photo/AixCoder/ddfd0cd3-9286-4f96-b7c1-b26dd0a9bed6.png!large" title="" alt=""&gt;&lt;/p&gt;

&lt;p&gt;安装 Homebrew 的过程中，它会顺带安装 &lt;strong&gt;Command Line Tools for Xcode&lt;/strong&gt;。&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Command Line Tools for Xcode 是什么？&lt;/strong&gt;
Xcode 是苹果官方出的集成开发工具，里面包含了开发 iPhone App 需要的所有重型设备（几十个 GB）。但对于 90% 的开发者来说，我们不需要那个庞然大物，只需要里面最核心的几件工具&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;编译器 (Compiler)&lt;/strong&gt;：把人类能看懂的代码，"翻译"成电脑能听懂的机器指令
&lt;strong&gt;构建工具 (Make)&lt;/strong&gt;：相当于安装指南，告诉电脑第一步拼哪块，第二步缝哪条线&lt;/p&gt;

&lt;p&gt;这就是 Command Line Tools for Xcode，包含了所有让代码"跑起来"的必要工具。&lt;/p&gt;
&lt;h5 id="验证安装成功"&gt;验证安装成功&lt;/h5&gt;&lt;pre class="highlight shell"&gt;&lt;code&gt;brew &lt;span class="nt"&gt;--version&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;如果显示出版本号（例如 &lt;code&gt;Homebrew 5.1.10&lt;/code&gt;），恭喜你，Homebrew 已经就位。&lt;/p&gt;
&lt;h2 id="第二步：安装 rbenv"&gt;第二步：安装 rbenv&lt;/h2&gt;&lt;h3 id="rbenv 是什么？"&gt;rbenv 是什么？&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;rbenv 是 Ruby 的「版本管家」。&lt;/strong&gt; 它让你在同一台电脑上安装多个 Ruby 版本，想用什么版本就切什么版本，互不干扰。&lt;/p&gt;
&lt;h3 id="为什么我们需要它？"&gt;为什么我们需要它？&lt;/h3&gt;
&lt;p&gt;想象一下，你是一个热爱生活且多才多艺的女生：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;今天你想做一个现代感十足的网站，需要请 &lt;strong&gt;Ruby 3.2&lt;/strong&gt; 大师来帮忙&lt;/li&gt;
&lt;li&gt;明天你想维护一个几年前的老项目，那个项目很挑剔，需要指定某个版本的 Ruby&lt;/li&gt;
&lt;li&gt;而你的 Mac 系统一直带着一个"老古董" &lt;strong&gt;Ruby 2.6&lt;/strong&gt; 系统管理员，它是用来维持系统运行的，你最好不要动它&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;如果没有 rbenv：&lt;/strong&gt; 你得在电脑里装好几个 Ruby，它们会为了争夺"谁才是真正的 Ruby"打架，最后把你的系统搞得一团糟&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;有了 rbenv：&lt;/strong&gt; 简单管理多个版本的 Ruby，系统自带的旧版本不动，新版本各住各的房间，互不打扰&lt;/p&gt;
&lt;h3 id="安装命令"&gt;安装命令&lt;/h3&gt;&lt;pre class="highlight shell"&gt;&lt;code&gt;brew &lt;span class="nb"&gt;install &lt;/span&gt;rbenv
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;安装的过程可能比较慢，因为电脑比较老了，很多工具得现场编译，耐心等待吧⌛️
&lt;img src="https://l.ruby-china.com/photo/AixCoder/77825179-e543-4e2a-a4ad-a59356680f28.png!large" title="" alt=""&gt;&lt;/p&gt;

&lt;p&gt;安装完成后，请运行以下命令来配置 rbenv&lt;/p&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;rbenv init
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;​&lt;strong&gt;关闭终端，重新打开&lt;/strong&gt;​，以确保更改生效。&lt;/p&gt;
&lt;h5 id="验证安装"&gt;验证安装&lt;/h5&gt;&lt;pre class="highlight shell"&gt;&lt;code&gt;rbenv &lt;span class="nt"&gt;-v&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;h2 id="第三步：安装 Ruby"&gt;第三步：安装 Ruby&lt;/h2&gt;&lt;h3 id="Ruby 的美丽：一门像诗一样的编程语言"&gt;Ruby 的美丽：一门像诗一样的编程语言&lt;/h3&gt;
&lt;p&gt;rbenv 管家已经就位，现在该请真正的主角进场了。&lt;/p&gt;

&lt;p&gt;Ruby 像一首诗&lt;/p&gt;

&lt;p&gt;它的发明者松本行弘（Yukihiro Matsumoto）说过一句话：&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;"我希望 Ruby 让程序员感到快乐。"&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3 id="安装命令"&gt;安装命令&lt;/h3&gt;
&lt;p&gt;先查看 rbenv 目前能提供的比较稳定的几个 Ruby 版本：&lt;/p&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;rbenv &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-l&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;对于这台老旧 MacBook，我装了的 Ruby3.3.10，和其他设备保持版本一致性&lt;/p&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;rbenv &lt;span class="nb"&gt;install &lt;/span&gt;3.3.10
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src="https://l.ruby-china.com/photo/AixCoder/b61515ba-bf33-4071-8c58-b4bdc9b872f1.png!large" title="" alt=""&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;💻 ​&lt;strong&gt;老旧 MacBook 特别提示&lt;/strong&gt;​：编译 Ruby 可能需要 ​&lt;strong&gt;10-20 分钟&lt;/strong&gt;​，风扇会狂转，这是正常的。建议插电进行，去泡杯茶，回来就好了。&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;装完后，设为默认版本：&lt;/p&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;rbenv global 3.3.10
&lt;/code&gt;&lt;/pre&gt;&lt;h3 id="验证安装"&gt;验证安装&lt;/h3&gt;&lt;pre class="highlight shell"&gt;&lt;code&gt;ruby &lt;span class="nt"&gt;-v&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;ruby 3.3.10 (2025-10-23 revision 343ea05002) [x86_64-darwin20]
旧 MacBook 现在拥有了一颗年轻的心脏&lt;/p&gt;
&lt;h2 id="第四步：安装 Rails"&gt;第四步：安装 Rails&lt;/h2&gt;&lt;h3 id="Rails：Ruby 的黄金搭档，让想法快速落地"&gt;Rails：Ruby 的黄金搭档，让想法快速落地&lt;/h3&gt;
&lt;p&gt;如果说 Ruby 是一首诗，​&lt;strong&gt;Rails 就是一家出版社&lt;/strong&gt;​
它不负责写诗，但它让诗能被印刷、装订、送到读者手中，而且整个过程快得惊人。&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Rails 像是一套「盖房子的标准化流程」。&lt;/strong&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;你想搭一个网站，Rails 已经帮你把地基打好了、水电接通了、门窗装好了。你只需要决定：墙上刷什么颜色的漆、客厅里摆什么家具。&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;Rails 就是这样。它假设你「大概率会这样做」，所以提前帮你把选择做好了。这种哲学叫 ​&lt;strong&gt;"约定优于配置"&lt;/strong&gt;​（Convention over Configuration）。&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;对于初学者来说，这太重要了。&lt;/strong&gt; 你不需要先成为「全栈专家」，就能先做出一个「能用的东西」。成就感来得早，学习动力就足。&lt;/p&gt;

&lt;p&gt;记得当时做一个诗歌小站——可以读诗、投稿、玩一个简单的文字游戏，结合 AI 编程，好像用了大半天就做出来了。&lt;/p&gt;
&lt;h3 id="gem 是什么？"&gt;gem 是什么？&lt;/h3&gt;
&lt;p&gt;当你通过 rbenv 安装好 Ruby 的那一刻，​&lt;strong&gt;gem 就已经坐在你电脑里了&lt;/strong&gt;​。&lt;/p&gt;

&lt;p&gt;​&lt;strong&gt;gem 的全名叫 RubyGems&lt;/strong&gt;​，是 Ruby 官方的包管理器，可以理解成 ​&lt;strong&gt;Ruby 世界的「快递总站」&lt;/strong&gt;​。&lt;/p&gt;

&lt;p&gt;它的工作流程：&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;你在终端说：&lt;code&gt;gem install rails&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;gem 就联网去 ​&lt;strong&gt;rubygems.org&lt;/strong&gt;​（Ruby 的官方仓库）&lt;/li&gt;
&lt;li&gt;找到 Rails，把它连同所有依赖一起打包下载、自动安装&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 id="安装命令"&gt;安装命令&lt;/h3&gt;&lt;pre class="highlight shell"&gt;&lt;code&gt;gem &lt;span class="nb"&gt;install &lt;/span&gt;rails &lt;span class="nt"&gt;-v&lt;/span&gt; 你想要的rails版本
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;⚠️ ​&lt;strong&gt;如果下载很慢&lt;/strong&gt;​：因为 gem 默认去国外的仓库拉货，可能会卡很久。建议先换成国内镜像源（ruby-china 镜像源）&lt;/p&gt;

&lt;p&gt;安装过程可能需要几分钟，gem 会自动下载 Rails 及其所有依赖。&lt;/p&gt;
&lt;h3 id="验证安装"&gt;验证安装&lt;/h3&gt;&lt;pre class="highlight shell"&gt;&lt;code&gt;rails &lt;span class="nt"&gt;-v&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;h2 id="第五步：创建并启动你的第一个项目"&gt;第五步：创建并启动你的第一个项目&lt;/h2&gt;&lt;h3 id="1. 新建项目"&gt;1. 新建项目&lt;/h3&gt;
&lt;p&gt;在终端里输入：&lt;/p&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;rails new rails_nice
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;你会看到很多绿色的输出，正在新建项目，请稍等。
&lt;img src="https://l.ruby-china.com/photo/AixCoder/7dfc5015-16a8-4508-8699-70ae0dc7e586.png!large" title="" alt=""&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;💻 ​&lt;strong&gt;老旧 MacBook 特别提示&lt;/strong&gt;​：第一次创建项目时，Rails 需要安装依赖，可能会比较慢，请耐心等待。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3 id="2. 进入项目文件夹"&gt;2. 进入项目文件夹&lt;/h3&gt;&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;cd &lt;/span&gt;rails_nice
&lt;/code&gt;&lt;/pre&gt;&lt;h3 id="3. 启动服务器"&gt;3. 启动服务器&lt;/h3&gt;&lt;pre class="highlight shell"&gt;&lt;code&gt;bin/rails s
&lt;/code&gt;&lt;/pre&gt;&lt;h3 id="4. 你会看到什么？"&gt;4. 你会看到什么？&lt;/h3&gt;
&lt;p&gt;终端里会显示：&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;=&amp;gt; Booting Puma
=&amp;gt; Rails 7.2.3 application starting in development 
=&amp;gt; Run `bin/rails server --help` for more startup options
Puma starting in single mode...
* Puma version: 8.0.1 ("Into the Arena")
* Ruby version: ruby 3.3.10 (2025-10-23 revision 343ea05002) [x86_64-darwin20]
*  Min threads: 3
*  Max threads: 3
*  Environment: development
*          PID: 14775
* Listening on http://127.0.0.1:3000
* Listening on http://[::1]:3000
Use Ctrl-C to stop
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;看到 &lt;code&gt;Listening on http://127.0.0.1:3000&lt;/code&gt;，就是​&lt;strong&gt;启动成功了&lt;/strong&gt;​。&lt;/p&gt;
&lt;h3 id="5. 在浏览器里查看成果"&gt;5. 在浏览器里查看成果&lt;/h3&gt;
&lt;p&gt;打开 Safari（或任何浏览器），地址栏输入：&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;http://127.0.0.1:3000
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src="https://l.ruby-china.com/photo/AixCoder/c8a645d4-2876-412d-b686-c83f0f1b74ca.png!large" title="" alt=""&gt;&lt;/p&gt;
&lt;h2 id="写在最后"&gt;写在最后&lt;/h2&gt;
&lt;p&gt;从重装系统，到 Homebrew，到 rbenv，到 Ruby，到 gem，到 Rails，再到 &lt;code&gt;bin/rails s&lt;/code&gt;——&lt;strong&gt;这台旧 MacBook，终于焕发了第二春。&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;旧物值得被认真对待，学习也可以从当下便宜的设备开始。&lt;/p&gt;

&lt;p&gt;M 芯片的 MacBook 就留着干重活吧，这台 macbook air 小电脑，很轻巧。&lt;/p&gt;
&lt;h3 id="💡 小结"&gt;💡 小结&lt;/h3&gt;
&lt;p&gt;整个流程其实就是一个“套娃”安装的过程：&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;用 &lt;strong&gt;command line tools for xocde&lt;/strong&gt; 为 &lt;strong&gt;Homebrew&lt;/strong&gt; 铺路。&lt;/li&gt;
&lt;li&gt;用 &lt;strong&gt;Homebrew&lt;/strong&gt; 安装 ​&lt;strong&gt;rbenv&lt;/strong&gt;​。&lt;/li&gt;
&lt;li&gt;用 &lt;strong&gt;rbenv&lt;/strong&gt; 安装 ​&lt;strong&gt;Ruby&lt;/strong&gt;​。&lt;/li&gt;
&lt;li&gt;用 ​&lt;strong&gt;Ruby&lt;/strong&gt;​（自带的 gem）安装 ​&lt;strong&gt;Rails&lt;/strong&gt;​。&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;每一个上层工具都依赖下层提供的环境。这就是为什么当初我们必须先解决 Xcode 开发工具安装的问题。&lt;/p&gt;

&lt;p&gt;好啦，看到这台“小破机”重新跑起代码，😄
作为编程学习机和写作工具来说真是很好，非常适合带出门轻度使用。小电脑，很轻巧。&lt;/p&gt;</description>
      <author>AixCoder</author>
      <pubDate>Sun, 10 May 2026 13:22:14 +0800</pubDate>
      <link>https://ruby-china.org/topics/44567</link>
      <guid>https://ruby-china.org/topics/44567</guid>
    </item>
    <item>
      <title>Ruby 小白，做了个女性友好的诗歌 web app</title>
      <description>&lt;p&gt;📱：&lt;a href="https://pomes.petercat.life/" rel="nofollow" target="_blank"&gt;https://pomes.petercat.life/&lt;/a&gt;&lt;/p&gt;
&lt;h3 id="起初的想法"&gt;起初的想法&lt;/h3&gt;
&lt;p&gt;去年冬天的时候就想着设计一款女性友好的诗歌 app&lt;/p&gt;
&lt;h3 id="落地实现"&gt;落地实现&lt;/h3&gt;
&lt;p&gt;2026 年开春了，借着当下 AI 编程的浪潮&lt;/p&gt;

&lt;p&gt;选择用 Ruby 技术展现出这么个诗歌网站&lt;/p&gt;

&lt;p&gt;这么个诗歌的小屋，用到的不过是那一点点技术
几行 Ruby 代码，一个 SQLite3，再加上让文字呼吸顺畅的 CSS 间距。&lt;/p&gt;

&lt;p&gt;在古法编程的时代来看一个小白要做出一个 app 并部署上线，要学习比较长时间的
如今来看这么个 app 并没有什么傲人的地方，门槛也比较低&lt;/p&gt;

&lt;p&gt;但是有的时候也常常想，如果更多女性来学这一点点技，多一些这样的港湾也蛮好&lt;/p&gt;
&lt;h3 id="ps：想问问群里的一些深耕Ruby领域多年的“老员工”"&gt;ps：想问问群里的一些深耕 Ruby 领域多年的“老员工”&lt;/h3&gt;
&lt;p&gt;Ruby 在国内的就业市场相比几年前下滑？&lt;/p&gt;

&lt;p&gt;几年前就听说 Ruby 在国内用人招人不好招 😂&lt;/p&gt;

&lt;p&gt;不过呢，学习 Ruby 倒不是说一味的奔着找工作去的&lt;/p&gt;

&lt;p&gt;目前以兴趣在驱动着学习，能够把自己的一些想法借助 AI coding 落地（以兴趣为主）&lt;/p&gt;</description>
      <author>AixCoder</author>
      <pubDate>Mon, 23 Mar 2026 14:45:29 +0800</pubDate>
      <link>https://ruby-china.org/topics/44528</link>
      <guid>https://ruby-china.org/topics/44528</guid>
    </item>
    <item>
      <title>给旧电脑整容，配置好干净的开发环境</title>
      <description>&lt;h2 id="物尽其用"&gt;物尽其用&lt;/h2&gt;
&lt;p&gt;十几年前的苹果💻还能用&lt;/p&gt;

&lt;p&gt;已经配置了很清爽的开发环境&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;稳定版 Ruby &lt;strong&gt;3.3.10&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Rails &lt;strong&gt;7.2.3&lt;/strong&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;对于我这个新手来说
感觉良好呢&lt;/p&gt;

&lt;p&gt;虽然有了 M 芯片的电脑
但是感觉这老电脑用来外出学习也够用了&lt;/p&gt;

&lt;p&gt;得感谢大模型
gemini3 生产的前端代码利用率极高&lt;/p&gt;

&lt;p&gt;用来学学 web app 开发效率很高呢
&lt;img src="https://l.ruby-china.com/photo/AixCoder/d4083f1f-bb09-466b-9443-bcd2ea7cf30d.jpg!large" title="" alt=""&gt;&lt;/p&gt;</description>
      <author>AixCoder</author>
      <pubDate>Tue, 10 Feb 2026 20:41:02 +0800</pubDate>
      <link>https://ruby-china.org/topics/44478</link>
      <guid>https://ruby-china.org/topics/44478</guid>
    </item>
    <item>
      <title>Ruby 新手，准备买点纸质书，大佬们看看这个清单是否合理。</title>
      <description>&lt;p&gt;大家好
目前 Ruby 新手阶段
已经开发了轻量级 web app，部署到了云服务器上，有一部分代码是 AI 生成的，想进一步了解 web app 的开发。&lt;/p&gt;

&lt;p&gt;从官网&lt;a href="https://www.ruby-lang.org/zh_cn/documentation/" rel="nofollow" target="_blank"&gt;https://www.ruby-lang.org/zh_cn/documentation/&lt;/a&gt;
了解到了一些初学者入门的建议。&lt;/p&gt;

&lt;p&gt;《Why’s (Poignant) Guide to Ruby》
这本非常有趣的书&lt;/p&gt;

&lt;p&gt;《Learn to Program》
非常棒的小教程&lt;/p&gt;

&lt;p&gt;除了上面的两本入门的电子书，想购入一点纸质书来学习，达到边学边练习。&lt;/p&gt;

&lt;p&gt;计划购入《Rails 5 敏捷开发》
这本书虽然技术方案老旧了，现在的学习和开发环境是 Mac+rails7.2.3+Ruby3.x
AI 提示：可以学习书里介绍的 MVC 思想&lt;/p&gt;

&lt;p&gt;AI 时代的好处就是不用死记 Ruby 的语法了，希望群里拥有成熟商业项目开发经验的靓仔们推荐一些书单，让新手学习的过程中有些路径可寻&lt;/p&gt;</description>
      <author>AixCoder</author>
      <pubDate>Fri, 16 Jan 2026 12:44:52 +0800</pubDate>
      <link>https://ruby-china.org/topics/44452</link>
      <guid>https://ruby-china.org/topics/44452</guid>
    </item>
    <item>
      <title>Ruby Girl-web 项目传统部署流程分享，自学 web app 开发并已经上线💐</title>
      <description>&lt;h2 id="Ubuntu 25.10 上部署 Rails 应用：从零到生产环境"&gt;Ubuntu 25.10 上部署 Rails 应用：从零到生产环境&lt;/h2&gt;&lt;h2 id="概述"&gt;概述&lt;/h2&gt;
&lt;p&gt;本文介绍在 Ubuntu 25.10 上使用 &lt;strong&gt;sqlite3 + Puma + Nginx&lt;/strong&gt; 部署 Rails 应用的完整流程。本文以学习为目的，不使用现代化的自动部署工具，而是直接在 Linux 宿主机上从零搭建生产环境。&lt;/p&gt;

&lt;p&gt;这种方式稳定可靠，足以应对多年的使用需求。尽管当前 AI 工具辅助开发已成为常态，但亲自动手实践仍是掌握技能的关键。&lt;/p&gt;

&lt;p&gt;####本地开发环境&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;操作系统&lt;/strong&gt;: macOS&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Ruby 版本&lt;/strong&gt;: 3.2.9&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Rails 版本&lt;/strong&gt;: 7.2.3&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;参考文章&lt;/strong&gt;: &lt;a href="https://ruby-china.org/topics/25774" title=""&gt;Ruby China 上的相关教程&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;由于原文章发布时间较早，本文结合当前技术环境优化了部署流程&lt;/p&gt;
&lt;/blockquote&gt;

&lt;hr&gt;
&lt;h3 id="前言"&gt;前言&lt;/h3&gt;
&lt;p&gt;近期自学了 Rails 开发，借助 AI 辅助实现了应用的部署上线。作为服务端开发的新手，初次部署应用时确实遇到了一些挑战，但成功发布第一个 Web 应用是全栈开发道路上的一大步。&lt;/p&gt;

&lt;p&gt;希望通过本文的分享，能够帮助更多 Ruby/Rails 初学者。亲手将自己开发的应用部署上线，这种创造带来的成就感远超刷手机的短暂快乐，也是持续学习的重要动力。&lt;/p&gt;

&lt;hr&gt;
&lt;h3 id="部署架构总览"&gt;部署架构总览&lt;/h3&gt;&lt;h4 id="请求流向"&gt;请求流向&lt;/h4&gt;
&lt;p&gt;用户浏览器 → Nginx (80/443 端口) → Unix Socket/端口 → Puma (应用服务器) → Rails 应用&lt;/p&gt;
&lt;h5 id="部署流程图（Google Cloud VM，1GB内存）"&gt;部署流程图（Google Cloud VM，1GB 内存）&lt;/h5&gt;&lt;pre class="highlight markdown"&gt;&lt;code&gt;┌─────────────────────────────────────────────────────────────────┐
│                     Rails 应用部署完整流程                          │
│                    (1GB Google Cloud VM)                        │
└─────────────────────────────────────────────────────────────────┘
                              开始
                               │
                               ▼
┌──────────────────────────────────────────────────────────────────┐
│  阶段 1: 本地准备工作                                               │
│  ┌────────────────────────────────────────────────────────┐      │
│  │ 1.1 确保代码已提交到 Git                                  │      │
│  │     git add . &amp;amp;&amp;amp; git commit -m "Ready to deploy"        │      │
│  │                                                         │      │
│  │ 1.2 推送到 GitHub/GitLab                                │      │
│  │     git push origin main                                │      │
│  │                                                         │      │
│  │ 1.3 准备环境变量清单                                      │      │
│  │     - SECRET_KEY_BASE                                   │      │
│  │     - DATABASE_PASSWORD                                 │      │
│  │     - 其他敏感信息                                        │      │
│  └────────────────────────────────────────────────────────┘      │
└──────────────────────────────────────────────────────────────────┘
                               │
                               ▼
┌──────────────────────────────────────────────────────────────────┐
│  阶段 2: 连接到 Google Cloud VM                                   │
│  ┌────────────────────────────────────────────────────────┐      │
│  │ 2.1 获取 VM 外部 IP 地址                                  │      │
│  │     GCP 控制台 → Compute Engine → VM 实例                │      │
│  │     记录: 外部 IP = XXX.XXX.XXX.XXX                      │      │
│  │                                                         │      │
│  │ 2.2 通过 SSH 连接                                         │      │
│  │     ssh username@XXX.XXX.XXX.XXX                        │      │
│  │     或使用 GCP 控制台的 SSH 按钮                           │      │
│  └────────────────────────────────────────────────────────┘      │
└──────────────────────────────────────────────────────────────────┘
                               │
                               ▼
┌──────────────────────────────────────────────────────────────────┐
│  阶段 3: 配置服务器环境 (首次需要)                                   │
│  ┌────────────────────────────────────────────────────────┐      │
│  │ 3.1 更新系统                                             │      │
│  │     sudo apt update &amp;amp;&amp;amp; sudo apt upgrade -y              │      │
│  │                                                         │      │
│  │ 3.2 安装基础工具                                          │      │
│  │     sudo apt install -y curl git build-essential        │      │
│  │                                                         │      │
│  │ 3.3 创建 Swap (重要!)                                     │      │
│  │     sudo fallocate -l 1G /swapfile                      │      │
│  │     sudo chmod 600 /swapfile                            │      │
│  │     sudo mkswap /swapfile &amp;amp;&amp;amp; sudo swapon /swapfile      │      │
│  │                                                         │      │
│  │ 3.4 安装 Ruby (rbenv)                                    │      │
│  │     git clone https://github.com/rbenv/rbenv.git        │      │
│  │     # ... (详细步骤见下方)                                 │      │
│  │                                                         │      │
│  │ 3.5 安装数据库 (PostgreSQL 或 SQLite)                    │      │
│  │     sudo apt install -y postgresql libpq-dev            │      │
│  │                                                         │      │
│  │ 3.6 安装 Nginx                                           │      │
│  │     sudo apt install -y nginx                           │      │
│  └────────────────────────────────────────────────────────┘      │
└──────────────────────────────────────────────────────────────────┘
                               │
                               ▼
┌──────────────────────────────────────────────────────────────────┐
│  阶段 4: 部署应用代码                                              │
│  ┌────────────────────────────────────────────────────────┐      │
│  │ 4.1 创建项目目录                                          │      │
│  │     sudo mkdir -p /var/www                              │      │
│  │     sudo chown $USER:$USER /var/www                     │      │
│  │                                                         │      │
│  │ 4.2 克隆项目                                              │      │
│  │     cd /var/www                                         │      │
│  │     git clone https://github.com/你的项目.git            │      │
│  │     cd 你的项目名                                         │      │
│  │                                                         │      │
│  │ 4.3 安装 Gem 依赖                                         │      │
│  │     bundle config set --local deployment 'true'         │      │
│  │     bundle install                                      │      │
│  │                                                         │      │
│  │ 4.4 配置环境变量                                          │      │
│  │     nano .env                                           │      │
│  │     # 填入 SECRET_KEY_BASE 等                            │      │
│  │                                                         │      │
│  │ 4.5 设置数据库                                            │      │
│  │     RAILS_ENV=production rails db:create                │      │
│  │     RAILS_ENV=production rails db:migrate               │      │
│  │                                                         │      │
│  │ 4.6 预编译资源                                            │      │
│  │     RAILS_ENV=production rails assets:precompile        │      │
│  └────────────────────────────────────────────────────────┘      │
└──────────────────────────────────────────────────────────────────┘
                               │
                               ▼
┌──────────────────────────────────────────────────────────────────┐
│  阶段 5: 配置 Puma 服务                                            │
│  ┌────────────────────────────────────────────────────────┐      │
│  │ 5.1 创建 Systemd 服务文件                                 │      │
│  │     sudo nano /etc/systemd/system/rails.service         │      │
│  │                                                         │      │
│  │ 5.2 启动服务                                              │      │
│  │     sudo systemctl daemon-reload                        │      │
│  │     sudo systemctl enable rails                         │      │
│  │     sudo systemctl start rails                          │      │
│  │                                                         │      │
│  │ 5.3 检查状态                                              │      │
│  │     sudo systemctl status rails                         │      │
│  └────────────────────────────────────────────────────────┘      │
└──────────────────────────────────────────────────────────────────┘
                               │
                               ▼
┌──────────────────────────────────────────────────────────────────┐
│  阶段 6: 配置 Nginx 反向代理                                       │
│  ┌────────────────────────────────────────────────────────┐      │
│  │ 6.1 创建 Nginx 配置                                       │      │
│  │     sudo nano /etc/nginx/sites-available/rails          │      │
│  │                                                         │      │
│  │ 6.2 启用配置                                              │      │
│  │     sudo ln -s /etc/nginx/sites-available/rails &lt;span class="se"&gt;\ &lt;/span&gt;      │      │
│  │                /etc/nginx/sites-enabled/                │      │
│  │     sudo rm /etc/nginx/sites-enabled/default            │      │
│  │                                                         │      │
│  │ 6.3 测试并重启 Nginx                                      │      │
│  │     sudo nginx -t                                       │      │
│  │     sudo systemctl restart nginx                        │      │
│  └────────────────────────────────────────────────────────┘      │
└──────────────────────────────────────────────────────────────────┘
                               │
                               ▼
┌──────────────────────────────────────────────────────────────────┐
│  阶段 7: 配置防火墙                                                │
│  ┌────────────────────────────────────────────────────────┐      │
│  │ 7.1 在 GCP 控制台配置防火墙规则                            │      │
│  │     VPC 网络 → 防火墙 → 创建防火墙规则                     │      │
│  │     - 允许 tcp:80 (HTTP)                                │      │
│  │     - 允许 tcp:443 (HTTPS)                              │      │
|  | 其实在创建虚拟机的时候，把https和http流量入口都打开，应该就可以了
│  └────────────────────────────────────────────────────────┘      │
└──────────────────────────────────────────────────────────────────┘
                               │
                               ▼
┌──────────────────────────────────────────────────────────────────┐
│  阶段 8: 测试访问                                                  │
│  ┌────────────────────────────────────────────────────────┐      │
│  │ 8.1 在浏览器中访问                                         │      │
│  │     http://你的云服务器外部IP                                   │      │
│  │                                                         │      │
│  │ 8.2 验证网站正常显示│  └───────────────────
─────────────────────────────────────┘      │
└──────────────────────────────────────────────────────────────────┘
                               │
                               ▼
                          部署完成! 🎉
&lt;/code&gt;&lt;/pre&gt;
&lt;hr&gt;
&lt;h4 id="详细部署步骤"&gt;详细部署步骤&lt;/h4&gt;&lt;h5 id="阶段 1: 本地准备工作"&gt;阶段 1: 本地准备工作&lt;/h5&gt;
&lt;p&gt;1.1 修改生产环境配置
在项目中的 &lt;code&gt;/config/environments/production.rb&lt;/code&gt; 文件中，临时允许 HTTP 访问：
config.force_ssl = false&lt;/p&gt;

&lt;p&gt;1.2 代码提交到 GitHub&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;将代码提交到 GitHub（如不想公开代码，请选择私有仓库）&lt;/li&gt;
&lt;li&gt;确保所有更改已提交并推送&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;1.3 生成生产环境密钥（非常重要！）&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;在本地 Rails 项目目录运行以下命令：
rails secret&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;复制输出的密钥（示例：3a5b8c9d7e6f1a2b3c4d5e6f7a8b9c0d...），稍后会用到。&lt;/p&gt;
&lt;h4 id="阶段 2: 创建并连接到 Google 云服务器"&gt;阶段 2: 创建并连接到 Google 云服务器&lt;/h4&gt;
&lt;p&gt;2.1 创建云服务器&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;区域选择：选择离您较近的区域（如香港）&lt;/li&gt;
&lt;li&gt;操作系统：Ubuntu 25.10&lt;/li&gt;
&lt;li&gt;网络配置：确保开启 HTTP 和 HTTPS 流量入口&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;2.2 通过 SSH 连接到服务器
ssh username@你的服务器 IP 地址
提示：如果不熟悉 SSH 连接，可以使用 Google Cloud Console 的 SSH 按钮或咨询 AI 工具获取帮助。&lt;/p&gt;
&lt;h4 id="阶段 3: 配置服务器环境（首次部署必需）"&gt;阶段 3: 配置服务器环境（首次部署必需）&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;3.1 更新系统
sudo apt-get update&lt;/li&gt;
&lt;li&gt;3.2 安装必要工具包
这些软件包是编译和运行 Ruby on Rails 应用的基础依赖：&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;sudo apt-get install -y git curl zlib1g-dev build-essential 
libssl-dev libreadline-dev libyaml-dev libsqlite3-dev sqlite3 
libxml2-dev libxslt1-dev libcurl4-openssl-dev libffi-dev 
libvips&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;比喻：连接上云服务器就像搬进新家，需要先完成通水通电等基础装修，后续的应用程序（家电）才能正常工作。&lt;/li&gt;
&lt;li&gt;3.3 创建 Swap 虚拟内存（重要！）&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# 创建 1GB Swap 文件（内存不足时可增加至 2GB）&lt;/span&gt;
&lt;span class="nb"&gt;sudo &lt;/span&gt;fallocate &lt;span class="nt"&gt;-l&lt;/span&gt; 1G /swapfile
&lt;span class="nb"&gt;sudo chmod &lt;/span&gt;600 /swapfile
&lt;span class="nb"&gt;sudo &lt;/span&gt;mkswap /swapfile
&lt;span class="nb"&gt;sudo &lt;/span&gt;swapon /swapfile
&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s1"&gt;'/swapfile none swap sw 0 0'&lt;/span&gt; | &lt;span class="nb"&gt;sudo tee&lt;/span&gt; &lt;span class="nt"&gt;-a&lt;/span&gt; /etc/fstab

&lt;span class="c"&gt;# 验证 Swap 是否生效&lt;/span&gt;
free &lt;span class="nt"&gt;-h&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;3.4 安装 Ruby
方法一：快速安装（推荐新手）
sudo apt-get install -y ruby-full
方法二：使用 rbenv（需要特定 Ruby 版本）&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# 安装 rbenv&lt;/span&gt;
git clone https://github.com/rbenv/rbenv.git ~/.rbenv
&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s1"&gt;'export PATH="$HOME/.rbenv/bin:$PATH"'&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt; ~/.bashrc
&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s1"&gt;'eval "$(rbenv init -)"'&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt; ~/.bashrc
&lt;span class="nb"&gt;source&lt;/span&gt; ~/.bashrc

&lt;span class="c"&gt;# 安装 ruby-build 插件&lt;/span&gt;
git clone https://github.com/rbenv/ruby-build.git ~/.rbenv/plugins/ruby-build

&lt;span class="c"&gt;# 安装指定 Ruby 版本（根据项目需要调整）&lt;/span&gt;
rbenv &lt;span class="nb"&gt;install &lt;/span&gt;3.2.0
rbenv global 3.2.0

&lt;span class="c"&gt;#验证安装：&lt;/span&gt;
ruby &lt;span class="nt"&gt;-v&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;3.5 安装 Bundler 和数据库&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# 安装 Bundler（Ruby 依赖管理工具）&lt;/span&gt;
gem &lt;span class="nb"&gt;install &lt;/span&gt;bundler

&lt;span class="c"&gt;# 安装 SQLite3（如项目使用）&lt;/span&gt;
&lt;span class="nb"&gt;sudo &lt;/span&gt;apt &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-y&lt;/span&gt; sqlite3 libsqlite3-dev

&lt;span class="c"&gt;# 或安装 PostgreSQL（如项目使用）&lt;/span&gt;
&lt;span class="nb"&gt;sudo &lt;/span&gt;apt &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-y&lt;/span&gt; postgresql postgresql-contrib libpq-dev
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;3.6 安装 Nginx
sudo apt install -y nginx
Nginx 比喻：如同机场航站楼，负责验证请求、路由转发、SSL/压缩处理和负载均衡。&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id="阶段 4: 部署应用代码"&gt;阶段 4: 部署应用代码&lt;/h4&gt;&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;#4.1 创建项目目录&lt;/span&gt;
  &lt;span class="nb"&gt;sudo mkdir&lt;/span&gt; &lt;span class="nt"&gt;-p&lt;/span&gt; /var/www
  &lt;span class="nb"&gt;sudo chown&lt;/span&gt; &lt;span class="nv"&gt;$USER&lt;/span&gt;:&lt;span class="nv"&gt;$USER&lt;/span&gt; /var/www
&lt;span class="c"&gt;#4.2 克隆项目&lt;/span&gt;
  &lt;span class="nb"&gt;cd&lt;/span&gt; /var/www
  git clone https://github.com/你的用户名/你的项目.git
  &lt;span class="nb"&gt;cd &lt;/span&gt;你的项目名
&lt;span class="c"&gt;#4.3 安装 Gem 依赖&lt;/span&gt;
  bundle config &lt;span class="nb"&gt;set&lt;/span&gt; &lt;span class="nt"&gt;--local&lt;/span&gt; deployment &lt;span class="s1"&gt;'true'&lt;/span&gt;
  bundle config &lt;span class="nb"&gt;set&lt;/span&gt; &lt;span class="nt"&gt;--local&lt;/span&gt; without &lt;span class="s1"&gt;'development test'&lt;/span&gt;
  bundle &lt;span class="nb"&gt;install&lt;/span&gt;
&lt;span class="c"&gt;# 4.4 配置环境变量&lt;/span&gt;
&lt;span class="c"&gt;# 创建 .env 文件：&lt;/span&gt;
  nano .env
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;.env 文件内容：&lt;/p&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Rails 环境&lt;/span&gt;
&lt;span class="nv"&gt;RAILS_ENV&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;production
&lt;span class="nv"&gt;RAILS_SERVE_STATIC_FILES&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;true&lt;/span&gt;

&lt;span class="c"&gt;# 安全密钥（使用之前生成的）&lt;/span&gt;
&lt;span class="nv"&gt;SECRET_KEY_BASE&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;粘贴你生成的密钥

&lt;span class="c"&gt;# 数据库配置（SQLite）&lt;/span&gt;
&lt;span class="nv"&gt;DATABASE_URL&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;sqlite3:db/production.sqlite3

&lt;span class="c"&gt;# PostgreSQL 配置（如使用）&lt;/span&gt;
&lt;span class="c"&gt;# DATABASE_URL=postgresql://用户名:密码@localhost/myapp_production&lt;/span&gt;

&lt;span class="c"&gt;# Puma 配置（1GB 内存优化）&lt;/span&gt;
&lt;span class="nv"&gt;RAILS_MAX_THREADS&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;5
&lt;span class="nv"&gt;WEB_CONCURRENCY&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;2

&lt;span class="c"&gt;# 日志级别&lt;/span&gt;
&lt;span class="nv"&gt;RAILS_LOG_LEVEL&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;info

&lt;span class="c"&gt;# 允许的主机（重要！）&lt;/span&gt;
&lt;span class="nv"&gt;ALLOWED_HOSTS&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;你的VM外部IP
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;操作提示：保存按 Ctrl+O，退出按 Ctrl+X&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;4.5 数据库设置和资源预编译&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# 创建数据库&lt;/span&gt;
&lt;span class="nv"&gt;RAILS_ENV&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;production bundle &lt;span class="nb"&gt;exec &lt;/span&gt;rails db:create

&lt;span class="c"&gt;# 运行数据库迁移&lt;/span&gt;
&lt;span class="nv"&gt;RAILS_ENV&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;production bundle &lt;span class="nb"&gt;exec &lt;/span&gt;rails db:migrate

&lt;span class="c"&gt;# 预编译静态资源&lt;/span&gt;
&lt;span class="nv"&gt;RAILS_ENV&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;production bundle &lt;span class="nb"&gt;exec &lt;/span&gt;rails assets:precompile

&lt;span class="c"&gt;# 测试启动（可选）&lt;/span&gt;
&lt;span class="nv"&gt;RAILS_ENV&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;production bundle &lt;span class="nb"&gt;exec &lt;/span&gt;rails server &lt;span class="nt"&gt;-b&lt;/span&gt; 0.0.0.0 &lt;span class="nt"&gt;-p&lt;/span&gt; 3000
&lt;span class="c"&gt;# 按 Ctrl+C 停止测试&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;h4 id="阶段 5: 配置 Puma 服务"&gt;阶段 5: 配置 Puma 服务&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;5.1 创建 Systemd 服务文件
sudo nano /etc/systemd/system/rails.service&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;粘贴以下内容（根据实际情况修改）：&lt;/p&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="o"&gt;[&lt;/span&gt;Unit]
&lt;span class="nv"&gt;Description&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;Rails Application &lt;span class="o"&gt;(&lt;/span&gt;Puma&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="nv"&gt;After&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;network.target

&lt;span class="o"&gt;[&lt;/span&gt;Service]
&lt;span class="nv"&gt;Type&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;simple
&lt;span class="nv"&gt;User&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;你的用户名
&lt;span class="nv"&gt;WorkingDirectory&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;/var/www/你的项目名
&lt;span class="nv"&gt;EnvironmentFile&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;/var/www/你的项目名/.env
&lt;span class="nv"&gt;ExecStart&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;/实际的bundle路径 &lt;span class="nb"&gt;exec &lt;/span&gt;puma &lt;span class="nt"&gt;-C&lt;/span&gt; config/puma.rb
&lt;span class="nv"&gt;Restart&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;always
&lt;span class="nv"&gt;RestartSec&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;10
&lt;span class="nv"&gt;StandardOutput&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;append:/var/www/你的项目名/log/puma.log
&lt;span class="nv"&gt;StandardError&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;append:/var/www/你的项目名/log/puma_error.log

&lt;span class="o"&gt;[&lt;/span&gt;Install]
&lt;span class="nv"&gt;WantedBy&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;multi-user.target
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;需要替换的内容：
1.你的用户名：运行 whoami 查看
2.你的项目名：你的项目目录名
3./实际的bundle路径：运行 which bundle 获取&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;5.2 启动并启用服务&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# 重新加载 systemd 配置&lt;/span&gt;
&lt;span class="nb"&gt;sudo &lt;/span&gt;systemctl daemon-reload

&lt;span class="c"&gt;# 启用开机自启&lt;/span&gt;
&lt;span class="nb"&gt;sudo &lt;/span&gt;systemctl &lt;span class="nb"&gt;enable &lt;/span&gt;rails

&lt;span class="c"&gt;# 启动服务&lt;/span&gt;
&lt;span class="nb"&gt;sudo &lt;/span&gt;systemctl start rails

&lt;span class="c"&gt;# 查看服务状态&lt;/span&gt;
&lt;span class="nb"&gt;sudo &lt;/span&gt;systemctl status rails

&lt;span class="c"&gt;# 如有错误，查看日志&lt;/span&gt;
&lt;span class="nb"&gt;sudo &lt;/span&gt;journalctl &lt;span class="nt"&gt;-u&lt;/span&gt; rails &lt;span class="nt"&gt;-n&lt;/span&gt; 50
&lt;/code&gt;&lt;/pre&gt;&lt;h4 id="阶段 6: 配置 Nginx 反向代理"&gt;阶段 6: 配置 Nginx 反向代理&lt;/h4&gt;
&lt;p&gt;6.1 创建 Nginx 配置
sudo nano /etc/nginx/sites-available/rails
粘贴以下配置：&lt;/p&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;upstream puma &lt;span class="o"&gt;{&lt;/span&gt;
  server 127.0.0.1:3000 &lt;span class="nv"&gt;fail_timeout&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;0&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;

server &lt;span class="o"&gt;{&lt;/span&gt;
  listen 80&lt;span class="p"&gt;;&lt;/span&gt;
  server_name 你的云服务器外部IP&lt;span class="p"&gt;;&lt;/span&gt;

  root /var/www/你的项目名/public&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="c"&gt;# 静态文件处理&lt;/span&gt;
  location ~ ^/&lt;span class="o"&gt;(&lt;/span&gt;assets|packs&lt;span class="o"&gt;)&lt;/span&gt;/ &lt;span class="o"&gt;{&lt;/span&gt;
    gzip_static on&lt;span class="p"&gt;;&lt;/span&gt;
    expires max&lt;span class="p"&gt;;&lt;/span&gt;
    add_header Cache-Control public&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="o"&gt;}&lt;/span&gt;

  try_files &lt;span class="nv"&gt;$uri&lt;/span&gt;/index.html &lt;span class="nv"&gt;$uri&lt;/span&gt; @puma&lt;span class="p"&gt;;&lt;/span&gt;

  location @puma &lt;span class="o"&gt;{&lt;/span&gt;
    proxy_set_header X-Forwarded-For &lt;span class="nv"&gt;$proxy_add_x_forwarded_for&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    proxy_set_header Host &lt;span class="nv"&gt;$http_host&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    proxy_set_header X-Forwarded-Proto &lt;span class="nv"&gt;$scheme&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    proxy_redirect off&lt;span class="p"&gt;;&lt;/span&gt;
    proxy_pass http://puma&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="o"&gt;}&lt;/span&gt;

  error_page 500 502 503 504 /500.html&lt;span class="p"&gt;;&lt;/span&gt;
  client_max_body_size 10M&lt;span class="p"&gt;;&lt;/span&gt;
  keepalive_timeout 10&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;6.2 启用配置并重启 Nginx&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# 启用站点配置&lt;/span&gt;
&lt;span class="nb"&gt;sudo ln&lt;/span&gt; &lt;span class="nt"&gt;-s&lt;/span&gt; /etc/nginx/sites-available/rails /etc/nginx/sites-enabled/

&lt;span class="c"&gt;# 删除默认配置&lt;/span&gt;
&lt;span class="nb"&gt;sudo rm&lt;/span&gt; /etc/nginx/sites-enabled/default

&lt;span class="c"&gt;# 测试配置语法&lt;/span&gt;
&lt;span class="nb"&gt;sudo &lt;/span&gt;nginx &lt;span class="nt"&gt;-t&lt;/span&gt;

&lt;span class="c"&gt;# 重启 Nginx&lt;/span&gt;
&lt;span class="nb"&gt;sudo &lt;/span&gt;systemctl restart nginx

&lt;span class="c"&gt;# 查看 Nginx 状态&lt;/span&gt;
&lt;span class="nb"&gt;sudo &lt;/span&gt;systemctl status nginx
&lt;/code&gt;&lt;/pre&gt;&lt;h4 id="阶段 7: 配置云服务器防火墙"&gt;阶段 7: 配置云服务器防火墙&lt;/h4&gt;
&lt;p&gt;注意：在创建虚拟机时已开启 HTTP/HTTPS 流量入口的，此步骤应该可跳过。
在 Google Cloud Console 中操作：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;1.导航到 VPC 网络 → 防火墙&lt;/li&gt;
&lt;li&gt;2.点击 创建防火墙规则&lt;/li&gt;
&lt;li&gt;3.配置 HTTP 规则：
名称：allow-http
目标：网络中的所有实例
来源 IP 范围：0.0.0.0/0
协议和端口：tcp:80&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id="阶段 8: 测试访问 🎉"&gt;阶段 8: 测试访问 &lt;img title=":tada:" alt="🎉" src="https://twemoji.ruby-china.com/2/svg/1f389.svg" class="twemoji"&gt;
&lt;/h4&gt;
&lt;p&gt;http://你的云服务器外部IP
如一切正常，您将看到您的 Rails 应用已成功上线！&lt;/p&gt;

&lt;p&gt;希望你上线顺利
中途需要注意的几个点：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;1.环境变量配置完整准确&lt;/li&gt;
&lt;li&gt;2.Systemd 服务文件路径一定要正确&lt;/li&gt;
&lt;li&gt;3.Nginx 配置无误&lt;/li&gt;
&lt;li&gt;4.防火墙规则正确设置&lt;/li&gt;
&lt;li&gt;5.还有上线的时候一定要预编译 Rails 的资源文件，不然你的网页上图片会裂开（加载不了图片）hhh&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;如果你的项目没跑起来，也不要急。好好跟 AI 对话，把你查到的错误日志、或者查看到的状态码都发给 AI 分析，基本上都能通过。&lt;/p&gt;</description>
      <author>AixCoder</author>
      <pubDate>Wed, 10 Dec 2025 14:11:11 +0800</pubDate>
      <link>https://ruby-china.org/topics/44415</link>
      <guid>https://ruby-china.org/topics/44415</guid>
    </item>
    <item>
      <title>小白学习 Ruby ,迈向全栈开发的第一步 | 环境安装 🎉</title>
      <description>&lt;p&gt;对于新手来说挺苦的，倒腾了一天。
&lt;img src="https://l.ruby-china.com/photo/AixCoder/30deefab-3f38-4a5d-8b0e-24c5ff8c9ca3.png!large" title="" alt=""&gt;&lt;/p&gt;

&lt;p&gt;明天再写下过程中遇到的坑
这次搭建环境遇到的问题 Google 上提供的答案都很少，苦逼了一天终于把 Rails 跑起来了&lt;/p&gt;

&lt;p&gt;老手们不要笑话 hhh&lt;/p&gt;</description>
      <author>AixCoder</author>
      <pubDate>Mon, 10 Nov 2025 21:18:07 +0800</pubDate>
      <link>https://ruby-china.org/topics/44386</link>
      <guid>https://ruby-china.org/topics/44386</guid>
    </item>
    <item>
      <title>Ruby Girl，小白学习 Ruby 第一弹 尝试下载小宇宙播客节目</title>
      <description>&lt;p&gt;哈喽&lt;img title=":grinning:" alt="😀" src="https://twemoji.ruby-china.com/2/svg/1f600.svg" class="twemoji"&gt;&lt;br&gt;
作为新手，开始接触 Ruby 这门编程语言&lt;/p&gt;

&lt;p&gt;看起来感觉跟 Py 差不多舒适&lt;/p&gt;

&lt;p&gt;都是比较简洁的语法&lt;/p&gt;

&lt;p&gt;比那隔壁的 Java 看起来感觉好多了&lt;/p&gt;

&lt;p&gt;目前不算怎么会写，暂时 get 不到所谓像诗一样的 Ruby 到底有多优雅，但至少代码第一观感还行&lt;/p&gt;

&lt;p&gt;这次练手的项目是写一个脚本来自动下载小宇宙的播客节目（蛮喜欢小宇宙这个播客平台）&lt;/p&gt;

&lt;p&gt;开发环境：macOS 自带的 Ruby 2.6 版本
附上代码 仅供学习参考！&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="c1"&gt;#!/usr/bin/env ruby&lt;/span&gt;
&lt;span class="c1"&gt;# 小宇宙播客下载脚本（支持备用地址重试）&lt;/span&gt;
&lt;span class="c1"&gt;# 仅使用 Ruby 系统内置库，无需额外安装&lt;/span&gt;

&lt;span class="c1"&gt;# 1. 引入 Ruby 标准库&lt;/span&gt;
&lt;span class="nb"&gt;require&lt;/span&gt; &lt;span class="s1"&gt;'net/http'&lt;/span&gt;
&lt;span class="nb"&gt;require&lt;/span&gt; &lt;span class="s1"&gt;'uri'&lt;/span&gt;
&lt;span class="nb"&gt;require&lt;/span&gt; &lt;span class="s1"&gt;'timeout'&lt;/span&gt;
&lt;span class="nb"&gt;require&lt;/span&gt; &lt;span class="s1"&gt;'json'&lt;/span&gt;
&lt;span class="nb"&gt;require&lt;/span&gt; &lt;span class="s1"&gt;'fileutils'&lt;/span&gt;

&lt;span class="c1"&gt;# 2. 配置参数&lt;/span&gt;
&lt;span class="no"&gt;TIMEOUT_SEC&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;300&lt;/span&gt;  &lt;span class="c1"&gt;# 下载超时时间（秒）&lt;/span&gt;
&lt;span class="no"&gt;NEXT_DATA_REGEX&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sr"&gt;/&amp;lt;script id="__NEXT_DATA__" type="application\/json"&amp;gt;([\s\S]*?)&amp;lt;\/script&amp;gt;/&lt;/span&gt;
&lt;span class="no"&gt;MEDIA_PATH&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sx"&gt;%w[props pageProps episode media]&lt;/span&gt;          &lt;span class="c1"&gt;# media字段路径&lt;/span&gt;
&lt;span class="no"&gt;SOURCE_URL_PATH&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sx"&gt;%w[source url]&lt;/span&gt;                       &lt;span class="c1"&gt;# 主下载地址路径（media -&amp;gt; source -&amp;gt; url）&lt;/span&gt;
&lt;span class="no"&gt;BACKUP_SOURCE_URL_PATH&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sx"&gt;%w[backupSource url]&lt;/span&gt;           &lt;span class="c1"&gt;# 备用下载地址路径（media -&amp;gt; backupSource -&amp;gt; url）&lt;/span&gt;
&lt;span class="no"&gt;TITLE_PATH&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sx"&gt;%w[props pageProps episode title]&lt;/span&gt;          &lt;span class="c1"&gt;# 节目名称路径&lt;/span&gt;
&lt;span class="no"&gt;CHUNK_SIZE&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;4096&lt;/span&gt;  &lt;span class="c1"&gt;# 下载分块大小（字节）&lt;/span&gt;
&lt;span class="no"&gt;INVALID_CHARS&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sr"&gt;/[\/\\:*?"&amp;lt;&amp;gt;|]/&lt;/span&gt;  &lt;span class="c1"&gt;# 文件名非法字符&lt;/span&gt;

&lt;span class="c1"&gt;# 3. 获取用户桌面路径（适配多系统）&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;desktop_path&lt;/span&gt;
  &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="no"&gt;RbConfig&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;CONFIG&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'host_os'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="k"&gt;when&lt;/span&gt; &lt;span class="sr"&gt;/mswin|mingw|cygwin/&lt;/span&gt;  &lt;span class="c1"&gt;# Windows&lt;/span&gt;
    &lt;span class="no"&gt;File&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;ENV&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'USERPROFILE'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="s1"&gt;'Desktop'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;when&lt;/span&gt; &lt;span class="sr"&gt;/darwin/&lt;/span&gt;  &lt;span class="c1"&gt;# macOS&lt;/span&gt;
    &lt;span class="no"&gt;File&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;ENV&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'HOME'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="s1"&gt;'Desktop'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;else&lt;/span&gt;  &lt;span class="c1"&gt;# Linux 等类 Unix 系统&lt;/span&gt;
    &lt;span class="no"&gt;File&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;ENV&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'HOME'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="s1"&gt;'Desktop'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;rescue&lt;/span&gt;
  &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="s2"&gt;"无法获取桌面路径，请检查系统环境"&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="c1"&gt;# 从下载链接提取文件扩展名&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;extract_file_extension&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="n"&gt;uri&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;URI&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="n"&gt;ext&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;File&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;extname&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;uri&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;path&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="n"&gt;ext&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;empty?&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="s1"&gt;'.m4a'&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;ext&lt;/span&gt;
&lt;span class="k"&gt;rescue&lt;/span&gt;
  &lt;span class="s1"&gt;'.m4a'&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="c1"&gt;# 清理文件名中的非法字符&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;clean_filename&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;raw_name&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="n"&gt;cleaned&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;raw_name&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;gsub&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;INVALID_CHARS&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'_'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="n"&gt;cleaned&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;strip&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;empty?&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="s2"&gt;"未命名节目"&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;cleaned&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;strip&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="c1"&gt;# 处理文件重名&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;resolve_save_path&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;desktop&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;base_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ext&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="n"&gt;base_filename&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;base_name&lt;/span&gt;&lt;span class="si"&gt;}#{&lt;/span&gt;&lt;span class="n"&gt;ext&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
  &lt;span class="n"&gt;base_path&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;File&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;desktop&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;base_filename&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;base_path&lt;/span&gt; &lt;span class="k"&gt;unless&lt;/span&gt; &lt;span class="no"&gt;File&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;exist?&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;base_path&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

  &lt;span class="n"&gt;counter&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
  &lt;span class="kp"&gt;loop&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="n"&gt;new_filename&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;base_name&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;_&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;counter&lt;/span&gt;&lt;span class="si"&gt;}#{&lt;/span&gt;&lt;span class="n"&gt;ext&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
    &lt;span class="n"&gt;new_path&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;File&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;desktop&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;new_filename&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;new_path&lt;/span&gt; &lt;span class="k"&gt;unless&lt;/span&gt; &lt;span class="no"&gt;File&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;exist?&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;new_path&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;counter&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="c1"&gt;# 下载文件（单独提取为方法，支持传入不同URL重试）&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;download_with_url&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;save_path&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="n"&gt;uri&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;URI&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="nb"&gt;puts&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;📥 开始下载（地址：&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;split&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'/'&lt;/span&gt;&lt;span class="p"&gt;)[&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;）：&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="no"&gt;File&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;basename&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;save_path&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
  &lt;span class="nb"&gt;puts&lt;/span&gt; &lt;span class="s2"&gt;"📌 保存路径：&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;save_path&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;

  &lt;span class="n"&gt;http&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Net&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;HTTP&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;uri&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;host&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;uri&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;port&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;use_ssl&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;uri&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;scheme&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s1"&gt;'https'&lt;/span&gt;
  &lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;open_timeout&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;TIMEOUT_SEC&lt;/span&gt;
  &lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;read_timeout&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;TIMEOUT_SEC&lt;/span&gt;

  &lt;span class="n"&gt;total_size&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kp"&gt;nil&lt;/span&gt;
  &lt;span class="k"&gt;begin&lt;/span&gt;
    &lt;span class="n"&gt;head_response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;request&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;Net&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;HTTP&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Head&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;uri&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="n"&gt;total_size&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;head_response&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'Content-Length'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;to_i&lt;/span&gt;
  &lt;span class="k"&gt;rescue&lt;/span&gt;
    &lt;span class="nb"&gt;puts&lt;/span&gt; &lt;span class="s2"&gt;"⚠️ 无法获取文件大小，将继续下载..."&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="n"&gt;downloaded_size&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
  &lt;span class="no"&gt;File&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;save_path&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'wb'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;file&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
    &lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;request&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;Net&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;HTTP&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Get&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;uri&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
      &lt;span class="k"&gt;unless&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;is_a?&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;Net&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;HTTPSuccess&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="s2"&gt;"下载请求失败，状态码：&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;code&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
      &lt;span class="k"&gt;end&lt;/span&gt;

      &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;read_body&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;chunk&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
        &lt;span class="n"&gt;file&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;write&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;chunk&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;downloaded_size&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="n"&gt;chunk&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;size&lt;/span&gt;

        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;total_size&lt;/span&gt;
          &lt;span class="n"&gt;progress&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;downloaded_size&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;to_f&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="n"&gt;total_size&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;round&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
          &lt;span class="nb"&gt;print&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\r&lt;/span&gt;&lt;span class="s2"&gt;⏳ 下载进度：&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;progress&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;% （&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;format_size&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;downloaded_size&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;format_size&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;total_size&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;）"&lt;/span&gt;
        &lt;span class="k"&gt;else&lt;/span&gt;
          &lt;span class="nb"&gt;print&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\r&lt;/span&gt;&lt;span class="s2"&gt;⏳ 已下载：&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;format_size&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;downloaded_size&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
        &lt;span class="k"&gt;end&lt;/span&gt;
      &lt;span class="k"&gt;end&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="nb"&gt;puts&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;✅ 下载完成！文件已保存到桌面"&lt;/span&gt;
&lt;span class="k"&gt;rescue&lt;/span&gt; &lt;span class="no"&gt;Timeout&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Error&lt;/span&gt;
  &lt;span class="no"&gt;FileUtils&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;rm_f&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;save_path&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="s2"&gt;"下载超时（超过 &lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="no"&gt;TIMEOUT_SEC&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; 秒）"&lt;/span&gt;
&lt;span class="k"&gt;rescue&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;
  &lt;span class="no"&gt;FileUtils&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;rm_f&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;save_path&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="s2"&gt;"下载失败：&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;message&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="c1"&gt;# 格式化文件大小&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;format_size&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;bytes&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;bytes&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; B"&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;bytes&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;1024&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="s2"&gt;"%.2f KB"&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;bytes&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mf"&gt;1024.0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;bytes&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;1024&lt;/span&gt;&lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;
  &lt;span class="s2"&gt;"%.2f MB"&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;bytes&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1024&lt;/span&gt;&lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;to_f&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="c1"&gt;# 链接校验&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;get_target_url&lt;/span&gt;
  &lt;span class="kp"&gt;loop&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="nb"&gt;print&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;请输入小宇宙播客详情页链接（例如：https://www.xiaoyuzhoufm.com/episode/xxx）："&lt;/span&gt;
    &lt;span class="n"&gt;url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;gets&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;chomp&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;empty?&lt;/span&gt;
      &lt;span class="nb"&gt;puts&lt;/span&gt; &lt;span class="s2"&gt;"⚠️ 链接不能为空，请重新输入！"&lt;/span&gt;
      &lt;span class="k"&gt;next&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;

    &lt;span class="k"&gt;begin&lt;/span&gt;
      &lt;span class="n"&gt;uri&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;URI&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="k"&gt;unless&lt;/span&gt; &lt;span class="n"&gt;uri&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;scheme&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="sx"&gt;%w[http https]&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;include?&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;uri&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;scheme&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="n"&gt;uri&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;host&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;include?&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'xiaoyuzhoufm.com'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="s2"&gt;"格式错误"&lt;/span&gt;
      &lt;span class="k"&gt;end&lt;/span&gt;
    &lt;span class="k"&gt;rescue&lt;/span&gt;
      &lt;span class="nb"&gt;puts&lt;/span&gt; &lt;span class="s2"&gt;"⚠️ 链接格式无效（需包含 http/https 且为小宇宙域名），请重新输入！"&lt;/span&gt;
      &lt;span class="k"&gt;next&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;url&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="c1"&gt;# 请求页面HTML&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;fetch_page_html&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="n"&gt;uri&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;URI&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="nb"&gt;puts&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;🔍 正在请求目标页面：&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;

  &lt;span class="n"&gt;http&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Net&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;HTTP&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;uri&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;host&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;uri&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;port&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;use_ssl&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;uri&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;scheme&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s1"&gt;'https'&lt;/span&gt;
  &lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;open_timeout&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;30&lt;/span&gt;
  &lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;read_timeout&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;30&lt;/span&gt;

  &lt;span class="n"&gt;request&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Net&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;HTTP&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Get&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;uri&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'User-Agent'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/128.0.0.0 Safari/537.36'&lt;/span&gt;
  &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'Accept'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8'&lt;/span&gt;
  &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'Referer'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'https://www.xiaoyuzhoufm.com/'&lt;/span&gt;

  &lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;request&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

  &lt;span class="k"&gt;unless&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;code&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;to_i&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;200&lt;/span&gt;
    &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="s2"&gt;"页面请求失败，状态码：&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;code&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;（可能链接无效或被拦截）"&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;body&lt;/span&gt;
&lt;span class="k"&gt;rescue&lt;/span&gt; &lt;span class="no"&gt;Timeout&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Error&lt;/span&gt;
  &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="s2"&gt;"网络请求超时（超过 30 秒），请检查网络连接"&lt;/span&gt;
&lt;span class="k"&gt;rescue&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;
  &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="s2"&gt;"网络请求异常：&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;message&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="c1"&gt;# 提取__NEXT_DATA__&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;extract_next_data&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;html&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="n"&gt;match_result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;html&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;match&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;NEXT_DATA_REGEX&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

  &lt;span class="k"&gt;unless&lt;/span&gt; &lt;span class="n"&gt;match_result&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="n"&gt;match_result&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="s2"&gt;"未在页面中找到 __NEXT_DATA__ 字段（可能页面结构已变更）"&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="n"&gt;match_result&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;strip&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="c1"&gt;# 提取节目名称&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;extract_episode_title&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;next_data_json&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;begin&lt;/span&gt;
    &lt;span class="n"&gt;next_data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;next_data_json&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;rescue&lt;/span&gt; &lt;span class="no"&gt;JSON&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;ParserError&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;
    &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="s2"&gt;"JSON 解析失败：&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;message&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;（无法提取节目名称）"&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="n"&gt;current&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;next_data&lt;/span&gt;
  &lt;span class="no"&gt;TITLE_PATH&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;each_with_index&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;index&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
    &lt;span class="k"&gt;unless&lt;/span&gt; &lt;span class="n"&gt;current&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;is_a?&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;Hash&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="n"&gt;current&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;key?&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="n"&gt;missing_path&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;TITLE_PATH&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="o"&gt;..&lt;/span&gt;&lt;span class="n"&gt;index&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;' -&amp;gt; '&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="s2"&gt;"未找到节目名称，缺失层级：&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;missing_path&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;
    &lt;span class="n"&gt;current&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;current&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="k"&gt;unless&lt;/span&gt; &lt;span class="n"&gt;current&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;is_a?&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;String&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="n"&gt;current&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;strip&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;empty?&lt;/span&gt;
    &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="s2"&gt;"提取的节目名称无效：&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;current&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;inspect&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="n"&gt;current&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;strip&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="c1"&gt;# 提取media字段&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;extract_media_field&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;next_data_json&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;begin&lt;/span&gt;
    &lt;span class="n"&gt;next_data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;next_data_json&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;rescue&lt;/span&gt; &lt;span class="no"&gt;JSON&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;ParserError&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;
    &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="s2"&gt;"JSON 解析失败：&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;message&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="n"&gt;current&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;next_data&lt;/span&gt;
  &lt;span class="no"&gt;MEDIA_PATH&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;each_with_index&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;index&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
    &lt;span class="k"&gt;unless&lt;/span&gt; &lt;span class="n"&gt;current&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;is_a?&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;Hash&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="n"&gt;current&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;key?&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="n"&gt;missing_path&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;MEDIA_PATH&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="o"&gt;..&lt;/span&gt;&lt;span class="n"&gt;index&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;' -&amp;gt; '&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="s2"&gt;"未找到media字段，缺失层级：&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;missing_path&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;
    &lt;span class="n"&gt;current&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;current&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="n"&gt;current&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="c1"&gt;# 提取主下载地址&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;extract_download_url&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;media_data&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;unless&lt;/span&gt; &lt;span class="n"&gt;media_data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;is_a?&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;Hash&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="s2"&gt;"media字段格式异常（不是哈希类型）"&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="n"&gt;current&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;media_data&lt;/span&gt;
  &lt;span class="no"&gt;SOURCE_URL_PATH&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;each_with_index&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;index&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
    &lt;span class="k"&gt;unless&lt;/span&gt; &lt;span class="n"&gt;current&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;is_a?&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;Hash&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="n"&gt;current&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;key?&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="n"&gt;full_missing_path&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"media"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="no"&gt;SOURCE_URL_PATH&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="o"&gt;..&lt;/span&gt;&lt;span class="n"&gt;index&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
      &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="s2"&gt;"未找到主下载地址，缺失层级：&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;full_missing_path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;' -&amp;gt; '&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;
    &lt;span class="n"&gt;current&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;current&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="n"&gt;validate_url&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;current&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="n"&gt;current&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="c1"&gt;# 新增：提取备用下载地址（不抛出异常，不存在则返回nil）&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;extract_backup_download_url&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;media_data&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kp"&gt;nil&lt;/span&gt; &lt;span class="k"&gt;unless&lt;/span&gt; &lt;span class="n"&gt;media_data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;is_a?&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;Hash&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

  &lt;span class="n"&gt;current&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;media_data&lt;/span&gt;
  &lt;span class="no"&gt;BACKUP_SOURCE_URL_PATH&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;each_with_index&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;index&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
    &lt;span class="c1"&gt;# 备用地址缺失时不报错，直接返回nil&lt;/span&gt;
    &lt;span class="k"&gt;unless&lt;/span&gt; &lt;span class="n"&gt;current&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;is_a?&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;Hash&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="n"&gt;current&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;key?&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="nb"&gt;puts&lt;/span&gt; &lt;span class="s2"&gt;"ℹ️ 未找到备用下载地址（缺失层级：media -&amp;gt; &lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="no"&gt;BACKUP_SOURCE_URL_PATH&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="o"&gt;..&lt;/span&gt;&lt;span class="n"&gt;index&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;' -&amp;gt; '&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;）"&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kp"&gt;nil&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;
    &lt;span class="n"&gt;current&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;current&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="k"&gt;begin&lt;/span&gt;
    &lt;span class="n"&gt;validate_url&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;current&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;current&lt;/span&gt;
  &lt;span class="k"&gt;rescue&lt;/span&gt;
    &lt;span class="nb"&gt;puts&lt;/span&gt; &lt;span class="s2"&gt;"ℹ️ 备用下载地址格式无效：&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;current&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
    &lt;span class="kp"&gt;nil&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="c1"&gt;# 校验URL格式&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;validate_url&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;is_a?&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;String&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;strip&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;empty?&lt;/span&gt;

  &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="s2"&gt;"URL格式无效（非字符串或为空）：&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;inspect&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;

  &lt;span class="n"&gt;uri&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;URI&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;unless&lt;/span&gt; &lt;span class="n"&gt;uri&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;scheme&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="sx"&gt;%w[http https]&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;include?&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;uri&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;scheme&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="s2"&gt;"URL协议错误（需http/https）：&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;rescue&lt;/span&gt;
  &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="s2"&gt;"URL解析失败：&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="c1"&gt;# 主执行流程&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;main&lt;/span&gt;
  &lt;span class="nb"&gt;puts&lt;/span&gt; &lt;span class="s2"&gt;"======================================"&lt;/span&gt;
  &lt;span class="nb"&gt;puts&lt;/span&gt; &lt;span class="s2"&gt;"小宇宙播客下载脚本（支持备用地址重试）"&lt;/span&gt;
  &lt;span class="nb"&gt;puts&lt;/span&gt; &lt;span class="s2"&gt;"功能：主地址失败自动尝试备用地址 → 提取节目名称 → 下载到桌面"&lt;/span&gt;
  &lt;span class="nb"&gt;puts&lt;/span&gt; &lt;span class="s2"&gt;"主地址路径：media -&amp;gt; source -&amp;gt; url"&lt;/span&gt;
  &lt;span class="nb"&gt;puts&lt;/span&gt; &lt;span class="s2"&gt;"备用地址路径：media -&amp;gt; backupSource -&amp;gt; url"&lt;/span&gt;
  &lt;span class="nb"&gt;puts&lt;/span&gt; &lt;span class="s2"&gt;"======================================"&lt;/span&gt;

  &lt;span class="k"&gt;begin&lt;/span&gt;
    &lt;span class="n"&gt;target_url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;get_target_url&lt;/span&gt;
    &lt;span class="n"&gt;page_html&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;fetch_page_html&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;target_url&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;next_data_json&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;extract_next_data&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;page_html&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;episode_title&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;extract_episode_title&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;next_data_json&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nb"&gt;puts&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;📌 提取到节目名称：&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;episode_title&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;

    &lt;span class="n"&gt;media_data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;extract_media_field&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;next_data_json&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;main_url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;extract_download_url&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;media_data&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nb"&gt;puts&lt;/span&gt; &lt;span class="s2"&gt;"📥 提取到主下载地址：&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;main_url&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;split&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'/'&lt;/span&gt;&lt;span class="p"&gt;)[&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;

    &lt;span class="c1"&gt;# 提取备用地址（不中断流程）&lt;/span&gt;
    &lt;span class="n"&gt;backup_url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;extract_backup_download_url&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;media_data&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nb"&gt;puts&lt;/span&gt; &lt;span class="s2"&gt;"📥 提取到备用下载地址：&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;backup_url&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;split&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'/'&lt;/span&gt;&lt;span class="p"&gt;)[&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;backup_url&lt;/span&gt;

    &lt;span class="c1"&gt;# 处理文件名和路径&lt;/span&gt;
    &lt;span class="n"&gt;cleaned_title&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;clean_filename&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;episode_title&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;file_ext&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;extract_file_extension&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;main_url&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;# 用主地址的扩展名（备用地址通常格式一致）&lt;/span&gt;
    &lt;span class="n"&gt;desktop&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;desktop_path&lt;/span&gt;
    &lt;span class="n"&gt;save_path&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;resolve_save_path&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;desktop&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;cleaned_title&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;file_ext&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="c1"&gt;# 先尝试主地址下载&lt;/span&gt;
    &lt;span class="k"&gt;begin&lt;/span&gt;
      &lt;span class="n"&gt;download_with_url&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;main_url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;save_path&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;rescue&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;
      &lt;span class="nb"&gt;puts&lt;/span&gt; &lt;span class="s2"&gt;"⚠️ 主地址下载失败：&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;message&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
      &lt;span class="c1"&gt;# 主地址失败且有备用地址时重试&lt;/span&gt;
      &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;backup_url&lt;/span&gt;
        &lt;span class="nb"&gt;puts&lt;/span&gt; &lt;span class="s2"&gt;"🔄 尝试使用备用地址下载..."&lt;/span&gt;
        &lt;span class="n"&gt;download_with_url&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;backup_url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;save_path&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="k"&gt;else&lt;/span&gt;
        &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="s2"&gt;"主地址下载失败，且无可用备用地址"&lt;/span&gt;
      &lt;span class="k"&gt;end&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="k"&gt;rescue&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;
    &lt;span class="nb"&gt;puts&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;❌ 执行失败：&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;message&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
    &lt;span class="nb"&gt;exit&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="nb"&gt;puts&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;🎉 操作完成！"&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="c1"&gt;# 启动脚本&lt;/span&gt;
&lt;span class="n"&gt;main&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;</description>
      <author>AixCoder</author>
      <pubDate>Fri, 24 Oct 2025 19:22:52 +0800</pubDate>
      <link>https://ruby-china.org/topics/44353</link>
      <guid>https://ruby-china.org/topics/44353</guid>
    </item>
    <item>
      <title>新人想学 Ruby，大家伙从业多年的来给些意见呢，谢谢</title>
      <description>&lt;p&gt;本人会一点 iOS 开发，有想法学习 Ruby
已经有多年 Ruby 开发经验的各位能否给出一些意见，劝学也好，劝退也好😁
还是很想听听已经在圈内的人士，问问大家如何看待新手入坑 Ruby
相信能给出一些更为实用的建议&lt;/p&gt;</description>
      <author>AixCoder</author>
      <pubDate>Fri, 20 Jun 2025 19:02:43 +0800</pubDate>
      <link>https://ruby-china.org/topics/44202</link>
      <guid>https://ruby-china.org/topics/44202</guid>
    </item>
  </channel>
</rss>
