<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0"
  xmlns:atom="http://www.w3.org/2005/Atom"
  xmlns:content="http://purl.org/rss/1.0/modules/content/">
  <channel>
    <title>Eucalyptus</title>
    <link>https://blog.eucalyptus.cc/</link>
    
    <image>
      <url>https://blog.eucalyptus.cc/icon.png</url>
      <title>Eucalyptus</title>
      <link>https://blog.eucalyptus.cc/</link>
    </image>
    
    <atom:link href="https://blog.eucalyptus.cc/rss.xml" rel="self" type="application/rss+xml"/>
    
    <description>一个记录建站折腾历程与生活碎片的个人博客，分享技术踩坑心得、工具推荐，以及日常里的微小确幸。在这里，代码与烟火气并存。</description>
    <pubDate>Thu, 16 Jul 2026 14:16:33 GMT</pubDate>
    <generator>http://hexo.io/</generator>
    
    <item>
      <title>基于 Node.js + 大模型的 AI 客服系统</title>
      <link>https://blog.eucalyptus.cc/2026/06/28/%E5%9F%BA%E4%BA%8E-Node-js-%E5%A4%A7%E6%A8%A1%E5%9E%8B%E7%9A%84-AI-%E5%AE%A2%E6%9C%8D%E7%B3%BB%E7%BB%9F/</link>
      <guid>https://blog.eucalyptus.cc/2026/06/28/%E5%9F%BA%E4%BA%8E-Node-js-%E5%A4%A7%E6%A8%A1%E5%9E%8B%E7%9A%84-AI-%E5%AE%A2%E6%9C%8D%E7%B3%BB%E7%BB%9F/</guid>
      <pubDate>Sun, 28 Jun 2026 11:26:54 GMT</pubDate>
      
        
        
          
          
      <description>&lt;h2 id=&quot;引言&quot;&gt;&lt;a href=&quot;#引言&quot; class=&quot;headerlink&quot;</description>
          
        
      
      
      
      <content:encoded><![CDATA[<h2 id="引言"><a href="#引言" class="headerlink" title="引言"></a>引言</h2><ul><li>痛点引入：博客文章越来越多，读者找不到想看的内容；评论区冷清，互动少；自己没时间回复每一条留言。</li><li>解决方案：给博客装一个 AI 智能助手，24 小时在线，能聊天、能搜文章、能回答问题。</li></ul><!-- 这是一张图片，ocr 内容为： --><p><img src="https://img.eucalyptus.cc/images/20260628/1.avif"></p><h2 id="项目介绍"><a href="#项目介绍" class="headerlink" title="项目介绍"></a>项目介绍</h2><p>核心功能：</p><ul><li>智能对话（基于大语言模型）</li><li>博客文章搜索与推荐</li><li>FAQ 知识库自动匹配</li><li>支持多模型切换（聊天&#x2F;图像生成）</li><li>限流保护，防止滥用</li></ul><p>技术栈：Node.js + Express + Axios + 大模型 API</p><h2 id="关键技术实现"><a href="#关键技术实现" class="headerlink" title="关键技术实现"></a>关键技术实现</h2><h3 id="文章索引与搜索"><a href="#文章索引与搜索" class="headerlink" title="文章索引与搜索"></a>文章索引与搜索</h3><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 从 Hexo 的 _posts 目录读取文章</span></span><br><span class="line"><span class="comment">// 解析 front-matter（标题、日期、标签、分类）</span></span><br><span class="line"><span class="comment">// 构建可搜索的文章对象数组</span></span><br><span class="line"><span class="keyword">const</span> <span class="variable constant_">POSTS_DIR</span> = <span class="string">&#x27;/www/wwwroot/myblog/source/_posts&#x27;</span>;</span><br></pre></td></tr></table></figure><p>亮点：直接读取源文件，无需数据库，部署简单。</p><h3 id="FAQ-知识库设计"><a href="#FAQ-知识库设计" class="headerlink" title="FAQ 知识库设计"></a>FAQ 知识库设计</h3><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// JSON 格式存储常见问题</span></span><br><span class="line"><span class="comment">// 关键词匹配 + 评分机制</span></span><br><span class="line"><span class="comment">// 匹配成功直接返回，无需调用大模型（省钱+快速）</span></span><br><span class="line"><span class="keyword">const</span> <span class="variable constant_">FAQ_FILE</span> = path.<span class="title function_">join</span>(__dirname, <span class="string">&#x27;faq.json&#x27;</span>);</span><br></pre></td></tr></table></figure><p>亮点：80% 的常见问题走 FAQ，只有复杂问题才调用大模型。</p><h3 id="多模型支持"><a href="#多模型支持" class="headerlink" title="多模型支持"></a>多模型支持</h3><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 支持聊天模型 + 图像生成模型</span></span><br><span class="line"><span class="comment">// 用户可切换模型</span></span><br><span class="line"><span class="comment">// 统一接口封装</span></span><br><span class="line">#ai-chatbot-input-area</span><br><span class="line">  #ai-chatbot-model-menu</span><br><span class="line">    div.<span class="property">ai</span>-chatbot-model-<span class="title function_">item</span>(data-model=<span class="string">&quot;sensenova-6.7-flash-lite&quot;</span> data-type=<span class="string">&quot;chat&quot;</span> onclick=<span class="string">&quot;selectModel(&#x27;sensenova-6.7-flash-lite&#x27;, &#x27;Sensenova 6.7 Lite&#x27;, &#x27;chat&#x27;)&quot;</span>)</span><br><span class="line">      span <span class="title class_">Sensenova</span> <span class="number">6.7</span> <span class="title class_">Lite</span></span><br><span class="line">      span.<span class="property">ai</span>-chatbot-model-desc 文本对话</span><br><span class="line">    div.<span class="property">ai</span>-chatbot-model-<span class="title function_">item</span>(data-model=<span class="string">&quot;sensenova-u1-fast&quot;</span> data-type=<span class="string">&quot;image&quot;</span> onclick=<span class="string">&quot;selectModel(&#x27;sensenova-u1-fast&#x27;, &#x27;Sensenova U1&#x27;, &#x27;image&#x27;)&quot;</span>)</span><br><span class="line">      span <span class="title class_">Sensenova</span> <span class="variable constant_">U1</span></span><br><span class="line">      span.<span class="property">ai</span>-chatbot-model-desc 图像生成</span><br><span class="line">    div.<span class="property">ai</span>-chatbot-model-<span class="title function_">item</span>(data-model=<span class="string">&quot;deepseek-v4-flash&quot;</span> data-type=<span class="string">&quot;chat&quot;</span> onclick=<span class="string">&quot;selectModel(&#x27;deepseek-v4-flash&#x27;, &#x27;DeepSeek V4&#x27;, &#x27;chat&#x27;)&quot;</span>)</span><br><span class="line">      span <span class="title class_">DeepSeek</span> <span class="variable constant_">V4</span></span><br><span class="line">      span.<span class="property">ai</span>-chatbot-model-desc 深度对话</span><br></pre></td></tr></table></figure><h3 id="限流与安全防护"><a href="#限流与安全防护" class="headerlink" title="限流与安全防护"></a>限流与安全防护</h3><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// IP 级别：60 秒内最多 20 次请求</span></span><br><span class="line"><span class="comment">// 防止 API Key 被刷爆</span></span><br><span class="line"><span class="comment">// 错误处理与降级</span></span><br><span class="line"><span class="keyword">function</span> <span class="title function_">getClientIP</span>(<span class="params">req</span>) &#123;</span><br><span class="line">  <span class="keyword">return</span> req.<span class="property">headers</span>[<span class="string">&#x27;x-forwarded-for&#x27;</span>]?.<span class="title function_">split</span>(<span class="string">&#x27;,&#x27;</span>)[<span class="number">0</span>]?.<span class="title function_">trim</span>() || </span><br><span class="line">         req.<span class="property">connection</span>?.<span class="property">remoteAddress</span> || </span><br><span class="line">         req.<span class="property">socket</span>?.<span class="property">remoteAddress</span>;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> <span class="variable constant_">RATE_LIMIT</span> = &#123;&#125;; </span><br><span class="line"><span class="keyword">const</span> <span class="variable constant_">RATE_LIMIT_WINDOW</span> = <span class="number">60000</span>;</span><br><span class="line"><span class="keyword">const</span> <span class="variable constant_">RATE_LIMIT_MAX</span> = <span class="number">20</span>;      </span><br></pre></td></tr></table></figure><hr><h2 id="前端集成"><a href="#前端集成" class="headerlink" title="前端集成"></a>前端集成</h2><ul><li>聊天窗口的 UI 设计</li><li>位置控制</li><li>与 Hexo 主题的融合</li><li>调用后端 API 的 JavaScript 代码</li></ul><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 发送消息到后端</span></span><br><span class="line"><span class="keyword">async</span> <span class="keyword">function</span> <span class="title function_">sendMessage</span>(<span class="params">message</span>) &#123;</span><br><span class="line">  <span class="keyword">const</span> response = <span class="keyword">await</span> <span class="title function_">fetch</span>(<span class="string">&#x27;https://your-api.com/api/chat&#x27;</span>, &#123;</span><br><span class="line">    <span class="attr">method</span>: <span class="string">&#x27;POST&#x27;</span>,</span><br><span class="line">    <span class="attr">headers</span>: &#123; <span class="string">&#x27;Content-Type&#x27;</span>: <span class="string">&#x27;application/json&#x27;</span> &#125;,</span><br><span class="line">    <span class="attr">body</span>: <span class="title class_">JSON</span>.<span class="title function_">stringify</span>(&#123; message, <span class="attr">history</span>: chatHistory &#125;)</span><br><span class="line">  &#125;);</span><br><span class="line">  <span class="keyword">const</span> data = <span class="keyword">await</span> response.<span class="title function_">json</span>();</span><br><span class="line">  <span class="keyword">return</span> data.<span class="property">reply</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><hr><h2 id="部署与运维"><a href="#部署与运维" class="headerlink" title="部署与运维"></a>部署与运维</h2><h3 id="部署方式"><a href="#部署方式" class="headerlink" title="部署方式"></a>部署方式</h3><table><thead><tr><th>方式</th><th>适用场景</th><th>命令</th></tr></thead><tbody><tr><td>本地开发</td><td>调试测试</td><td><code>node server.js</code></td></tr><tr><td>服务器部署</td><td>生产环境</td><td><code>pm2 start server.js</code></td></tr></tbody></table><h3 id="环境变量配置"><a href="#环境变量配置" class="headerlink" title="环境变量配置"></a>环境变量配置</h3><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">API_KEY=your-api-key</span><br><span class="line">BOT_NAME=AI助手</span><br><span class="line">BOT_AVATAR=https://your-avatar-url</span><br><span class="line">SERVICE_PORT=5004</span><br></pre></td></tr></table></figure><h3 id="自动重启与监控"><a href="#自动重启与监控" class="headerlink" title="自动重启与监控"></a>自动重启与监控</h3><ul><li>使用 <code>pm2</code> 进程管理</li><li>配合 Nginx 反向代理</li><li>日志轮转</li></ul><hr><h2 id="效果展示"><a href="#效果展示" class="headerlink" title="效果展示"></a>效果展示</h2><h3 id="对话示例"><a href="#对话示例" class="headerlink" title="对话示例"></a>对话示例</h3><p>用户：推荐一些MySQL相关的文章<br>用户：你是谁  </p><!-- 这是一张图片，ocr 内容为： --><p><img src="https://img.eucalyptus.cc/images/20260628/2.avif"></p><p>用户：樱花纷飞的放学路上，两个身影隔着三米的距离一前一后走着。女生抱着书包，马尾辫随着步伐轻轻晃动，耳朵尖泛着可疑的红。男生双手插兜，假装在看远处的天空，却每隔几秒就用余光偷瞄前面的背影。一阵风吹过，樱花瓣纷纷扬扬落在两人之间，像一条粉色的河。画面定格在女生突然停下脚步、男生差点撞上去的刹那，两人错愕的对视中，一片花瓣恰好停在女生的睫毛上。背景虚化，前景花瓣清晰，浪漫氛围拉满。</p><!-- 这是一张图片，ocr 内容为： --><p><img src="https://img.eucalyptus.cc/images/20260628/3.avif"></p><h3 id="性能数据"><a href="#性能数据" class="headerlink" title="性能数据"></a>性能数据</h3><ul><li>平均响应时间：~500ms（FAQ）&#x2F; ~2s（大模型）</li><li>支持并发：取决于 API 限额</li><li>文章索引：自动加载，无需手动更新</li></ul><h2 id="遇到的问题与解决方案"><a href="#遇到的问题与解决方案" class="headerlink" title="遇到的问题与解决方案"></a>遇到的问题与解决方案</h2><table><thead><tr><th>问题</th><th>解决方案</th></tr></thead><tbody><tr><td>大模型 API 费用高</td><td>FAQ 优先匹配，减少 API 调用</td></tr><tr><td>文章更新后索引不同步</td><td>提供 <code>/api/reload</code> 接口手动刷新</td></tr><tr><td>前端样式与主题冲突</td><td>使用 CSS 变量，适配 Anzhiyu 主题</td></tr><tr><td>响应慢</td><td>增加 timeout 处理，超时返回友好提示</td></tr></tbody></table><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><ul><li>项目价值：让博客从”静态展示”变成”动态交互”</li><li>学习收获：Node.js 服务开发、大模型 API 调用、全文检索思路</li><li>体验地址：<a href="https://blog.eucalyptus.cc/">blog.eucalyptus.cc</a></li></ul>]]></content:encoded>
      
      
      <category domain="https://blog.eucalyptus.cc/categories/AI/">AI</category>
      
      
      <category domain="https://blog.eucalyptus.cc/tags/ChatBot/">ChatBot</category>
      
      
      <comments>https://blog.eucalyptus.cc/2026/06/28/%E5%9F%BA%E4%BA%8E-Node-js-%E5%A4%A7%E6%A8%A1%E5%9E%8B%E7%9A%84-AI-%E5%AE%A2%E6%9C%8D%E7%B3%BB%E7%BB%9F/#disqus_thread</comments>
      
    </item>
    
    <item>
      <title>Go + Redis：本地部署高性能图片主色调提取服务</title>
      <link>https://blog.eucalyptus.cc/2026/06/10/Go-Redis%EF%BC%9A%E6%9C%AC%E5%9C%B0%E9%83%A8%E7%BD%B2%E9%AB%98%E6%80%A7%E8%83%BD%E5%9B%BE%E7%89%87%E4%B8%BB%E8%89%B2%E8%B0%83%E6%8F%90%E5%8F%96%E6%9C%8D%E5%8A%A1/</link>
      <guid>https://blog.eucalyptus.cc/2026/06/10/Go-Redis%EF%BC%9A%E6%9C%AC%E5%9C%B0%E9%83%A8%E7%BD%B2%E9%AB%98%E6%80%A7%E8%83%BD%E5%9B%BE%E7%89%87%E4%B8%BB%E8%89%B2%E8%B0%83%E6%8F%90%E5%8F%96%E6%9C%8D%E5%8A%A1/</guid>
      <pubDate>Wed, 10 Jun 2026 11:32:58 GMT</pubDate>
      
        
        
          
          
      <description>&lt;p&gt;部署 &lt;code&gt;img2color-go&lt;/code&gt; 所需的环境非常轻量，主要包含的核心组件&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;go语言环境编译用（Go 1.21+ (建议最新稳定版)。）&lt;/li&gt;
&lt;li&gt;docker拉起redis容器缓存用（Docker 20.10+,</description>
          
        
      
      
      
      <content:encoded><![CDATA[<p>部署 <code>img2color-go</code> 所需的环境非常轻量，主要包含的核心组件</p><ul><li>go语言环境编译用（Go 1.21+ (建议最新稳定版)。）</li><li>docker拉起redis容器缓存用（Docker 20.10+, Docker Compose V2。）</li></ul><h2 id="拉起Redis容器做缓存"><a href="#拉起Redis容器做缓存" class="headerlink" title="拉起Redis容器做缓存"></a>拉起Redis容器做缓存</h2><p>在 <code>/val/lib/docker/redis-img2color</code>目录下创建 <code>docker-compose.yml</code>：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line">services:</span><br><span class="line">  redis-img2color:</span><br><span class="line">    image: redis:7-alpine</span><br><span class="line">    container_name: redis-img2color</span><br><span class="line">    restart: always</span><br><span class="line">    ports:</span><br><span class="line">      - &quot;127.0.0.1:6380:6379&quot;</span><br><span class="line">    volumes:</span><br><span class="line">      - /val/lib/docker/redis-img2color:/data</span><br><span class="line">    command: redis-server --appendonly yes</span><br></pre></td></tr></table></figure><p>执行启动：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">docker compose up -d</span><br></pre></td></tr></table></figure><h2 id="克隆代码"><a href="#克隆代码" class="headerlink" title="克隆代码"></a>克隆代码</h2><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">git clone https://github.com/anzhiyu-c/img2color-go.git</span><br><span class="line">cd img2color-go</span><br></pre></td></tr></table></figure><h2 id="修改包名：api-img2color-go"><a href="#修改包名：api-img2color-go" class="headerlink" title="修改包名：api&#x2F;img2color.go"></a>修改包名：api&#x2F;img2color.go</h2><h3 id="为什么要改？"><a href="#为什么要改？" class="headerlink" title="为什么要改？"></a>为什么要改？</h3><ul><li>原代码 (<code>package handler</code>)：这是为 Vercel&#x2F;Serverless 环境设计的“库”模式，它没有 <code>main</code> 函数入口，直接 <code>go build</code> 会报错或者生成无法直接运行的库</li><li>修改后 (<code>package main</code>)：这告诉 Go 编译器这是一个独立的可执行程序。配合我们添加的 <code>main</code> 函数（或原有的入口逻辑），才能编译出一个可以在服务器上直接运行的二进制文件 (<code>img2color-server</code>)。</li></ul><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"># 将 api/img2color.go 第一行的 package handler 替换为 package main</span><br><span class="line">sed -i &#x27;1s/package handler/package main/&#x27; api/img2color.go</span><br><span class="line"></span><br><span class="line"># 验证一下是否修改成功</span><br><span class="line">head -n 1 api/img2color.go</span><br></pre></td></tr></table></figure><h2 id="配置环境变量"><a href="#配置环境变量" class="headerlink" title="配置环境变量"></a>配置环境变量</h2><p>创建&#x2F;修改 <code>.env</code> 文件：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line">PORT=9000</span><br><span class="line">REDIS_ADDRESS=127.0.0.1:6380</span><br><span class="line">REDIS_PASSWORD=</span><br><span class="line">USE_REDIS_CACHE=true</span><br><span class="line">REDIS_DB=0</span><br><span class="line">USE_MONGODB=false</span><br><span class="line">MONGO_URI=</span><br><span class="line">MONGO_DB=</span><br><span class="line">ALLOWED_REFERERS=</span><br></pre></td></tr></table></figure><h2 id="编译-Go-程序"><a href="#编译-Go-程序" class="headerlink" title="编译 Go 程序"></a>编译 Go 程序</h2><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">cd /opt/img2color-go</span><br><span class="line">go mod tidy</span><br><span class="line">go build -o img2color-server ./api/</span><br><span class="line">chmod +x img2color-server</span><br></pre></td></tr></table></figure><h2 id="配置-Systemd-服务"><a href="#配置-Systemd-服务" class="headerlink" title="配置 Systemd 服务"></a>配置 Systemd 服务</h2><p>创建 <code>/etc/systemd/system/img2color.service</code>：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line">[Unit]</span><br><span class="line">Description=img2color-go Service</span><br><span class="line">After=network.target docker.service</span><br><span class="line"></span><br><span class="line">[Service]</span><br><span class="line">Type=simple</span><br><span class="line">User=root</span><br><span class="line">WorkingDirectory=/opt/img2color-go</span><br><span class="line">ExecStart=/opt/img2color-go/img2color-server</span><br><span class="line">Restart=on-failure</span><br><span class="line"></span><br><span class="line">[Install]</span><br><span class="line">WantedBy=multi-user.target</span><br></pre></td></tr></table></figure><h2 id="启动并验证"><a href="#启动并验证" class="headerlink" title="启动并验证"></a>启动并验证</h2><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"># 重载配置并启动</span><br><span class="line">sudo systemctl daemon-reload</span><br><span class="line">sudo systemctl enable --now img2color</span><br><span class="line"></span><br><span class="line"># 查看日志确认连接成功</span><br><span class="line">sudo journalctl -u img2color -f</span><br></pre></td></tr></table></figure><h2 id="最终测试"><a href="#最终测试" class="headerlink" title="最终测试"></a>最终测试</h2><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">curl &quot;http://127.0.0.1:9000/api?img=https://avatars.githubusercontent.com/u/9919?v=4&quot;</span><br></pre></td></tr></table></figure><h2 id="反向代理"><a href="#反向代理" class="headerlink" title="反向代理"></a>反向代理</h2><p>后续可以配置个反向代理将ip+port换成域名，这里不做过多赘述</p>]]></content:encoded>
      
      
      <category domain="https://blog.eucalyptus.cc/categories/%E6%95%B0%E6%8D%AE%E5%BA%93%E6%8A%80%E6%9C%AF/">数据库技术</category>
      
      <category domain="https://blog.eucalyptus.cc/categories/%E6%95%B0%E6%8D%AE%E5%BA%93%E6%8A%80%E6%9C%AF/%E5%AE%B9%E5%99%A8/">容器</category>
      
      
      <category domain="https://blog.eucalyptus.cc/tags/Redis/">Redis</category>
      
      <category domain="https://blog.eucalyptus.cc/tags/Docker/">Docker</category>
      
      
      <comments>https://blog.eucalyptus.cc/2026/06/10/Go-Redis%EF%BC%9A%E6%9C%AC%E5%9C%B0%E9%83%A8%E7%BD%B2%E9%AB%98%E6%80%A7%E8%83%BD%E5%9B%BE%E7%89%87%E4%B8%BB%E8%89%B2%E8%B0%83%E6%8F%90%E5%8F%96%E6%9C%8D%E5%8A%A1/#disqus_thread</comments>
      
    </item>
    
    <item>
      <title>解决博客零评论困境：AI 评论机器人部署全记录</title>
      <link>https://blog.eucalyptus.cc/2026/06/08/%E8%A7%A3%E5%86%B3%E5%8D%9A%E5%AE%A2%E9%9B%B6%E8%AF%84%E8%AE%BA%E5%9B%B0%E5%A2%83%EF%BC%9AAI-%E8%AF%84%E8%AE%BA%E6%9C%BA%E5%99%A8%E4%BA%BA%E9%83%A8%E7%BD%B2%E5%85%A8%E8%AE%B0%E5%BD%95/</link>
      <guid>https://blog.eucalyptus.cc/2026/06/08/%E8%A7%A3%E5%86%B3%E5%8D%9A%E5%AE%A2%E9%9B%B6%E8%AF%84%E8%AE%BA%E5%9B%B0%E5%A2%83%EF%BC%9AAI-%E8%AF%84%E8%AE%BA%E6%9C%BA%E5%99%A8%E4%BA%BA%E9%83%A8%E7%BD%B2%E5%85%A8%E8%AE%B0%E5%BD%95/</guid>
      <pubDate>Mon, 08 Jun 2026 11:19:18 GMT</pubDate>
      
        
        
          
          
      <description>&lt;h2 id=&quot;前言&quot;&gt;&lt;a href=&quot;#前言&quot; class=&quot;headerlink&quot;</description>
          
        
      
      
      
      <content:encoded><![CDATA[<h2 id="前言"><a href="#前言" class="headerlink" title="前言"></a>前言</h2><p>写博客的人大多有过这种体验：精心打磨一篇文章发布后，评论区却长期空白。对读者而言，”零评论”往往意味着内容无人问津，反而降低了互动意愿；对博主来说，手动维护评论既耗时又难以持续，尤其是当站点积累了数十篇甚至上百篇文章后，逐篇补充评论几乎成了不可能完成的任务。</p><p>我目前维护的个人博客基于 Hexo 框架，使用 anzhiyu 主题，评论系统选用 Twikoo 并通过 Docker 独立部署。随着文章数量增加，我逐渐意识到需要一个自动化的方案来解决评论冷启动问题——不是批量注册马甲账号刷量，而是让 AI 以真实读者的视角参与讨论，生成有实质内容、角度各异的评论，从而营造自然的社区氛围。</p><p>这个方案的核心思路是：利用大模型能力生成拟人化评论内容，同时通过模拟真实用户的浏览器 UA、IP 地址、Gravatar 头像等细节，让 AI 评论在数据层面与真实用户无异。系统不修改 Hexo 主题或 Twikoo 源码，而是以独立 Python 脚本的形式运行，通过读取 Twikoo 本地数据库获取现有评论，再经 HTTPS 接口提交新评论或回复。</p><p>本文将完整记录该系统的部署过程，从环境准备、模块配置到定时任务运行，力求每一步都可复现。Twikoo 本身的部署已有大量文档，此处不再赘述；代码实现仅展示关键结构与思路，具体细节可根据实际需求调整补充</p><h2 id="代码架构"><a href="#代码架构" class="headerlink" title="代码架构"></a>代码架构</h2><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line">ai_bot/                          # Python 包</span><br><span class="line">├── __init__.py                  # 空文件，标记包</span><br><span class="line">├── config.py                    # 配置中心（API密钥、URL、比例）</span><br><span class="line">├── ai_service.py                # AI生成服务（昵称、评论、回复）</span><br><span class="line">├── twikoo_client.py             # Twikoo交互层（读db、写接口）</span><br><span class="line">├── auto_comment.py              # 新评论逻辑</span><br><span class="line">├── reply_bot.py                 # 回复评论逻辑</span><br><span class="line">└── main.py                      # 入口调度（CLI、批量、比例控制）</span><br></pre></td></tr></table></figure><h3 id="关键设计"><a href="#关键设计" class="headerlink" title="关键设计"></a>关键设计</h3><p><strong>分层隔离</strong></p><ul><li><code>ai_service</code> 只负责内容生成，不感知 Twikoo</li><li><code>twikoo_client</code> 只负责数据读写，不感知 AI</li><li><code>main</code> 负责编排，业务逻辑下沉到 <code>auto_comment</code> &#x2F; <code>reply_bot</code></li></ul><p><strong>双通道读写</strong></p><ul><li>读：直接解析本地 <code>db.json.0</code>，绕过 API 限制</li><li>写：HTTS POST 到 Twikoo 容器，复用官方接口</li></ul><p><strong>状态外置</strong></p><ul><li>角度轮换记录 <code>USED_ANGLES</code> 放内存（非持久，重启清空）</li><li>配置全放 <code>config.py</code>，支持环境变量注入</li></ul><h3 id="相关代码"><a href="#相关代码" class="headerlink" title="相关代码"></a>相关代码</h3><p>下述代码统一放在博客根目录下的一个文件中，例如新建 ai_bot</p><h4 id="config-py"><a href="#config-py" class="headerlink" title="config.py"></a>config.py</h4><p><code>config.py</code> 作为配置中心模块，集中管理 AI 接口密钥、Twikoo 服务地址、评论策略比例等全局参数，全部支持通过环境变量注入，实现配置与代码分离，便于不同环境快速切换。</p><p>SENSENOVA_API_URL这个需要使用自己模型的URL调用地址，SENSENOVA_MODEL切换模型，TWIKOO_URL可以使用域名或IP+PORT</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">#!/usr/bin/env python3</span></span><br><span class="line"><span class="comment"># -*- coding: utf-8 -*-</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> os</span><br><span class="line"></span><br><span class="line"><span class="comment"># ========== 日日新 SenseNova 配置 ==========</span></span><br><span class="line">SENSENOVA_API_KEY = os.getenv(<span class="string">&quot;OPENAI_API_KEY&quot;</span>, <span class="string">&quot;sk-your-sensenova-key&quot;</span>)</span><br><span class="line">SENSENOVA_API_URL = <span class="string">&quot;https://token.sensenova.cn/v1/chat/completions&quot;</span></span><br><span class="line">SENSENOVA_MODEL = <span class="string">&quot;deepseek-v4-flash&quot;</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># Twikoo 配置</span></span><br><span class="line">TWIKOO_URL = <span class="string">&quot;http://localhost:8080&quot;</span>  </span><br><span class="line">SITE_URL = <span class="string">&quot;https://blog.eucalyptus.cc&quot;</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># ========== AI 评论策略配置 ==========</span></span><br><span class="line"><span class="comment"># 新评论 vs 回复评论的比例（0-100）</span></span><br><span class="line"><span class="comment"># 例如：NEW_COMMENT_RATIO = 70 表示 70% 概率发新评论，30% 概率回复</span></span><br><span class="line">NEW_COMMENT_RATIO = <span class="number">70</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># 日志</span></span><br><span class="line">LOG_DIR = <span class="string">&quot;/var/log/ai-bot&quot;</span></span><br><span class="line">os.makedirs(LOG_DIR, exist_ok=<span class="literal">True</span>)</span><br></pre></td></tr></table></figure><p>OPENAI_API_KEY环境变量配置并持久化</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># 写入 ~/.bashrc</span></span><br><span class="line">echo <span class="string">&#x27;export OPENAI_API_KEY=&quot;sk-your-actual-sensenova-key&quot;&#x27;</span> &gt;&gt; ~/.bashrc</span><br><span class="line"></span><br><span class="line"><span class="comment"># 立即生效</span></span><br><span class="line">source ~/.bashrc</span><br></pre></td></tr></table></figure><h4 id="ai-service-py"><a href="#ai-service-py" class="headerlink" title="ai_service.py"></a>ai_service.py</h4><p><code>ai_service.py</code> 作为 AI 生成服务模块，封装 SenseNova 大模型 API 调用，提供昵称生成、评论生成、回复生成三个核心接口；通过预设多角度评论池与角色扮演策略，结合温度参数和长度随机控制，确保输出内容拟人化且避免重复，同时维护已用角度记录实现单篇文章内的评论多样性。</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br><span class="line">89</span><br><span class="line">90</span><br><span class="line">91</span><br><span class="line">92</span><br><span class="line">93</span><br><span class="line">94</span><br><span class="line">95</span><br><span class="line">96</span><br><span class="line">97</span><br><span class="line">98</span><br><span class="line">99</span><br><span class="line">100</span><br><span class="line">101</span><br><span class="line">102</span><br><span class="line">103</span><br><span class="line">104</span><br><span class="line">105</span><br><span class="line">106</span><br><span class="line">107</span><br><span class="line">108</span><br><span class="line">109</span><br><span class="line">110</span><br><span class="line">111</span><br><span class="line">112</span><br><span class="line">113</span><br><span class="line">114</span><br><span class="line">115</span><br><span class="line">116</span><br><span class="line">117</span><br><span class="line">118</span><br><span class="line">119</span><br><span class="line">120</span><br><span class="line">121</span><br><span class="line">122</span><br><span class="line">123</span><br><span class="line">124</span><br><span class="line">125</span><br><span class="line">126</span><br><span class="line">127</span><br><span class="line">128</span><br><span class="line">129</span><br><span class="line">130</span><br><span class="line">131</span><br><span class="line">132</span><br><span class="line">133</span><br><span class="line">134</span><br><span class="line">135</span><br><span class="line">136</span><br><span class="line">137</span><br><span class="line">138</span><br><span class="line">139</span><br><span class="line">140</span><br><span class="line">141</span><br><span class="line">142</span><br><span class="line">143</span><br><span class="line">144</span><br><span class="line">145</span><br><span class="line">146</span><br><span class="line">147</span><br><span class="line">148</span><br><span class="line">149</span><br><span class="line">150</span><br><span class="line">151</span><br><span class="line">152</span><br><span class="line">153</span><br><span class="line">154</span><br><span class="line">155</span><br><span class="line">156</span><br><span class="line">157</span><br><span class="line">158</span><br><span class="line">159</span><br><span class="line">160</span><br><span class="line">161</span><br><span class="line">162</span><br><span class="line">163</span><br><span class="line">164</span><br><span class="line">165</span><br><span class="line">166</span><br><span class="line">167</span><br><span class="line">168</span><br><span class="line">169</span><br><span class="line">170</span><br><span class="line">171</span><br><span class="line">172</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">#!/usr/bin/env python3</span></span><br><span class="line"><span class="comment"># -*- coding: utf-8 -*-</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> requests</span><br><span class="line"><span class="keyword">import</span> random</span><br><span class="line"><span class="keyword">from</span> typing <span class="keyword">import</span> <span class="type">Optional</span></span><br><span class="line"><span class="keyword">from</span> .config <span class="keyword">import</span> SENSENOVA_API_KEY, SENSENOVA_API_URL, SENSENOVA_MODEL</span><br><span class="line"></span><br><span class="line"><span class="comment"># 评论角度池</span></span><br><span class="line">COMMENT_ANGLES = [</span><br><span class="line">    <span class="string">&quot;技术实现角度，提出疑问或建议&quot;</span>,</span><br><span class="line">    <span class="string">&quot;个人经验角度，分享相关经历&quot;</span>,</span><br><span class="line">    <span class="string">&quot;应用场景角度，讨论实际使用&quot;</span>,</span><br><span class="line">    <span class="string">&quot;学习收获角度，表达感谢和启发&quot;</span>,</span><br><span class="line">    <span class="string">&quot;问题反馈角度，指出可能的坑&quot;</span>,</span><br><span class="line">    <span class="string">&quot;扩展思考角度，提出延伸问题&quot;</span>,</span><br><span class="line">    <span class="string">&quot;情感共鸣角度，表达认同和感受&quot;</span>,</span><br><span class="line">]</span><br><span class="line"></span><br><span class="line"><span class="comment"># 角色设定池</span></span><br><span class="line">COMMENT_ROLES = [</span><br><span class="line">    <span class="string">&quot;技术小白，刚入门，有很多疑问&quot;</span>,</span><br><span class="line">    <span class="string">&quot;有经验的开发者，喜欢分享经验&quot;</span>,</span><br><span class="line">    <span class="string">&quot;学生党，正在学习相关技术&quot;</span>,</span><br><span class="line">    <span class="string">&quot;职场老鸟，关注实际应用&quot;</span>,</span><br><span class="line">    <span class="string">&quot;爱好者，喜欢折腾新技术&quot;</span>,</span><br><span class="line">    <span class="string">&quot;博主同行，交流心得&quot;</span>,</span><br><span class="line">]</span><br><span class="line"></span><br><span class="line"><span class="comment"># 记录已用角度（内存中，重启会清空）</span></span><br><span class="line">USED_ANGLES = &#123;&#125;</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">generate_nick</span>() -&gt; <span class="built_in">str</span>:</span><br><span class="line">    <span class="string">&quot;&quot;&quot;AI 生成随机昵称&quot;&quot;&quot;</span></span><br><span class="line">    prompt = <span class="string">&quot;&quot;&quot;你是一个中文网名生成器，请生成一个2-4个字的博客读者昵称。</span></span><br><span class="line"><span class="string">要求：</span></span><br><span class="line"><span class="string">- 像真实的中文网名，有生活气息</span></span><br><span class="line"><span class="string">- 可以是：技术向、文艺向、幽默向、日常向</span></span><br><span class="line"><span class="string">- 不要英文，不要符号</span></span><br><span class="line"><span class="string">- 直接输出昵称，不要解释</span></span><br><span class="line"><span class="string"></span></span><br><span class="line"><span class="string">示例：代码诗人、深夜咖啡、佛系码农、探索者、路过人间、技术小白&quot;&quot;&quot;</span></span><br><span class="line"></span><br><span class="line">    headers = &#123;</span><br><span class="line">        <span class="string">&quot;Authorization&quot;</span>: <span class="string">f&quot;Bearer <span class="subst">&#123;SENSENOVA_API_KEY&#125;</span>&quot;</span>,</span><br><span class="line">        <span class="string">&quot;Content-Type&quot;</span>: <span class="string">&quot;application/json&quot;</span></span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    data = &#123;</span><br><span class="line">        <span class="string">&quot;model&quot;</span>: SENSENOVA_MODEL,</span><br><span class="line">        <span class="string">&quot;messages&quot;</span>: [&#123;<span class="string">&quot;role&quot;</span>: <span class="string">&quot;user&quot;</span>, <span class="string">&quot;content&quot;</span>: prompt&#125;],</span><br><span class="line">        <span class="string">&quot;temperature&quot;</span>: <span class="number">1.2</span>,</span><br><span class="line">        <span class="string">&quot;max_tokens&quot;</span>: <span class="number">20</span>,</span><br><span class="line">        <span class="string">&quot;reasoning_effort&quot;</span>: <span class="string">&quot;none&quot;</span></span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">try</span>:</span><br><span class="line">        resp = requests.post(SENSENOVA_API_URL, headers=headers, json=data, timeout=<span class="number">10</span>)</span><br><span class="line">        result = resp.json()</span><br><span class="line">        nick = result[<span class="string">&#x27;choices&#x27;</span>][<span class="number">0</span>][<span class="string">&#x27;message&#x27;</span>][<span class="string">&#x27;content&#x27;</span>].strip()</span><br><span class="line">        nick = nick.replace(<span class="string">&quot;\&quot;&quot;</span>, <span class="string">&quot;&quot;</span>).replace(<span class="string">&quot;&#x27;&quot;</span>, <span class="string">&quot;&quot;</span>).replace(<span class="string">&quot;：&quot;</span>, <span class="string">&quot;&quot;</span>).replace(<span class="string">&quot;:&quot;</span>, <span class="string">&quot;&quot;</span>).strip()</span><br><span class="line">        <span class="keyword">return</span> nick[:<span class="number">10</span>]</span><br><span class="line">    <span class="keyword">except</span> Exception <span class="keyword">as</span> e:</span><br><span class="line">        <span class="built_in">print</span>(<span class="string">f&quot;昵称生成失败: <span class="subst">&#123;e&#125;</span>&quot;</span>)</span><br><span class="line">        fallback = [<span class="string">&quot;技术小白&quot;</span>, <span class="string">&quot;路过读者&quot;</span>, <span class="string">&quot;前端爱好者&quot;</span>, <span class="string">&quot;博主粉丝&quot;</span>, <span class="string">&quot;学习中的猫&quot;</span>, <span class="string">&quot;代码搬运工&quot;</span>, <span class="string">&quot;夜猫子程序员&quot;</span>, <span class="string">&quot;探索者&quot;</span>]</span><br><span class="line">        <span class="keyword">return</span> random.choice(fallback)</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">generate_comment</span>(<span class="params">title: <span class="built_in">str</span>, content: <span class="built_in">str</span>, url: <span class="built_in">str</span> = <span class="string">&quot;&quot;</span></span>) -&gt; <span class="type">Optional</span>[<span class="built_in">str</span>]:</span><br><span class="line">    <span class="string">&quot;&quot;&quot;生成文章评论，支持多角度和角色&quot;&quot;&quot;</span></span><br><span class="line"></span><br><span class="line">    <span class="comment"># 选择未用过的角度</span></span><br><span class="line">    used = USED_ANGLES.get(url, [])</span><br><span class="line">    available = [a <span class="keyword">for</span> a <span class="keyword">in</span> COMMENT_ANGLES <span class="keyword">if</span> a <span class="keyword">not</span> <span class="keyword">in</span> used]</span><br><span class="line"></span><br><span class="line">    <span class="keyword">if</span> <span class="keyword">not</span> available:</span><br><span class="line">        <span class="comment"># 所有角度用完，重置</span></span><br><span class="line">        available = COMMENT_ANGLES</span><br><span class="line">        USED_ANGLES[url] = []</span><br><span class="line"></span><br><span class="line">    angle = random.choice(available)</span><br><span class="line">    USED_ANGLES[url] = USED_ANGLES.get(url, []) + [angle]</span><br><span class="line"></span><br><span class="line">    <span class="comment"># 随机角色</span></span><br><span class="line">    role = random.choice(COMMENT_ROLES)</span><br><span class="line"></span><br><span class="line">    <span class="comment"># 随机长度要求</span></span><br><span class="line">    length_req = random.choice([</span><br><span class="line">        <span class="string">&quot;简短有力，30-50字&quot;</span>,</span><br><span class="line">        <span class="string">&quot;中等长度，50-80字&quot;</span>,</span><br><span class="line">        <span class="string">&quot;稍微详细，80-100字&quot;</span>,</span><br><span class="line">    ])</span><br><span class="line"></span><br><span class="line">    prompt = <span class="string">f&quot;&quot;&quot;你是一位<span class="subst">&#123;role&#125;</span>，请根据以下文章生成一条评论。</span></span><br><span class="line"><span class="string">要求：</span></span><br><span class="line"><span class="string">- 从&quot;<span class="subst">&#123;angle&#125;</span>&quot;出发</span></span><br><span class="line"><span class="string">- <span class="subst">&#123;length_req&#125;</span></span></span><br><span class="line"><span class="string">- 像真实人类一样，有个人观点</span></span><br><span class="line"><span class="string">- 不要太过正式，口语化一点</span></span><br><span class="line"><span class="string">- 避免和之前评论重复的内容</span></span><br><span class="line"><span class="string"></span></span><br><span class="line"><span class="string">文章标题：<span class="subst">&#123;title&#125;</span></span></span><br><span class="line"><span class="string">文章内容摘要：<span class="subst">&#123;content[:<span class="number">500</span>]&#125;</span>&quot;&quot;&quot;</span></span><br><span class="line"></span><br><span class="line">    headers = &#123;</span><br><span class="line">        <span class="string">&quot;Authorization&quot;</span>: <span class="string">f&quot;Bearer <span class="subst">&#123;SENSENOVA_API_KEY&#125;</span>&quot;</span>,</span><br><span class="line">        <span class="string">&quot;Content-Type&quot;</span>: <span class="string">&quot;application/json&quot;</span></span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    data = &#123;</span><br><span class="line">        <span class="string">&quot;model&quot;</span>: SENSENOVA_MODEL,</span><br><span class="line">        <span class="string">&quot;messages&quot;</span>: [&#123;<span class="string">&quot;role&quot;</span>: <span class="string">&quot;user&quot;</span>, <span class="string">&quot;content&quot;</span>: prompt&#125;],</span><br><span class="line">        <span class="string">&quot;temperature&quot;</span>: <span class="number">1.5</span>,  <span class="comment"># 提高随机性</span></span><br><span class="line">        <span class="string">&quot;max_tokens&quot;</span>: <span class="number">150</span>,</span><br><span class="line">        <span class="string">&quot;reasoning_effort&quot;</span>: <span class="string">&quot;none&quot;</span></span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">try</span>:</span><br><span class="line">        resp = requests.post(SENSENOVA_API_URL, headers=headers, json=data, timeout=<span class="number">30</span>)</span><br><span class="line">        result = resp.json()</span><br><span class="line">        <span class="keyword">return</span> result[<span class="string">&#x27;choices&#x27;</span>][<span class="number">0</span>][<span class="string">&#x27;message&#x27;</span>][<span class="string">&#x27;content&#x27;</span>].strip()</span><br><span class="line">    <span class="keyword">except</span> Exception <span class="keyword">as</span> e:</span><br><span class="line">        <span class="built_in">print</span>(<span class="string">f&quot;AI 生成失败: <span class="subst">&#123;e&#125;</span>&quot;</span>)</span><br><span class="line">        <span class="keyword">return</span> <span class="literal">None</span></span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">generate_reply</span>(<span class="params">comment_text: <span class="built_in">str</span>, article_title: <span class="built_in">str</span></span>) -&gt; <span class="type">Optional</span>[<span class="built_in">str</span>]:</span><br><span class="line">    <span class="string">&quot;&quot;&quot;生成回复评论，使用读者角度&quot;&quot;&quot;</span></span><br><span class="line">    </span><br><span class="line">    prompt = <span class="string">f&quot;&quot;&quot;你是一位博客读者，正在回复这篇文章下的评论。</span></span><br><span class="line"><span class="string">要求：</span></span><br><span class="line"><span class="string">- 以读者身份回复，不是博主/作者</span></span><br><span class="line"><span class="string">- 像真实读者一样，分享自己的看法、经验或疑问</span></span><br><span class="line"><span class="string">- 可以表示认同、补充观点、提出相关问题</span></span><br><span class="line"><span class="string">- 语气自然、口语化，像朋友聊天</span></span><br><span class="line"><span class="string">- 不要说&quot;作为作者&quot;、&quot;我会写&quot;、&quot;我的文章&quot;等博主口吻</span></span><br><span class="line"><span class="string">- 可以说&quot;我也遇到过&quot;、&quot;同感&quot;、&quot;学到了&quot;、&quot;想问下&quot;等读者常用语</span></span><br><span class="line"><span class="string">- 长度适中，50-100字左右</span></span><br><span class="line"><span class="string">- ⚠️ 重要：不要@任何人，直接回复即可，不要出现@用户名</span></span><br><span class="line"><span class="string"></span></span><br><span class="line"><span class="string">文章标题：<span class="subst">&#123;article_title&#125;</span></span></span><br><span class="line"><span class="string">你要回复的评论：<span class="subst">&#123;comment_text[:<span class="number">300</span>]&#125;</span>&quot;&quot;&quot;</span></span><br><span class="line"></span><br><span class="line">    headers = &#123;</span><br><span class="line">        <span class="string">&quot;Authorization&quot;</span>: <span class="string">f&quot;Bearer <span class="subst">&#123;SENSENOVA_API_KEY&#125;</span>&quot;</span>,</span><br><span class="line">        <span class="string">&quot;Content-Type&quot;</span>: <span class="string">&quot;application/json&quot;</span></span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    data = &#123;</span><br><span class="line">        <span class="string">&quot;model&quot;</span>: SENSENOVA_MODEL,</span><br><span class="line">        <span class="string">&quot;messages&quot;</span>: [&#123;<span class="string">&quot;role&quot;</span>: <span class="string">&quot;user&quot;</span>, <span class="string">&quot;content&quot;</span>: prompt&#125;],</span><br><span class="line">        <span class="string">&quot;temperature&quot;</span>: <span class="number">1.2</span>,</span><br><span class="line">        <span class="string">&quot;max_tokens&quot;</span>: <span class="number">150</span>,</span><br><span class="line">        <span class="string">&quot;reasoning_effort&quot;</span>: <span class="string">&quot;none&quot;</span></span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">try</span>:</span><br><span class="line">        resp = requests.post(SENSENOVA_API_URL, headers=headers, json=data, timeout=<span class="number">30</span>)</span><br><span class="line">        result = resp.json()</span><br><span class="line">        reply = result[<span class="string">&#x27;choices&#x27;</span>][<span class="number">0</span>][<span class="string">&#x27;message&#x27;</span>][<span class="string">&#x27;content&#x27;</span>].strip()</span><br><span class="line">        </span><br><span class="line">        <span class="comment"># 过滤掉 AI 可能生成的 @用户名</span></span><br><span class="line">        <span class="keyword">import</span> re</span><br><span class="line">        reply = re.sub(<span class="string">r&#x27;@\w+\s*&#x27;</span>, <span class="string">&#x27;&#x27;</span>, reply).strip()</span><br><span class="line">        </span><br><span class="line">        <span class="keyword">return</span> reply</span><br><span class="line">    <span class="keyword">except</span> Exception <span class="keyword">as</span> e:</span><br><span class="line">        <span class="built_in">print</span>(<span class="string">f&quot;AI 生成回复失败: <span class="subst">&#123;e&#125;</span>&quot;</span>)</span><br><span class="line">        <span class="keyword">return</span> <span class="literal">None</span></span><br><span class="line"></span><br><span class="line">        </span><br></pre></td></tr></table></figure><h4 id="twikoo-client-py"><a href="#twikoo-client-py" class="headerlink" title="twikoo_client.py"></a>twikoo_client.py</h4><p><code>twikoo_client.py</code> 作为 Twikoo 交互层模块，采用双通道设计实现评论数据的读写操作：读取通道直接解析本地 <code>db.json.0</code> 文件获取完整评论列表，写入通道通过 HTTP POST 向 Twikoo 容器提交新评论或回复；同时负责拟人化字段构造（UA、IP、邮箱等）及 URL 编码兼容处理，确保数据层面与真实用户无异。</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br><span class="line">89</span><br><span class="line">90</span><br><span class="line">91</span><br><span class="line">92</span><br><span class="line">93</span><br><span class="line">94</span><br><span class="line">95</span><br><span class="line">96</span><br><span class="line">97</span><br><span class="line">98</span><br><span class="line">99</span><br><span class="line">100</span><br><span class="line">101</span><br><span class="line">102</span><br><span class="line">103</span><br><span class="line">104</span><br><span class="line">105</span><br><span class="line">106</span><br><span class="line">107</span><br><span class="line">108</span><br><span class="line">109</span><br><span class="line">110</span><br><span class="line">111</span><br><span class="line">112</span><br><span class="line">113</span><br><span class="line">114</span><br><span class="line">115</span><br><span class="line">116</span><br><span class="line">117</span><br><span class="line">118</span><br><span class="line">119</span><br><span class="line">120</span><br><span class="line">121</span><br><span class="line">122</span><br><span class="line">123</span><br><span class="line">124</span><br><span class="line">125</span><br><span class="line">126</span><br><span class="line">127</span><br><span class="line">128</span><br><span class="line">129</span><br><span class="line">130</span><br><span class="line">131</span><br><span class="line">132</span><br><span class="line">133</span><br><span class="line">134</span><br><span class="line">135</span><br><span class="line">136</span><br><span class="line">137</span><br><span class="line">138</span><br><span class="line">139</span><br><span class="line">140</span><br><span class="line">141</span><br><span class="line">142</span><br><span class="line">143</span><br><span class="line">144</span><br><span class="line">145</span><br><span class="line">146</span><br><span class="line">147</span><br><span class="line">148</span><br><span class="line">149</span><br><span class="line">150</span><br><span class="line">151</span><br><span class="line">152</span><br><span class="line">153</span><br><span class="line">154</span><br><span class="line">155</span><br><span class="line">156</span><br><span class="line">157</span><br><span class="line">158</span><br><span class="line">159</span><br><span class="line">160</span><br><span class="line">161</span><br><span class="line">162</span><br><span class="line">163</span><br><span class="line">164</span><br><span class="line">165</span><br><span class="line">166</span><br><span class="line">167</span><br><span class="line">168</span><br><span class="line">169</span><br><span class="line">170</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># twikoo_client.py</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> requests</span><br><span class="line"><span class="keyword">import</span> random</span><br><span class="line"><span class="keyword">import</span> json</span><br><span class="line"><span class="keyword">import</span> os</span><br><span class="line"><span class="keyword">import</span> urllib.parse</span><br><span class="line"><span class="keyword">from</span> typing <span class="keyword">import</span> <span class="type">Optional</span>, <span class="type">Dict</span>, <span class="type">List</span></span><br><span class="line"><span class="keyword">from</span> .config <span class="keyword">import</span> TWIKOO_URL, SITE_URL</span><br><span class="line"></span><br><span class="line"><span class="comment"># 真实用户 UA 池</span></span><br><span class="line">USER_AGENTS = [</span><br><span class="line">    <span class="string">&quot;Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36&quot;</span>,</span><br><span class="line">    <span class="string">&quot;Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36&quot;</span>,</span><br><span class="line">    <span class="string">&quot;Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/121.0&quot;</span>,</span><br><span class="line">    <span class="string">&quot;Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.1 Safari/605.1.15&quot;</span>,</span><br><span class="line">    <span class="string">&quot;Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36&quot;</span>,</span><br><span class="line">]</span><br><span class="line"></span><br><span class="line"><span class="comment"># 真实 IP 池（国内常见）</span></span><br><span class="line">IP_POOL = [</span><br><span class="line">    <span class="string">&quot;223.104.212.1&quot;</span>, <span class="string">&quot;183.192.123.45&quot;</span>, <span class="string">&quot;117.136.62.89&quot;</span>,</span><br><span class="line">    <span class="string">&quot;120.197.198.2&quot;</span>, <span class="string">&quot;183.60.235.1&quot;</span>, <span class="string">&quot;113.96.201.23&quot;</span>,</span><br><span class="line">    <span class="string">&quot;116.22.29.1&quot;</span>, <span class="string">&quot;125.220.157.89&quot;</span>, <span class="string">&quot;42.236.10.1&quot;</span></span><br><span class="line">]</span><br><span class="line"></span><br><span class="line"><span class="comment"># Twikoo 本地数据库路径</span></span><br><span class="line">DB_PATH = <span class="string">&quot;/var/lib/docker/twikoo/data/db.json.0&quot;</span></span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">_get_comments_from_db</span>(<span class="params">url: <span class="built_in">str</span></span>) -&gt; <span class="type">List</span>[<span class="type">Dict</span>]:</span><br><span class="line">    <span class="string">&quot;&quot;&quot;从 Twikoo 本地 db.json 读取评论&quot;&quot;&quot;</span></span><br><span class="line">    comments = []</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">if</span> <span class="keyword">not</span> os.path.exists(DB_PATH):</span><br><span class="line">        <span class="built_in">print</span>(<span class="string">f&quot;数据库文件不存在: <span class="subst">&#123;DB_PATH&#125;</span>&quot;</span>)</span><br><span class="line">        <span class="keyword">return</span> comments</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">try</span>:</span><br><span class="line">        <span class="keyword">with</span> <span class="built_in">open</span>(DB_PATH, <span class="string">&#x27;r&#x27;</span>, encoding=<span class="string">&#x27;utf-8&#x27;</span>) <span class="keyword">as</span> f:</span><br><span class="line">            <span class="keyword">for</span> line <span class="keyword">in</span> f:</span><br><span class="line">                line = line.strip()</span><br><span class="line">                <span class="keyword">if</span> <span class="keyword">not</span> line:</span><br><span class="line">                    <span class="keyword">continue</span></span><br><span class="line">                <span class="keyword">try</span>:</span><br><span class="line">                    doc = json.loads(line)</span><br><span class="line">                    doc_url = doc.get(<span class="string">&quot;url&quot;</span>, <span class="string">&quot;&quot;</span>)</span><br><span class="line">                    </span><br><span class="line">                    <span class="comment"># 匹配 url（同时支持编码和未编码）</span></span><br><span class="line">                    urls_match = (</span><br><span class="line">                        doc_url == url <span class="keyword">or</span> </span><br><span class="line">                        doc_url == urllib.parse.unquote(url) <span class="keyword">or</span></span><br><span class="line">                        urllib.parse.unquote(doc_url) == url</span><br><span class="line">                    )</span><br><span class="line">                    </span><br><span class="line">                    <span class="keyword">if</span> <span class="keyword">not</span> urls_match:</span><br><span class="line">                        <span class="keyword">continue</span></span><br><span class="line">                    </span><br><span class="line">                    <span class="comment"># 排除垃圾评论和已删除</span></span><br><span class="line">                    <span class="keyword">if</span> doc.get(<span class="string">&quot;isSpam&quot;</span>, <span class="literal">False</span>) <span class="keyword">or</span> doc.get(<span class="string">&quot;status&quot;</span>) == <span class="string">&quot;deleted&quot;</span>:</span><br><span class="line">                        <span class="keyword">continue</span></span><br><span class="line">                    </span><br><span class="line">                    <span class="comment"># 统一字段格式</span></span><br><span class="line">                    comment = &#123;</span><br><span class="line">                        <span class="string">&quot;_id&quot;</span>: doc.get(<span class="string">&quot;_id&quot;</span>),</span><br><span class="line">                        <span class="string">&quot;id&quot;</span>: doc.get(<span class="string">&quot;id&quot;</span>) <span class="keyword">or</span> doc.get(<span class="string">&quot;_id&quot;</span>),</span><br><span class="line">                        <span class="string">&quot;nick&quot;</span>: doc.get(<span class="string">&quot;nick&quot;</span>, <span class="string">&quot;&quot;</span>),</span><br><span class="line">                        <span class="string">&quot;mail&quot;</span>: doc.get(<span class="string">&quot;mail&quot;</span>, <span class="string">&quot;&quot;</span>),</span><br><span class="line">                        <span class="string">&quot;mailMd5&quot;</span>: doc.get(<span class="string">&quot;mailMd5&quot;</span>, <span class="string">&quot;&quot;</span>),</span><br><span class="line">                        <span class="string">&quot;link&quot;</span>: doc.get(<span class="string">&quot;link&quot;</span>, <span class="string">&quot;&quot;</span>),</span><br><span class="line">                        <span class="string">&quot;ua&quot;</span>: doc.get(<span class="string">&quot;ua&quot;</span>, <span class="string">&quot;&quot;</span>),</span><br><span class="line">                        <span class="string">&quot;ip&quot;</span>: doc.get(<span class="string">&quot;ip&quot;</span>, <span class="string">&quot;&quot;</span>),</span><br><span class="line">                        <span class="string">&quot;master&quot;</span>: doc.get(<span class="string">&quot;master&quot;</span>, <span class="literal">False</span>),</span><br><span class="line">                        <span class="string">&quot;url&quot;</span>: doc_url,</span><br><span class="line">                        <span class="string">&quot;href&quot;</span>: doc.get(<span class="string">&quot;href&quot;</span>, <span class="string">&quot;&quot;</span>),</span><br><span class="line">                        <span class="string">&quot;comment&quot;</span>: doc.get(<span class="string">&quot;comment&quot;</span>, <span class="string">&quot;&quot;</span>),</span><br><span class="line">                        <span class="string">&quot;rid&quot;</span>: doc.get(<span class="string">&quot;rid&quot;</span>, <span class="string">&quot;&quot;</span>),</span><br><span class="line">                        <span class="string">&quot;pid&quot;</span>: doc.get(<span class="string">&quot;pid&quot;</span>, <span class="string">&quot;&quot;</span>),</span><br><span class="line">                        <span class="string">&quot;isSpam&quot;</span>: doc.get(<span class="string">&quot;isSpam&quot;</span>, <span class="literal">False</span>),</span><br><span class="line">                        <span class="string">&quot;created&quot;</span>: doc.get(<span class="string">&quot;created&quot;</span>, <span class="number">0</span>),</span><br><span class="line">                        <span class="string">&quot;updated&quot;</span>: doc.get(<span class="string">&quot;updated&quot;</span>, <span class="number">0</span>),</span><br><span class="line">                    &#125;</span><br><span class="line">                    comments.append(comment)</span><br><span class="line">                <span class="keyword">except</span> json.JSONDecodeError:</span><br><span class="line">                    <span class="keyword">continue</span></span><br><span class="line">    <span class="keyword">except</span> Exception <span class="keyword">as</span> e:</span><br><span class="line">        <span class="built_in">print</span>(<span class="string">f&quot;读取数据库失败: <span class="subst">&#123;e&#125;</span>&quot;</span>)</span><br><span class="line">    </span><br><span class="line">    <span class="built_in">print</span>(<span class="string">f&quot;从数据库读取到 <span class="subst">&#123;<span class="built_in">len</span>(comments)&#125;</span> 条评论&quot;</span>)</span><br><span class="line">    <span class="keyword">return</span> comments</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">get_comments</span>(<span class="params">url: <span class="built_in">str</span>, limit: <span class="built_in">int</span> = <span class="number">100</span></span>) -&gt; <span class="type">List</span>[<span class="type">Dict</span>]:</span><br><span class="line">    <span class="string">&quot;&quot;&quot;获取评论列表 - 直接读本地数据库&quot;&quot;&quot;</span></span><br><span class="line">    <span class="keyword">return</span> _get_comments_from_db(url)</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">submit_comment</span>(<span class="params">url: <span class="built_in">str</span>, comment: <span class="built_in">str</span>, nick: <span class="built_in">str</span>, mail: <span class="built_in">str</span>, pid: <span class="built_in">str</span> = <span class="string">&quot;&quot;</span>, rid: <span class="built_in">str</span> = <span class="string">&quot;&quot;</span></span>) -&gt; <span class="built_in">bool</span>:</span><br><span class="line">    <span class="string">&quot;&quot;&quot;提交评论到 Twikoo，支持回复&quot;&quot;&quot;</span></span><br><span class="line">    </span><br><span class="line">    <span class="comment"># 1. 移除 SITE_URL 前缀</span></span><br><span class="line">    <span class="keyword">if</span> url.startswith(SITE_URL):</span><br><span class="line">        url = url.replace(SITE_URL, <span class="string">&quot;&quot;</span>)</span><br><span class="line">    </span><br><span class="line">    <span class="comment"># 2. 确保格式正确</span></span><br><span class="line">    <span class="keyword">if</span> <span class="keyword">not</span> url.startswith(<span class="string">&quot;/&quot;</span>):</span><br><span class="line">        url = <span class="string">&quot;/&quot;</span> + url</span><br><span class="line">    <span class="keyword">if</span> <span class="keyword">not</span> url.endswith(<span class="string">&quot;/&quot;</span>):</span><br><span class="line">        url = url + <span class="string">&quot;/&quot;</span></span><br><span class="line">    </span><br><span class="line">    <span class="comment"># 3. 先完全解码，再重新编码（防止双重编码）</span></span><br><span class="line">    url = urllib.parse.unquote(url)</span><br><span class="line">    parts = url.split(<span class="string">&#x27;/&#x27;</span>)</span><br><span class="line">    encoded_parts = [urllib.parse.quote(part, safe=<span class="string">&#x27;&#x27;</span>) <span class="keyword">for</span> part <span class="keyword">in</span> parts]</span><br><span class="line">    url = <span class="string">&#x27;/&#x27;</span>.join(encoded_parts)</span><br><span class="line">    </span><br><span class="line">    <span class="comment"># 4. 构造 href</span></span><br><span class="line">    href = SITE_URL + url</span><br><span class="line">    </span><br><span class="line">    ua = random.choice(USER_AGENTS)</span><br><span class="line">    fake_ip = random.choice(IP_POOL)</span><br><span class="line">    </span><br><span class="line">    <span class="comment"># 5. 构造 payload</span></span><br><span class="line">    <span class="comment"># 关键修复：顶级评论不要传 pid/rid 字段（而不是传空字符串）</span></span><br><span class="line">    payload = &#123;</span><br><span class="line">        <span class="string">&quot;event&quot;</span>: <span class="string">&quot;COMMENT_SUBMIT&quot;</span>,</span><br><span class="line">        <span class="string">&quot;url&quot;</span>: url,</span><br><span class="line">        <span class="string">&quot;href&quot;</span>: href,</span><br><span class="line">        <span class="string">&quot;comment&quot;</span>: comment,</span><br><span class="line">        <span class="string">&quot;nick&quot;</span>: nick,</span><br><span class="line">        <span class="string">&quot;mail&quot;</span>: mail,</span><br><span class="line">        <span class="string">&quot;link&quot;</span>: <span class="string">&quot;&quot;</span>,</span><br><span class="line">        <span class="string">&quot;ua&quot;</span>: ua,</span><br><span class="line">        <span class="string">&quot;ip&quot;</span>: fake_ip,</span><br><span class="line">        <span class="string">&quot;isSpam&quot;</span>: <span class="literal">False</span>,  <span class="comment"># 显式设置，必须和手动评论一致</span></span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="comment"># 只有回复时才添加 pid 和 rid</span></span><br><span class="line">    <span class="keyword">if</span> pid:</span><br><span class="line">        payload[<span class="string">&quot;pid&quot;</span>] = pid</span><br><span class="line">    <span class="keyword">if</span> rid:</span><br><span class="line">        payload[<span class="string">&quot;rid&quot;</span>] = rid</span><br><span class="line">    </span><br><span class="line">    headers = &#123;</span><br><span class="line">        <span class="string">&quot;Content-Type&quot;</span>: <span class="string">&quot;application/json&quot;</span>,</span><br><span class="line">        <span class="string">&quot;User-Agent&quot;</span>: ua,</span><br><span class="line">        <span class="string">&quot;X-Forwarded-For&quot;</span>: fake_ip,</span><br><span class="line">        <span class="string">&quot;X-Real-IP&quot;</span>: fake_ip</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">try</span>:</span><br><span class="line">        resp = requests.post(TWIKOO_URL, json=payload, headers=headers, timeout=<span class="number">10</span>)</span><br><span class="line">        result = resp.json()</span><br><span class="line">        <span class="built_in">print</span>(<span class="string">f&quot;  Twikoo 返回: <span class="subst">&#123;result&#125;</span>&quot;</span>)</span><br><span class="line">        <span class="built_in">print</span>(<span class="string">f&quot;  提交 URL: <span class="subst">&#123;url&#125;</span>&quot;</span>)</span><br><span class="line">        <span class="built_in">print</span>(<span class="string">f&quot;  Payload: <span class="subst">&#123;json.dumps(payload, ensure_ascii=<span class="literal">False</span>)&#125;</span>&quot;</span>)  <span class="comment"># 调试用</span></span><br><span class="line">        <span class="keyword">return</span> <span class="string">&quot;id&quot;</span> <span class="keyword">in</span> result <span class="keyword">or</span> result.get(<span class="string">&quot;code&quot;</span>) == <span class="number">0</span></span><br><span class="line">    <span class="keyword">except</span> Exception <span class="keyword">as</span> e:</span><br><span class="line">        <span class="built_in">print</span>(<span class="string">f&quot;提交失败: <span class="subst">&#123;e&#125;</span>&quot;</span>)</span><br><span class="line">        <span class="keyword">return</span> <span class="literal">False</span></span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">check_ai_comment_exists</span>(<span class="params">url: <span class="built_in">str</span></span>) -&gt; <span class="built_in">bool</span>:</span><br><span class="line">    <span class="string">&quot;&quot;&quot;检查是否已有 AI 评论（通过 UA 识别）&quot;&quot;&quot;</span></span><br><span class="line">    comments = get_comments(url)</span><br><span class="line">    <span class="keyword">for</span> c <span class="keyword">in</span> comments:</span><br><span class="line">        ua = c.get(<span class="string">&quot;ua&quot;</span>, <span class="string">&quot;&quot;</span>)</span><br><span class="line">        <span class="keyword">if</span> ua <span class="keyword">in</span> USER_AGENTS:</span><br><span class="line">            <span class="keyword">return</span> <span class="literal">True</span></span><br><span class="line">    <span class="keyword">return</span> <span class="literal">False</span></span><br></pre></td></tr></table></figure><h4 id="auto-comment-py"><a href="#auto-comment-py" class="headerlink" title="auto_comment.py"></a>auto_comment.py</h4><p><code>auto_comment.py</code> 作为新评论逻辑模块，负责组装并提交单篇文章的首条 AI 评论：依次调用 AI 服务生成评论内容、随机昵称和邮箱，构造完整的 Twikoo 评论数据包，最终通过客户端提交；内置可选的去重检查机制，避免同一篇文章被重复评论。</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">#!/usr/bin/env python3</span></span><br><span class="line"><span class="comment"># -*- coding: utf-8 -*-</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> random</span><br><span class="line"><span class="keyword">from</span> .ai_service <span class="keyword">import</span> generate_comment, generate_nick</span><br><span class="line"><span class="keyword">from</span> .twikoo_client <span class="keyword">import</span> submit_comment, check_ai_comment_exists</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">generate_mail</span>() -&gt; <span class="built_in">str</span>:</span><br><span class="line">    <span class="string">&quot;&quot;&quot;生成邮箱，Twikoo 自动显示 Gravatar 头像&quot;&quot;&quot;</span></span><br><span class="line">    seed = random.randint(<span class="number">1000</span>, <span class="number">9999</span>)</span><br><span class="line">    <span class="keyword">return</span> <span class="string">f&quot;user<span class="subst">&#123;seed&#125;</span>@example.com&quot;</span></span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">process_article</span>(<span class="params">url: <span class="built_in">str</span>, title: <span class="built_in">str</span>, content: <span class="built_in">str</span></span>) -&gt; <span class="built_in">bool</span>:</span><br><span class="line">    <span class="string">&quot;&quot;&quot;处理单篇文章：发新评论&quot;&quot;&quot;</span></span><br><span class="line">    <span class="built_in">print</span>(<span class="string">f&quot;处理: <span class="subst">&#123;title&#125;</span>&quot;</span>)</span><br><span class="line">    </span><br><span class="line">    <span class="comment"># 检查是否已有 AI 评论（可选，如果想多次评论可以注释掉）</span></span><br><span class="line">    <span class="comment"># if check_ai_comment_exists(url):</span></span><br><span class="line">    <span class="comment">#     print(&quot;  已有 AI 评论，跳过&quot;)</span></span><br><span class="line">    <span class="comment">#     return False</span></span><br><span class="line">    </span><br><span class="line">    comment_text = generate_comment(title, content, url)</span><br><span class="line">    <span class="keyword">if</span> <span class="keyword">not</span> comment_text:</span><br><span class="line">        <span class="built_in">print</span>(<span class="string">&quot;  AI 生成失败&quot;</span>)</span><br><span class="line">        <span class="keyword">return</span> <span class="literal">False</span></span><br><span class="line">    </span><br><span class="line">    <span class="built_in">print</span>(<span class="string">f&quot;  生成: <span class="subst">&#123;comment_text[:<span class="number">50</span>]&#125;</span>...&quot;</span>)</span><br><span class="line">    </span><br><span class="line">    nick = generate_nick()</span><br><span class="line">    <span class="built_in">print</span>(<span class="string">f&quot;  昵称: <span class="subst">&#123;nick&#125;</span>&quot;</span>)</span><br><span class="line">    </span><br><span class="line">    mail = generate_mail()</span><br><span class="line">    <span class="built_in">print</span>(<span class="string">f&quot;  邮箱: <span class="subst">&#123;mail&#125;</span>&quot;</span>)</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">if</span> submit_comment(url, comment_text, nick, mail):</span><br><span class="line">        <span class="built_in">print</span>(<span class="string">f&quot;  ✅ 提交成功&quot;</span>)</span><br><span class="line">        <span class="keyword">return</span> <span class="literal">True</span></span><br><span class="line">    <span class="keyword">else</span>:</span><br><span class="line">        <span class="built_in">print</span>(<span class="string">f&quot;  ❌ 提交失败&quot;</span>)</span><br><span class="line">        <span class="keyword">return</span> <span class="literal">False</span></span><br></pre></td></tr></table></figure><h4 id="reply-bot-py"><a href="#reply-bot-py" class="headerlink" title="reply_bot.py"></a>reply_bot.py</h4><p><code>reply_bot.py</code> 作为回复评论逻辑模块，负责筛选并回复文章下现有评论：读取 Twikoo 本地数据库获取未回复评论列表（排除 AI 自身评论），调用 AI 服务生成针对性回复内容，正确设置 <code>pid</code> 和 <code>rid</code> 字段确保回复层级关系准确，实现模拟真实读者间的互动对话。</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br><span class="line">89</span><br><span class="line">90</span><br><span class="line">91</span><br><span class="line">92</span><br><span class="line">93</span><br><span class="line">94</span><br><span class="line">95</span><br><span class="line">96</span><br><span class="line">97</span><br><span class="line">98</span><br><span class="line">99</span><br><span class="line">100</span><br><span class="line">101</span><br><span class="line">102</span><br><span class="line">103</span><br><span class="line">104</span><br><span class="line">105</span><br><span class="line">106</span><br><span class="line">107</span><br><span class="line">108</span><br><span class="line">109</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># reply_bot.py</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> random</span><br><span class="line"><span class="keyword">from</span> typing <span class="keyword">import</span> <span class="type">List</span>, <span class="type">Dict</span></span><br><span class="line"><span class="keyword">from</span> .ai_service <span class="keyword">import</span> generate_reply, generate_nick  <span class="comment"># 导入 generate_nick</span></span><br><span class="line"><span class="keyword">from</span> .twikoo_client <span class="keyword">import</span> get_comments, submit_comment</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">get_unreplied_comments</span>(<span class="params">url: <span class="built_in">str</span>, bot_nicks: <span class="type">List</span>[<span class="built_in">str</span>]</span>) -&gt; <span class="type">List</span>[<span class="type">Dict</span>]:</span><br><span class="line">    <span class="string">&quot;&quot;&quot;获取未回复的评论（排除自己的评论）&quot;&quot;&quot;</span></span><br><span class="line">    comments = get_comments(url)</span><br><span class="line">    </span><br><span class="line">    unreplied = []</span><br><span class="line">    </span><br><span class="line">    <span class="comment"># 收集所有被回复的评论 ID（用 pid）</span></span><br><span class="line">    replied_ids = <span class="built_in">set</span>()</span><br><span class="line">    <span class="keyword">for</span> c <span class="keyword">in</span> comments:</span><br><span class="line">        pid = c.get(<span class="string">&quot;pid&quot;</span>)</span><br><span class="line">        <span class="keyword">if</span> pid:</span><br><span class="line">            replied_ids.add(pid)</span><br><span class="line">    </span><br><span class="line">    <span class="built_in">print</span>(<span class="string">f&quot;已被回复的评论ID: <span class="subst">&#123;replied_ids&#125;</span>&quot;</span>)</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">for</span> comment <span class="keyword">in</span> comments:</span><br><span class="line">        <span class="comment"># 获取评论 ID（优先 _id）</span></span><br><span class="line">        comment_id = comment.get(<span class="string">&quot;_id&quot;</span>) <span class="keyword">or</span> comment.get(<span class="string">&quot;id&quot;</span>)</span><br><span class="line">        nick = comment.get(<span class="string">&quot;nick&quot;</span>, <span class="string">&quot;unknown&quot;</span>)</span><br><span class="line">        </span><br><span class="line">        <span class="comment"># 跳过自己的评论</span></span><br><span class="line">        <span class="keyword">if</span> nick <span class="keyword">in</span> bot_nicks:</span><br><span class="line">            <span class="keyword">continue</span></span><br><span class="line">        </span><br><span class="line">        <span class="comment"># 检查是否已被回复</span></span><br><span class="line">        <span class="keyword">if</span> comment_id <span class="keyword">and</span> comment_id <span class="keyword">in</span> replied_ids:</span><br><span class="line">            <span class="keyword">continue</span></span><br><span class="line">        </span><br><span class="line">        unreplied.append(comment)</span><br><span class="line">    </span><br><span class="line">    <span class="built_in">print</span>(<span class="string">f&quot;发现 <span class="subst">&#123;<span class="built_in">len</span>(unreplied)&#125;</span> 条未回复评论&quot;</span>)</span><br><span class="line">    <span class="keyword">return</span> unreplied</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">reply_to_comment</span>(<span class="params">parent_comment: <span class="type">Dict</span>, article_title: <span class="built_in">str</span></span>) -&gt; <span class="built_in">bool</span>:</span><br><span class="line">    parent_nick = parent_comment.get(<span class="string">&quot;nick&quot;</span>, <span class="string">&quot;网友&quot;</span>)</span><br><span class="line">    parent_text = parent_comment.get(<span class="string">&quot;comment&quot;</span>, <span class="string">&quot;&quot;</span>)[:<span class="number">30</span>]</span><br><span class="line">    <span class="built_in">print</span>(<span class="string">f&quot;回复 [<span class="subst">&#123;parent_nick&#125;</span>]: <span class="subst">&#123;parent_text&#125;</span>...&quot;</span>)</span><br><span class="line">    </span><br><span class="line">    <span class="comment"># 生成回复内容</span></span><br><span class="line">    reply_text = generate_reply(</span><br><span class="line">        comment_text=parent_comment.get(<span class="string">&quot;comment&quot;</span>, <span class="string">&quot;&quot;</span>),</span><br><span class="line">        article_title=article_title</span><br><span class="line">    )</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">if</span> <span class="keyword">not</span> reply_text:</span><br><span class="line">        <span class="built_in">print</span>(<span class="string">&quot;  回复生成失败&quot;</span>)</span><br><span class="line">        <span class="keyword">return</span> <span class="literal">False</span></span><br><span class="line">    </span><br><span class="line">    <span class="built_in">print</span>(<span class="string">f&quot;  回复: <span class="subst">&#123;reply_text[:<span class="number">50</span>]&#125;</span>...&quot;</span>)</span><br><span class="line">    </span><br><span class="line">    <span class="comment"># 随机生成昵称</span></span><br><span class="line">    reply_nick = generate_nick()</span><br><span class="line">    <span class="built_in">print</span>(<span class="string">f&quot;  昵称: <span class="subst">&#123;reply_nick&#125;</span>&quot;</span>)</span><br><span class="line">    </span><br><span class="line">    <span class="comment"># 获取父评论ID</span></span><br><span class="line">    parent_id = parent_comment.get(<span class="string">&quot;_id&quot;</span>) <span class="keyword">or</span> parent_comment.get(<span class="string">&quot;id&quot;</span>)</span><br><span class="line">    </span><br><span class="line">    <span class="comment"># 获取父评论的 rid</span></span><br><span class="line">    parent_rid = parent_comment.get(<span class="string">&quot;rid&quot;</span>, <span class="string">&quot;&quot;</span>)</span><br><span class="line">    <span class="keyword">if</span> parent_rid:</span><br><span class="line">        <span class="comment"># 父评论本身是回复，继承其 rid</span></span><br><span class="line">        root_id = parent_rid</span><br><span class="line">    <span class="keyword">else</span>:</span><br><span class="line">        <span class="comment"># 父评论是顶级评论，rid 就是父评论自己的 id</span></span><br><span class="line">        root_id = parent_id</span><br><span class="line">    </span><br><span class="line">    <span class="comment"># 提交回复（不加 @，Twikoo 前端自动显示）</span></span><br><span class="line">    result = submit_comment(</span><br><span class="line">        url=parent_comment.get(<span class="string">&quot;url&quot;</span>, <span class="string">&quot;/&quot;</span>),</span><br><span class="line">        comment=reply_text,</span><br><span class="line">        nick=reply_nick,</span><br><span class="line">        mail=<span class="string">f&quot;ai-reply-<span class="subst">&#123;random.randint(<span class="number">1000</span>,<span class="number">9999</span>)&#125;</span>@example.com&quot;</span>,</span><br><span class="line">        pid=parent_id,</span><br><span class="line">        rid=root_id,</span><br><span class="line">    )</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">return</span> result</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">run_reply_bot</span>(<span class="params">url: <span class="built_in">str</span>, bot_nicks: <span class="type">List</span>[<span class="built_in">str</span>], max_reply: <span class="built_in">int</span> = <span class="number">3</span></span>):</span><br><span class="line">    <span class="string">&quot;&quot;&quot;运行回复机器人&quot;&quot;&quot;</span></span><br><span class="line">    <span class="built_in">print</span>(<span class="string">f&quot;检查文章: <span class="subst">&#123;url&#125;</span>&quot;</span>)</span><br><span class="line">    </span><br><span class="line">    unreplied = get_unreplied_comments(url, bot_nicks)</span><br><span class="line">    </span><br><span class="line">    <span class="comment"># 随机打乱顺序</span></span><br><span class="line">    random.shuffle(unreplied)</span><br><span class="line">    </span><br><span class="line">    <span class="comment"># 取前 max_reply 条</span></span><br><span class="line">    to_reply = unreplied[:max_reply]</span><br><span class="line">    </span><br><span class="line">    <span class="built_in">print</span>(<span class="string">f&quot;选择回复 <span class="subst">&#123;<span class="built_in">len</span>(to_reply)&#125;</span>/<span class="subst">&#123;<span class="built_in">len</span>(unreplied)&#125;</span> 条评论&quot;</span>)</span><br><span class="line">    </span><br><span class="line">    success = <span class="number">0</span></span><br><span class="line">    <span class="keyword">for</span> comment <span class="keyword">in</span> to_reply:</span><br><span class="line">        <span class="keyword">if</span> reply_to_comment(comment, <span class="string">&quot;文章标题&quot;</span>):</span><br><span class="line">            success += <span class="number">1</span></span><br><span class="line">    </span><br><span class="line">    <span class="built_in">print</span>(<span class="string">f&quot;完成: 回复 <span class="subst">&#123;success&#125;</span>/<span class="subst">&#123;<span class="built_in">len</span>(to_reply)&#125;</span> 条评论&quot;</span>)</span><br><span class="line">    <span class="keyword">return</span> success</span><br></pre></td></tr></table></figure><h4 id="main-py"><a href="#main-py" class="headerlink" title="main.py"></a>main.py</h4><p><code>main.py</code> 作为入口调度模块，负责整合全系统运行流程：自动扫描 Hexo 生成目录发现文章列表，按配置比例随机调度新评论或回复模式，提供 CLI 参数支持单篇&#x2F;批量&#x2F;范围选择及临时比例覆盖，最终输出执行统计报告，是日常定时任务调用的唯一入口。</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br><span class="line">89</span><br><span class="line">90</span><br><span class="line">91</span><br><span class="line">92</span><br><span class="line">93</span><br><span class="line">94</span><br><span class="line">95</span><br><span class="line">96</span><br><span class="line">97</span><br><span class="line">98</span><br><span class="line">99</span><br><span class="line">100</span><br><span class="line">101</span><br><span class="line">102</span><br><span class="line">103</span><br><span class="line">104</span><br><span class="line">105</span><br><span class="line">106</span><br><span class="line">107</span><br><span class="line">108</span><br><span class="line">109</span><br><span class="line">110</span><br><span class="line">111</span><br><span class="line">112</span><br><span class="line">113</span><br><span class="line">114</span><br><span class="line">115</span><br><span class="line">116</span><br><span class="line">117</span><br><span class="line">118</span><br><span class="line">119</span><br><span class="line">120</span><br><span class="line">121</span><br><span class="line">122</span><br><span class="line">123</span><br><span class="line">124</span><br><span class="line">125</span><br><span class="line">126</span><br><span class="line">127</span><br><span class="line">128</span><br><span class="line">129</span><br><span class="line">130</span><br><span class="line">131</span><br><span class="line">132</span><br><span class="line">133</span><br><span class="line">134</span><br><span class="line">135</span><br><span class="line">136</span><br><span class="line">137</span><br><span class="line">138</span><br><span class="line">139</span><br><span class="line">140</span><br><span class="line">141</span><br><span class="line">142</span><br><span class="line">143</span><br><span class="line">144</span><br><span class="line">145</span><br><span class="line">146</span><br><span class="line">147</span><br><span class="line">148</span><br><span class="line">149</span><br><span class="line">150</span><br><span class="line">151</span><br><span class="line">152</span><br><span class="line">153</span><br><span class="line">154</span><br><span class="line">155</span><br><span class="line">156</span><br><span class="line">157</span><br><span class="line">158</span><br><span class="line">159</span><br><span class="line">160</span><br><span class="line">161</span><br><span class="line">162</span><br><span class="line">163</span><br><span class="line">164</span><br><span class="line">165</span><br><span class="line">166</span><br><span class="line">167</span><br><span class="line">168</span><br><span class="line">169</span><br><span class="line">170</span><br><span class="line">171</span><br><span class="line">172</span><br><span class="line">173</span><br><span class="line">174</span><br><span class="line">175</span><br><span class="line">176</span><br><span class="line">177</span><br><span class="line">178</span><br><span class="line">179</span><br><span class="line">180</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">#!/usr/bin/env python3</span></span><br><span class="line"><span class="comment"># -*- coding: utf-8 -*-</span></span><br><span class="line"><span class="string">&quot;&quot;&quot;</span></span><br><span class="line"><span class="string">AI 博客评论机器人 - 智能模式</span></span><br><span class="line"><span class="string">按配置比例随机选择发新评论或回复现有评论</span></span><br><span class="line"><span class="string">&quot;&quot;&quot;</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> sys</span><br><span class="line"><span class="keyword">import</span> argparse</span><br><span class="line"><span class="keyword">import</span> os</span><br><span class="line"><span class="keyword">import</span> re</span><br><span class="line"><span class="keyword">import</span> urllib.parse</span><br><span class="line"><span class="keyword">import</span> random</span><br><span class="line"><span class="keyword">from</span> typing <span class="keyword">import</span> <span class="type">List</span>, <span class="type">Dict</span></span><br><span class="line"><span class="keyword">from</span> .reply_bot <span class="keyword">import</span> run_reply_bot</span><br><span class="line"><span class="keyword">from</span> .ai_service <span class="keyword">import</span> generate_comment, generate_nick</span><br><span class="line"><span class="keyword">from</span> .twikoo_client <span class="keyword">import</span> submit_comment</span><br><span class="line"><span class="keyword">from</span> .config <span class="keyword">import</span> NEW_COMMENT_RATIO</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="comment"># ========== 文章加载 ==========</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">get_hexo_articles</span>(<span class="params">public_dir: <span class="built_in">str</span> = <span class="string">&quot;/www/wwwroot/myblog/public&quot;</span></span>) -&gt; <span class="type">List</span>[<span class="type">Dict</span>]:</span><br><span class="line">    <span class="string">&quot;&quot;&quot;从 Hexo 生成目录获取文章列表&quot;&quot;&quot;</span></span><br><span class="line">    articles = []</span><br><span class="line">    <span class="keyword">for</span> root, dirs, files <span class="keyword">in</span> os.walk(public_dir):</span><br><span class="line">        rel_path = os.path.relpath(root, public_dir)</span><br><span class="line">        <span class="keyword">if</span> <span class="keyword">not</span> re.<span class="keyword">match</span>(<span class="string">r&#x27;^\d&#123;4&#125;/\d&#123;2&#125;/\d&#123;2&#125;/&#x27;</span>, rel_path):</span><br><span class="line">            <span class="keyword">continue</span></span><br><span class="line">        <span class="keyword">if</span> <span class="string">&quot;index.html&quot;</span> <span class="keyword">not</span> <span class="keyword">in</span> files:</span><br><span class="line">            <span class="keyword">continue</span></span><br><span class="line">        url = <span class="string">f&quot;/<span class="subst">&#123;rel_path&#125;</span>/&quot;</span></span><br><span class="line">        dir_name = os.path.basename(rel_path)</span><br><span class="line">        title = urllib.parse.unquote(dir_name)</span><br><span class="line">        articles.append(&#123;<span class="string">&quot;url&quot;</span>: url, <span class="string">&quot;title&quot;</span>: title, <span class="string">&quot;content&quot;</span>: <span class="string">&quot;&quot;</span>&#125;)</span><br><span class="line">    <span class="keyword">return</span> articles</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">load_articles</span>() -&gt; <span class="type">List</span>[<span class="type">Dict</span>]:</span><br><span class="line">    <span class="string">&quot;&quot;&quot;加载文章列表&quot;&quot;&quot;</span></span><br><span class="line">    articles = get_hexo_articles()</span><br><span class="line">    <span class="keyword">if</span> articles:</span><br><span class="line">        <span class="built_in">print</span>(<span class="string">f&quot;自动获取到 <span class="subst">&#123;<span class="built_in">len</span>(articles)&#125;</span> 篇文章&quot;</span>)</span><br><span class="line">        <span class="keyword">return</span> articles</span><br><span class="line">    <span class="built_in">print</span>(<span class="string">&quot;自动获取失败&quot;</span>)</span><br><span class="line">    <span class="keyword">return</span> []</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="comment"># ========== 新评论 ==========</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">generate_mail</span>() -&gt; <span class="built_in">str</span>:</span><br><span class="line">    <span class="string">&quot;&quot;&quot;生成随机邮箱&quot;&quot;&quot;</span></span><br><span class="line">    <span class="keyword">return</span> <span class="string">f&quot;user<span class="subst">&#123;random.randint(<span class="number">1000</span>, <span class="number">9999</span>)&#125;</span>@example.com&quot;</span></span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">process_new_comment</span>(<span class="params">url: <span class="built_in">str</span>, title: <span class="built_in">str</span>, content: <span class="built_in">str</span> = <span class="string">&quot;&quot;</span></span>) -&gt; <span class="built_in">bool</span>:</span><br><span class="line">    <span class="string">&quot;&quot;&quot;发新评论&quot;&quot;&quot;</span></span><br><span class="line">    <span class="built_in">print</span>(<span class="string">f&quot;处理: <span class="subst">&#123;title&#125;</span>&quot;</span>)</span><br><span class="line"></span><br><span class="line">    comment_text = generate_comment(title, content, url)</span><br><span class="line">    <span class="keyword">if</span> <span class="keyword">not</span> comment_text:</span><br><span class="line">        <span class="built_in">print</span>(<span class="string">&quot;  AI 生成失败&quot;</span>)</span><br><span class="line">        <span class="keyword">return</span> <span class="literal">False</span></span><br><span class="line"></span><br><span class="line">    <span class="built_in">print</span>(<span class="string">f&quot;  生成: <span class="subst">&#123;comment_text[:<span class="number">50</span>]&#125;</span>...&quot;</span>)</span><br><span class="line"></span><br><span class="line">    nick = generate_nick()</span><br><span class="line">    mail = generate_mail()</span><br><span class="line">    <span class="built_in">print</span>(<span class="string">f&quot;  昵称: <span class="subst">&#123;nick&#125;</span>&quot;</span>)</span><br><span class="line">    <span class="built_in">print</span>(<span class="string">f&quot;  邮箱: <span class="subst">&#123;mail&#125;</span>&quot;</span>)</span><br><span class="line"></span><br><span class="line">    <span class="keyword">if</span> submit_comment(url, comment_text, nick, mail):</span><br><span class="line">        <span class="built_in">print</span>(<span class="string">f&quot;  ✅ 提交成功&quot;</span>)</span><br><span class="line">        <span class="keyword">return</span> <span class="literal">True</span></span><br><span class="line">    <span class="keyword">else</span>:</span><br><span class="line">        <span class="built_in">print</span>(<span class="string">f&quot;  ❌ 提交失败&quot;</span>)</span><br><span class="line">        <span class="keyword">return</span> <span class="literal">False</span></span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="comment"># ========== 智能调度 ==========</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">process_article_smart</span>(<span class="params">article: <span class="type">Dict</span>, bot_nicks: <span class="type">List</span>[<span class="built_in">str</span>], max_reply: <span class="built_in">int</span> = <span class="number">3</span></span>) -&gt; <span class="type">Dict</span>:</span><br><span class="line">    <span class="string">&quot;&quot;&quot;智能处理：随机选择发新评论或回复&quot;&quot;&quot;</span></span><br><span class="line"></span><br><span class="line">    url = article[<span class="string">&quot;url&quot;</span>]</span><br><span class="line">    title = article[<span class="string">&quot;title&quot;</span>]</span><br><span class="line">    is_new = random.randint(<span class="number">1</span>, <span class="number">100</span>) &lt;= NEW_COMMENT_RATIO</span><br><span class="line"></span><br><span class="line">    result = &#123;</span><br><span class="line">        <span class="string">&quot;url&quot;</span>: url, <span class="string">&quot;title&quot;</span>: title,</span><br><span class="line">        <span class="string">&quot;mode&quot;</span>: <span class="string">&quot;new&quot;</span> <span class="keyword">if</span> is_new <span class="keyword">else</span> <span class="string">&quot;reply&quot;</span>,</span><br><span class="line">        <span class="string">&quot;success&quot;</span>: <span class="literal">False</span>, <span class="string">&quot;detail&quot;</span>: <span class="string">&quot;&quot;</span></span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">if</span> is_new:</span><br><span class="line">        <span class="built_in">print</span>(<span class="string">f&quot;\n📝 [<span class="subst">&#123;title&#125;</span>] 随机选择：发新评论&quot;</span>)</span><br><span class="line">        success = process_new_comment(url, title, article.get(<span class="string">&quot;content&quot;</span>, <span class="string">&quot;&quot;</span>))</span><br><span class="line">        result[<span class="string">&quot;success&quot;</span>] = success</span><br><span class="line">        result[<span class="string">&quot;detail&quot;</span>] = <span class="string">&quot;新评论提交成功&quot;</span> <span class="keyword">if</span> success <span class="keyword">else</span> <span class="string">&quot;新评论提交失败&quot;</span></span><br><span class="line">    <span class="keyword">else</span>:</span><br><span class="line">        <span class="built_in">print</span>(<span class="string">f&quot;\n💬 [<span class="subst">&#123;title&#125;</span>] 随机选择：回复评论&quot;</span>)</span><br><span class="line">        count = run_reply_bot(url, bot_nicks, max_reply=max_reply)</span><br><span class="line">        result[<span class="string">&quot;success&quot;</span>] = count &gt; <span class="number">0</span></span><br><span class="line">        result[<span class="string">&quot;detail&quot;</span>] = <span class="string">f&quot;回复了 <span class="subst">&#123;count&#125;</span> 条评论&quot;</span></span><br><span class="line"></span><br><span class="line">    <span class="keyword">return</span> result</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">run_smart</span>(<span class="params">articles: <span class="type">List</span>[<span class="type">Dict</span>], bot_nicks: <span class="type">List</span>[<span class="built_in">str</span>] = <span class="literal">None</span>, max_reply: <span class="built_in">int</span> = <span class="number">3</span></span>):</span><br><span class="line">    <span class="string">&quot;&quot;&quot;批量智能处理&quot;&quot;&quot;</span></span><br><span class="line">    <span class="keyword">if</span> bot_nicks <span class="keyword">is</span> <span class="literal">None</span>:</span><br><span class="line">        bot_nicks = [<span class="string">&quot;AI助手&quot;</span>, <span class="string">&quot;博主助手&quot;</span>]</span><br><span class="line"></span><br><span class="line">    stats = &#123;<span class="string">&quot;new&quot;</span>: <span class="number">0</span>, <span class="string">&quot;reply&quot;</span>: <span class="number">0</span>, <span class="string">&quot;new_ok&quot;</span>: <span class="number">0</span>, <span class="string">&quot;reply_ok&quot;</span>: <span class="number">0</span>&#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">for</span> article <span class="keyword">in</span> articles:</span><br><span class="line">        r = process_article_smart(article, bot_nicks, max_reply)</span><br><span class="line">        stats[r[<span class="string">&quot;mode&quot;</span>]] += <span class="number">1</span></span><br><span class="line">        <span class="keyword">if</span> r[<span class="string">&quot;success&quot;</span>]:</span><br><span class="line">            stats[<span class="string">f&quot;<span class="subst">&#123;r[<span class="string">&#x27;mode&#x27;</span>]&#125;</span>_ok&quot;</span>] += <span class="number">1</span></span><br><span class="line"></span><br><span class="line">    <span class="built_in">print</span>(<span class="string">f&quot;\n<span class="subst">&#123;<span class="string">&#x27;=&#x27;</span>*<span class="number">50</span>&#125;</span>&quot;</span>)</span><br><span class="line">    <span class="built_in">print</span>(<span class="string">&quot;📊 执行统计&quot;</span>)</span><br><span class="line">    <span class="built_in">print</span>(<span class="string">f&quot;<span class="subst">&#123;<span class="string">&#x27;=&#x27;</span>*<span class="number">50</span>&#125;</span>&quot;</span>)</span><br><span class="line">    <span class="built_in">print</span>(<span class="string">f&quot;总文章: <span class="subst">&#123;<span class="built_in">len</span>(articles)&#125;</span>&quot;</span>)</span><br><span class="line">    <span class="built_in">print</span>(<span class="string">f&quot;新评论: <span class="subst">&#123;stats[<span class="string">&#x27;new&#x27;</span>]&#125;</span> 篇 (成功 <span class="subst">&#123;stats[<span class="string">&#x27;new_ok&#x27;</span>]&#125;</span>)&quot;</span>)</span><br><span class="line">    <span class="built_in">print</span>(<span class="string">f&quot;回  复: <span class="subst">&#123;stats[<span class="string">&#x27;reply&#x27;</span>]&#125;</span> 篇 (成功 <span class="subst">&#123;stats[<span class="string">&#x27;reply_ok&#x27;</span>]&#125;</span>)&quot;</span>)</span><br><span class="line">    <span class="built_in">print</span>(<span class="string">f&quot;设置比例: <span class="subst">&#123;NEW_COMMENT_RATIO&#125;</span>%&quot;</span>)</span><br><span class="line">    actual = <span class="built_in">round</span>(stats[<span class="string">&#x27;new&#x27;</span>]/<span class="built_in">len</span>(articles)*<span class="number">100</span>, <span class="number">1</span>) <span class="keyword">if</span> articles <span class="keyword">else</span> <span class="number">0</span></span><br><span class="line">    <span class="built_in">print</span>(<span class="string">f&quot;实际比例: <span class="subst">&#123;actual&#125;</span>%&quot;</span>)</span><br><span class="line">    <span class="built_in">print</span>(<span class="string">f&quot;<span class="subst">&#123;<span class="string">&#x27;=&#x27;</span>*<span class="number">50</span>&#125;</span>&quot;</span>)</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="comment"># ========== CLI ==========</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">main</span>():</span><br><span class="line">    parser = argparse.ArgumentParser(description=<span class="string">&quot;AI Blog Bot&quot;</span>)</span><br><span class="line">    parser.add_argument(<span class="string">&quot;command&quot;</span>, choices=[<span class="string">&quot;run&quot;</span>, <span class="string">&quot;list&quot;</span>])</span><br><span class="line">    parser.add_argument(<span class="string">&quot;--id&quot;</span>, <span class="built_in">type</span>=<span class="built_in">str</span>, <span class="built_in">help</span>=<span class="string">&quot;文章序号，如 12 或 1,3,5-10&quot;</span>)</span><br><span class="line">    parser.add_argument(<span class="string">&quot;--count&quot;</span>, <span class="built_in">type</span>=<span class="built_in">int</span>, default=<span class="number">3</span>, <span class="built_in">help</span>=<span class="string">&quot;回复上限（默认3）&quot;</span>)</span><br><span class="line">    parser.add_argument(<span class="string">&quot;--ratio&quot;</span>, <span class="built_in">type</span>=<span class="built_in">int</span>, <span class="built_in">help</span>=<span class="string">&quot;临时新评论比例 1-100&quot;</span>)</span><br><span class="line"></span><br><span class="line">    args = parser.parse_args()</span><br><span class="line"></span><br><span class="line">    <span class="keyword">if</span> args.command == <span class="string">&quot;list&quot;</span>:</span><br><span class="line">        articles = load_articles()</span><br><span class="line">        <span class="built_in">print</span>(<span class="string">f&quot;\n<span class="subst">&#123;<span class="string">&#x27;序号&#x27;</span>:&lt;<span class="number">6</span>&#125;</span><span class="subst">&#123;<span class="string">&#x27;标题&#x27;</span>:&lt;<span class="number">40</span>&#125;</span><span class="subst">&#123;<span class="string">&#x27;URL&#x27;</span>&#125;</span>&quot;</span>)</span><br><span class="line">        <span class="built_in">print</span>(<span class="string">&quot;-&quot;</span> * <span class="number">80</span>)</span><br><span class="line">        <span class="keyword">for</span> i, a <span class="keyword">in</span> <span class="built_in">enumerate</span>(articles, <span class="number">1</span>):</span><br><span class="line">            <span class="built_in">print</span>(<span class="string">f&quot;<span class="subst">&#123;i:&lt;<span class="number">6</span>&#125;</span><span class="subst">&#123;a[<span class="string">&#x27;title&#x27;</span>][:<span class="number">38</span>]:&lt;<span class="number">40</span>&#125;</span><span class="subst">&#123;a[<span class="string">&#x27;url&#x27;</span>][:<span class="number">50</span>]&#125;</span>&quot;</span>)</span><br><span class="line">        <span class="built_in">print</span>(<span class="string">f&quot;\n共 <span class="subst">&#123;<span class="built_in">len</span>(articles)&#125;</span> 篇&quot;</span>)</span><br><span class="line">        <span class="keyword">return</span></span><br><span class="line"></span><br><span class="line">    <span class="comment"># 临时覆盖比例</span></span><br><span class="line">    <span class="keyword">if</span> args.ratio <span class="keyword">is</span> <span class="keyword">not</span> <span class="literal">None</span>:</span><br><span class="line">        <span class="keyword">global</span> NEW_COMMENT_RATIO</span><br><span class="line">        NEW_COMMENT_RATIO = <span class="built_in">max</span>(<span class="number">1</span>, <span class="built_in">min</span>(<span class="number">100</span>, args.ratio))</span><br><span class="line">        <span class="built_in">print</span>(<span class="string">f&quot;临时比例: <span class="subst">&#123;NEW_COMMENT_RATIO&#125;</span>%&quot;</span>)</span><br><span class="line"></span><br><span class="line">    bot_nicks = [<span class="string">&quot;AI助手&quot;</span>, <span class="string">&quot;博主助手&quot;</span>]</span><br><span class="line">    articles = load_articles()</span><br><span class="line"></span><br><span class="line">    <span class="keyword">if</span> args.<span class="built_in">id</span>:</span><br><span class="line">        selected = []</span><br><span class="line">        <span class="keyword">for</span> part <span class="keyword">in</span> args.<span class="built_in">id</span>.split(<span class="string">&#x27;,&#x27;</span>):</span><br><span class="line">            <span class="keyword">if</span> <span class="string">&#x27;-&#x27;</span> <span class="keyword">in</span> part:</span><br><span class="line">                s, e = <span class="built_in">map</span>(<span class="built_in">int</span>, part.split(<span class="string">&#x27;-&#x27;</span>))</span><br><span class="line">                selected.extend([articles[i-<span class="number">1</span>] <span class="keyword">for</span> i <span class="keyword">in</span> <span class="built_in">range</span>(s, e+<span class="number">1</span>) <span class="keyword">if</span> <span class="number">1</span> &lt;= i &lt;= <span class="built_in">len</span>(articles)])</span><br><span class="line">            <span class="keyword">else</span>:</span><br><span class="line">                i = <span class="built_in">int</span>(part)</span><br><span class="line">                <span class="keyword">if</span> <span class="number">1</span> &lt;= i &lt;= <span class="built_in">len</span>(articles):</span><br><span class="line">                    selected.append(articles[i-<span class="number">1</span>])</span><br><span class="line">        <span class="built_in">print</span>(<span class="string">f&quot;处理 <span class="subst">&#123;<span class="built_in">len</span>(selected)&#125;</span> 篇&quot;</span>)</span><br><span class="line">        run_smart(selected, bot_nicks, max_reply=args.count)</span><br><span class="line">    <span class="keyword">else</span>:</span><br><span class="line">        run_smart(articles, bot_nicks, max_reply=args.count)</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="keyword">if</span> __name__ == <span class="string">&quot;__main__&quot;</span>:</span><br><span class="line">    main()</span><br></pre></td></tr></table></figure><h2 id="使用方法"><a href="#使用方法" class="headerlink" title="使用方法"></a>使用方法</h2><h3 id="基础用法"><a href="#基础用法" class="headerlink" title="基础用法"></a>基础用法</h3><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">#查看文章列表</span></span><br><span class="line">python -m ai_bot.main list</span><br><span class="line"></span><br><span class="line"><span class="comment"># 处理单篇文章（按配置比例随机选择模式）</span></span><br><span class="line">python -m ai_bot.main run --<span class="built_in">id</span> 3</span><br><span class="line"></span><br><span class="line"><span class="comment"># 处理多篇</span></span><br><span class="line">python -m ai_bot.main run --<span class="built_in">id</span> 1,3,5</span><br><span class="line"></span><br><span class="line"><span class="comment"># 处理范围</span></span><br><span class="line">python -m ai_bot.main run --<span class="built_in">id</span> 1-10</span><br></pre></td></tr></table></figure><h3 id="模式控制"><a href="#模式控制" class="headerlink" title="模式控制"></a>模式控制</h3><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># 强制发新评论（100% 概率）</span></span><br><span class="line">python -m ai_bot.main run --<span class="built_in">id</span> 3 --ratio 100</span><br><span class="line"></span><br><span class="line"><span class="comment"># 强制回复模式（0% 概率发新评论）</span></span><br><span class="line">python -m ai_bot.main run --<span class="built_in">id</span> 3 --ratio 0</span><br><span class="line"></span><br><span class="line"><span class="comment"># 回复模式下，最多回复 5 条现有评论</span></span><br><span class="line">python -m ai_bot.main run --<span class="built_in">id</span> 3 --ratio 0 --count 5</span><br></pre></td></tr></table></figure><h3 id="批量处理"><a href="#批量处理" class="headerlink" title="批量处理"></a>批量处理</h3><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># 全部文章（按默认比例）</span></span><br><span class="line">python -m ai_bot.main run</span><br><span class="line"></span><br><span class="line"><span class="comment"># 指定范围 + 强制新评论</span></span><br><span class="line">python -m ai_bot.main run --<span class="built_in">id</span> 5-20 --ratio 100</span><br></pre></td></tr></table></figure><h3 id="相关参数"><a href="#相关参数" class="headerlink" title="相关参数"></a>相关参数</h3><table><thead><tr><th>参数</th><th>必填</th><th>默认值</th><th>说明</th></tr></thead><tbody><tr><td><code>--id</code></td><td>否</td><td>全部</td><td>文章序号，如 <code>3</code> &#x2F; <code>1,3</code> &#x2F; <code>5-10</code></td></tr><tr><td><code>--ratio</code></td><td>否</td><td>配置值</td><td>临时覆盖新评论比例，1-100</td></tr><tr><td><code>--count</code></td><td>否</td><td>3</td><td>回复模式下单篇最大回复数</td></tr></tbody></table><h2 id="后续扩展"><a href="#后续扩展" class="headerlink" title="后续扩展"></a>后续扩展</h2><p><strong>AI 互动机器人</strong>：注册固定昵称（如 <code>@小助手</code>），访客可在评论区 <code>@</code> 主动提问，AI 实时检测触发词并回复，支持多轮对话，从单向评论输出升级为双向互动助手。</p>]]></content:encoded>
      
      
      <category domain="https://blog.eucalyptus.cc/categories/AI/">AI</category>
      
      
      <category domain="https://blog.eucalyptus.cc/tags/CommentBot/">CommentBot</category>
      
      
      <comments>https://blog.eucalyptus.cc/2026/06/08/%E8%A7%A3%E5%86%B3%E5%8D%9A%E5%AE%A2%E9%9B%B6%E8%AF%84%E8%AE%BA%E5%9B%B0%E5%A2%83%EF%BC%9AAI-%E8%AF%84%E8%AE%BA%E6%9C%BA%E5%99%A8%E4%BA%BA%E9%83%A8%E7%BD%B2%E5%85%A8%E8%AE%B0%E5%BD%95/#disqus_thread</comments>
      
    </item>
    
    <item>
      <title>从 Ollama 到云端：Hexo AI 摘要的多平台进化之路</title>
      <link>https://blog.eucalyptus.cc/2026/06/01/%E4%BB%8E-Ollama-%E5%88%B0%E4%BA%91%E7%AB%AF%EF%BC%9AHexo-AI-%E6%91%98%E8%A6%81%E7%9A%84%E5%A4%9A%E5%B9%B3%E5%8F%B0%E8%BF%9B%E5%8C%96%E4%B9%8B%E8%B7%AF/</link>
      <guid>https://blog.eucalyptus.cc/2026/06/01/%E4%BB%8E-Ollama-%E5%88%B0%E4%BA%91%E7%AB%AF%EF%BC%9AHexo-AI-%E6%91%98%E8%A6%81%E7%9A%84%E5%A4%9A%E5%B9%B3%E5%8F%B0%E8%BF%9B%E5%8C%96%E4%B9%8B%E8%B7%AF/</guid>
      <pubDate>Mon, 01 Jun 2026 05:16:37 GMT</pubDate>
      
        
        
          
          
      <description>&lt;h1 id=&quot;从-Ollama-到云端：Hexo-AI-摘要的多平台进化之路&quot;&gt;&lt;a href=&quot;#从-Ollama-到云端：Hexo-AI-摘要的多平台进化之路&quot; class=&quot;headerlink&quot; title=&quot;从 Ollama 到云端：Hexo AI</description>
          
        
      
      
      
      <content:encoded><![CDATA[<h1 id="从-Ollama-到云端：Hexo-AI-摘要的多平台进化之路"><a href="#从-Ollama-到云端：Hexo-AI-摘要的多平台进化之路" class="headerlink" title="从 Ollama 到云端：Hexo AI 摘要的多平台进化之路"></a>从 Ollama 到云端：Hexo AI 摘要的多平台进化之路</h1><blockquote><p>之前我写了《使用 Ollama 为 Hexo 博客部署 AI 文章摘要》，介绍了本地部署大模型生成摘要的方案。运行一段时间后，发现几个问题：3B 模型效果一般，更大的模型又吃资源；生成速度慢，用户体验不佳；服务器配置有限，经常卡顿。于是，我重新设计了一套架构：保留前端交互，后端加入代理层，支持多个云端 API，并用 Redis 缓存加速。本文分享这个升级过程。</p></blockquote><div class='anzhiyu-tag-link'><a class="tag-Link"  href="/2026/05/16/%E4%BD%BF%E7%94%A8-Ollama-%E4%B8%BA-Hexo-%E5%8D%9A%E5%AE%A2%E9%83%A8%E7%BD%B2-AI-%E6%96%87%E7%AB%A0%E6%91%98%E8%A6%81/">    <div class="tag-link-tips">站内地址</div>    <div class="tag-link-bottom">        <div class="tag-link-left" data-icon="https://img.eucalyptus.cc/images/touxiang.avif" style="width:60px;height:60px;min-width:60px;min-height:60px;position:relative;border-radius:var(--anzhiyu-border-radius);overflow:hidden;">          <img class="tag-link-favicon" src="https://img.eucalyptus.cc/images/touxiang.avif" onerror="this.style.display='none';this.nextElementSibling.style.display='flex';" style="width:60px;height:60px;object-fit:cover;border-radius:var(--anzhiyu-border-radius);position:absolute;top:0;left:0;" alt="favicon"/>          <i class="fa-solid fa-link" style="display:flex;align-items:center;justify-content:center;width:100%;height:100%;"></i>        </div>        <div class="tag-link-right">            <div class="tag-link-title">使用 Ollama 为 Hexo 博客部署 AI 文章摘要</div>            <div class="tag-link-sitename">本站文章</div>        </div>        <i class="eucalyptus icon-angle-right"></i>    </div>    </a></div> <h2 id="Ollama-方案的局限"><a href="#Ollama-方案的局限" class="headerlink" title="Ollama 方案的局限"></a>Ollama 方案的局限</h2><p>之前的架构：</p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">前端 JS → Ollama API (本地 11434 端口)</span><br></pre></td></tr></table></figure><p>遇到的问题：</p><table><thead><tr><th>问题</th><th>说明</th></tr></thead><tbody><tr><td>模型效果</td><td>qwen2.5:3b 摘要质量一般，7b 又太慢</td></tr><tr><td>资源占用</td><td>8G 内存服务器，跑模型容易卡死</td></tr><tr><td>生成速度</td><td>本地生成一次摘要要 5-20 秒</td></tr><tr><td>维护成本</td><td>需要定期更新模型，管理模型文件</td></tr></tbody></table><h2 id="新架构设计"><a href="#新架构设计" class="headerlink" title="新架构设计"></a>新架构设计</h2><p>升级后的架构：</p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">前端 JS → Nginx → Node.js 代理 → Redis 缓存 / 云端 API</span><br><span class="line">              ↑</span><br><span class="line">         静态文件 (Hexo)</span><br></pre></td></tr></table></figure><p>核心变化：</p><ol><li><strong>加入代理层</strong>：前端不再直接调 AI API，而是通过代理转发</li><li><strong>支持多平台</strong>：日日新、DeepSeek、Ollama 自由切换</li><li><strong>Redis 缓存</strong>：相同文章直接返回缓存，不重复调用 API</li><li><strong>Key 安全</strong>：API Key 放在服务器，前端不可见</li></ol><h2 id="部署"><a href="#部署" class="headerlink" title="部署"></a>部署</h2><h3 id="Docker拉起Redis-容器编排"><a href="#Docker拉起Redis-容器编排" class="headerlink" title="Docker拉起Redis(容器编排)"></a>Docker拉起Redis(容器编排)</h3><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># 创建目录</span></span><br><span class="line"><span class="built_in">mkdir</span> -p /opt/redis/data</span><br><span class="line"><span class="built_in">cd</span> /opt/redis</span><br><span class="line"></span><br><span class="line"><span class="comment"># 创建 docker-compose.yml</span></span><br><span class="line"><span class="built_in">cat</span> &gt; docker-compose.yml &lt;&lt; <span class="string">&#x27;EOF&#x27;</span></span><br><span class="line">version: <span class="string">&#x27;3.8&#x27;</span></span><br><span class="line">services:</span><br><span class="line">  redis:</span><br><span class="line">    image: redis:7-alpine</span><br><span class="line">    container_name: redis-ai</span><br><span class="line">    restart: always</span><br><span class="line">    ports:</span><br><span class="line">      - <span class="string">&quot;127.0.0.1:6379:6379&quot;</span></span><br><span class="line">    volumes:</span><br><span class="line">      - /var/lib/docker//redis:/data</span><br><span class="line">    <span class="built_in">command</span>: redis-server --appendonly <span class="built_in">yes</span></span><br><span class="line">EOF</span><br><span class="line"></span><br><span class="line"><span class="comment"># 启动</span></span><br><span class="line">docker-compose up -d</span><br><span class="line"></span><br><span class="line"><span class="comment"># 验证</span></span><br><span class="line">docker <span class="built_in">exec</span> redis-ai redis-cli ping</span><br></pre></td></tr></table></figure><!-- 这是一张图片，ocr 内容为： --><p><img src="https://img.eucalyptus.cc/images/20260601/1.avif"></p><h3 id="部署AI代理"><a href="#部署AI代理" class="headerlink" title="部署AI代理"></a>部署AI代理</h3><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># 创建目录</span></span><br><span class="line"><span class="built_in">mkdir</span> -p /opt/ai-proxy</span><br><span class="line"><span class="built_in">cd</span> /opt/ai-proxy</span><br><span class="line"></span><br><span class="line"><span class="comment"># 初始化</span></span><br><span class="line">npm init -y</span><br><span class="line">npm install express node-fetch ioredis</span><br><span class="line"></span><br><span class="line"><span class="comment"># 创建 index.js（代理代码）</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># 启动</span></span><br><span class="line">pm2 start index.js --name ai-proxy</span><br><span class="line">pm2 save</span><br><span class="line">pm2 startup</span><br></pre></td></tr></table></figure><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br><span class="line">89</span><br><span class="line">90</span><br><span class="line">91</span><br><span class="line">92</span><br><span class="line">93</span><br><span class="line">94</span><br><span class="line">95</span><br><span class="line">96</span><br><span class="line">97</span><br><span class="line">98</span><br><span class="line">99</span><br><span class="line">100</span><br><span class="line">101</span><br><span class="line">102</span><br><span class="line">103</span><br><span class="line">104</span><br><span class="line">105</span><br><span class="line">106</span><br><span class="line">107</span><br><span class="line">108</span><br><span class="line">109</span><br><span class="line">110</span><br><span class="line">111</span><br><span class="line">112</span><br><span class="line">113</span><br><span class="line">114</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> express = <span class="built_in">require</span>(<span class="string">&#x27;express&#x27;</span>);</span><br><span class="line"><span class="keyword">const</span> fetch = <span class="built_in">require</span>(<span class="string">&#x27;node-fetch&#x27;</span>);</span><br><span class="line"><span class="keyword">const</span> <span class="title class_">Redis</span> = <span class="built_in">require</span>(<span class="string">&#x27;ioredis&#x27;</span>);</span><br><span class="line"><span class="keyword">const</span> crypto = <span class="built_in">require</span>(<span class="string">&#x27;crypto&#x27;</span>);</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> app = <span class="title function_">express</span>();</span><br><span class="line"></span><br><span class="line"><span class="comment">// Redis 连接</span></span><br><span class="line"><span class="keyword">const</span> redis = <span class="keyword">new</span> <span class="title class_">Redis</span>(&#123;</span><br><span class="line">  <span class="attr">host</span>: <span class="string">&#x27;127.0.0.1&#x27;</span>,</span><br><span class="line">  <span class="attr">port</span>: <span class="number">6379</span>,</span><br><span class="line">  <span class="attr">db</span>: <span class="number">0</span></span><br><span class="line">&#125;);</span><br><span class="line"></span><br><span class="line">redis.<span class="title function_">on</span>(<span class="string">&#x27;connect&#x27;</span>, <span class="function">() =&gt;</span> <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">&#x27;[Redis] Connected&#x27;</span>));</span><br><span class="line">redis.<span class="title function_">on</span>(<span class="string">&#x27;error&#x27;</span>, <span class="function">(<span class="params">err</span>) =&gt;</span> <span class="variable language_">console</span>.<span class="title function_">error</span>(<span class="string">&#x27;[Redis] Error:&#x27;</span>, err));</span><br><span class="line"></span><br><span class="line"><span class="comment">// 缓存时间：7天（秒）</span></span><br><span class="line"><span class="keyword">const</span> <span class="variable constant_">CACHE_TTL</span> = <span class="number">7</span> * <span class="number">24</span> * <span class="number">60</span> * <span class="number">60</span>;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 只用内容哈希生成缓存 key</span></span><br><span class="line"><span class="keyword">function</span> <span class="title function_">getCacheKey</span>(<span class="params">text</span>) &#123;</span><br><span class="line">  <span class="keyword">const</span> contentHash = crypto.<span class="title function_">createHash</span>(<span class="string">&#x27;md5&#x27;</span>).<span class="title function_">update</span>(text.<span class="title function_">slice</span>(<span class="number">0</span>, <span class="number">4000</span>)).<span class="title function_">digest</span>(<span class="string">&#x27;hex&#x27;</span>).<span class="title function_">substring</span>(<span class="number">0</span>, <span class="number">16</span>);</span><br><span class="line">  <span class="keyword">return</span> <span class="string">`ai:summary:<span class="subst">$&#123;contentHash&#125;</span>`</span>;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">app.<span class="title function_">use</span>(express.<span class="title function_">json</span>());</span><br><span class="line"></span><br><span class="line">app.<span class="title function_">use</span>(<span class="function">(<span class="params">req, res, next</span>) =&gt;</span> &#123;</span><br><span class="line">  <span class="keyword">const</span> allowedOrigins = [<span class="string">&#x27;https://blog.eucalyptus.cc&#x27;</span>];</span><br><span class="line">  <span class="keyword">const</span> origin = req.<span class="property">headers</span>.<span class="property">origin</span>;</span><br><span class="line">  </span><br><span class="line">  <span class="keyword">if</span> (allowedOrigins.<span class="title function_">includes</span>(origin)) &#123;</span><br><span class="line">    res.<span class="title function_">header</span>(<span class="string">&#x27;Access-Control-Allow-Origin&#x27;</span>, origin);</span><br><span class="line">  &#125;</span><br><span class="line">  </span><br><span class="line">  res.<span class="title function_">header</span>(<span class="string">&#x27;Access-Control-Allow-Methods&#x27;</span>, <span class="string">&#x27;POST, OPTIONS&#x27;</span>);</span><br><span class="line">  res.<span class="title function_">header</span>(<span class="string">&#x27;Access-Control-Allow-Headers&#x27;</span>, <span class="string">&#x27;Content-Type, Authorization&#x27;</span>);</span><br><span class="line">  </span><br><span class="line">  <span class="keyword">if</span> (req.<span class="property">method</span> === <span class="string">&#x27;OPTIONS&#x27;</span>) <span class="keyword">return</span> res.<span class="title function_">sendStatus</span>(<span class="number">200</span>);</span><br><span class="line">  <span class="title function_">next</span>();</span><br><span class="line">&#125;);</span><br><span class="line"></span><br><span class="line">app.<span class="title function_">post</span>(<span class="string">&#x27;/api/ai-summary&#x27;</span>, <span class="title function_">async</span> (req, res) =&gt; &#123;</span><br><span class="line">  <span class="keyword">const</span> &#123; text &#125; = req.<span class="property">body</span> || &#123;&#125;;</span><br><span class="line">  </span><br><span class="line">  <span class="keyword">if</span> (!text || text.<span class="property">length</span> &lt; <span class="number">50</span>) &#123;</span><br><span class="line">    <span class="keyword">return</span> res.<span class="title function_">status</span>(<span class="number">400</span>).<span class="title function_">json</span>(&#123; <span class="attr">error</span>: <span class="string">&#x27;Text too short&#x27;</span> &#125;);</span><br><span class="line">  &#125;</span><br><span class="line">  </span><br><span class="line">  <span class="keyword">const</span> cacheKey = <span class="title function_">getCacheKey</span>(text);</span><br><span class="line">  </span><br><span class="line">  <span class="keyword">try</span> &#123;</span><br><span class="line">    <span class="comment">// 1. 查 Redis 缓存</span></span><br><span class="line">    <span class="keyword">const</span> cached = <span class="keyword">await</span> redis.<span class="title function_">get</span>(cacheKey);</span><br><span class="line">    <span class="keyword">if</span> (cached) &#123;</span><br><span class="line">      <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">&#x27;[Cache] Hit:&#x27;</span>, cacheKey);</span><br><span class="line">      <span class="keyword">return</span> res.<span class="title function_">json</span>(&#123; <span class="attr">summary</span>: cached, <span class="attr">cached</span>: <span class="literal">true</span> &#125;);</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">&#x27;[Cache] Miss:&#x27;</span>, cacheKey);</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 2. 调日日新 API</span></span><br><span class="line">    <span class="keyword">const</span> response = <span class="keyword">await</span> <span class="title function_">fetch</span>(<span class="string">&#x27;https://token.sensenova.cn/v1/chat/completions&#x27;</span>, &#123;</span><br><span class="line">      <span class="attr">method</span>: <span class="string">&#x27;POST&#x27;</span>,</span><br><span class="line">      <span class="attr">headers</span>: &#123;</span><br><span class="line">        <span class="string">&#x27;Authorization&#x27;</span>: <span class="string">&#x27;Bearer 输入自己的key&#x27;</span>,</span><br><span class="line">        <span class="string">&#x27;Content-Type&#x27;</span>: <span class="string">&#x27;application/json&#x27;</span></span><br><span class="line">      &#125;,</span><br><span class="line">      <span class="attr">body</span>: <span class="title class_">JSON</span>.<span class="title function_">stringify</span>(&#123;</span><br><span class="line">        <span class="attr">model</span>: <span class="string">&#x27;deepseek-v4-flash&#x27;</span>,</span><br><span class="line">        <span class="attr">messages</span>: [</span><br><span class="line">          &#123;</span><br><span class="line">            <span class="attr">role</span>: <span class="string">&#x27;system&#x27;</span>,</span><br><span class="line">            <span class="attr">content</span>: <span class="string">&#x27;请为以下文章生成一段简洁的摘要，突出核心内容。&#x27;</span></span><br><span class="line">          &#125;,</span><br><span class="line">          &#123;</span><br><span class="line">            <span class="attr">role</span>: <span class="string">&#x27;user&#x27;</span>,</span><br><span class="line">            <span class="attr">content</span>: text.<span class="title function_">slice</span>(<span class="number">0</span>, <span class="number">4000</span>)</span><br><span class="line">          &#125;</span><br><span class="line">        ],</span><br><span class="line">        <span class="attr">max_tokens</span>: <span class="number">200</span>,</span><br><span class="line">        <span class="attr">temperature</span>: <span class="number">0.3</span></span><br><span class="line">      &#125;)</span><br><span class="line">    &#125;);</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">if</span> (!response.<span class="property">ok</span>) &#123;</span><br><span class="line">      <span class="keyword">const</span> error = <span class="keyword">await</span> response.<span class="title function_">text</span>();</span><br><span class="line">      <span class="keyword">return</span> res.<span class="title function_">status</span>(response.<span class="property">status</span>).<span class="title function_">json</span>(&#123; error &#125;);</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">const</span> data = <span class="keyword">await</span> response.<span class="title function_">json</span>();</span><br><span class="line">    <span class="keyword">const</span> summary = data.<span class="property">choices</span>?.[<span class="number">0</span>]?.<span class="property">message</span>?.<span class="property">content</span>?.<span class="title function_">trim</span>();</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">if</span> (!summary) &#123;</span><br><span class="line">      <span class="keyword">return</span> res.<span class="title function_">status</span>(<span class="number">500</span>).<span class="title function_">json</span>(&#123; <span class="attr">error</span>: <span class="string">&#x27;Empty summary&#x27;</span> &#125;);</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 3. 写入 Redis 缓存</span></span><br><span class="line">    <span class="keyword">await</span> redis.<span class="title function_">setex</span>(cacheKey, <span class="variable constant_">CACHE_TTL</span>, summary);</span><br><span class="line">    <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">&#x27;[Cache] Saved:&#x27;</span>, cacheKey);</span><br><span class="line">    </span><br><span class="line">    res.<span class="title function_">json</span>(&#123; summary, <span class="attr">cached</span>: <span class="literal">false</span> &#125;);</span><br><span class="line">    </span><br><span class="line">  &#125; <span class="keyword">catch</span> (err) &#123;</span><br><span class="line">    <span class="variable language_">console</span>.<span class="title function_">error</span>(<span class="string">&#x27;[AI Proxy] Error:&#x27;</span>, err.<span class="property">message</span>);</span><br><span class="line">    res.<span class="title function_">status</span>(<span class="number">500</span>).<span class="title function_">json</span>(&#123; <span class="attr">error</span>: err.<span class="property">message</span> &#125;);</span><br><span class="line">  &#125;</span><br><span class="line">&#125;);</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> <span class="variable constant_">PORT</span> = <span class="number">3000</span>;</span><br><span class="line">app.<span class="title function_">listen</span>(<span class="variable constant_">PORT</span>, <span class="string">&#x27;127.0.0.1&#x27;</span>, <span class="function">() =&gt;</span> &#123;</span><br><span class="line">  <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">`AI Proxy running on http://127.0.0.1:<span class="subst">$&#123;PORT&#125;</span>`</span>);</span><br><span class="line">&#125;);</span><br></pre></td></tr></table></figure><!-- 这是一张图片，ocr 内容为： --><p><img src="https://img.eucalyptus.cc/images/20260601/2.avif"></p><h3 id="Nginx反向代理"><a href="#Nginx反向代理" class="headerlink" title="Nginx反向代理"></a>Nginx反向代理</h3><p>这里直接再宝塔面板中配置</p><!-- 这是一张图片，ocr 内容为： --><p><img src="https://img.eucalyptus.cc/images/20260601/3.avif"></p><h3 id="前端集成"><a href="#前端集成" class="headerlink" title="前端集成"></a>前端集成</h3><p>这里不在做过多缀述，参考之前的文章即可，但是需要修改ai-summary.js代码</p><div class='anzhiyu-tag-link'><a class="tag-Link"  href="/2026/05/16/%E4%BD%BF%E7%94%A8-Ollama-%E4%B8%BA-Hexo-%E5%8D%9A%E5%AE%A2%E9%83%A8%E7%BD%B2-AI-%E6%96%87%E7%AB%A0%E6%91%98%E8%A6%81/">    <div class="tag-link-tips">站内地址</div>    <div class="tag-link-bottom">        <div class="tag-link-left" data-icon="https://img.eucalyptus.cc/images/touxiang.avif" style="width:60px;height:60px;min-width:60px;min-height:60px;position:relative;border-radius:var(--anzhiyu-border-radius);overflow:hidden;">          <img class="tag-link-favicon" src="https://img.eucalyptus.cc/images/touxiang.avif" onerror="this.style.display='none';this.nextElementSibling.style.display='flex';" style="width:60px;height:60px;object-fit:cover;border-radius:var(--anzhiyu-border-radius);position:absolute;top:0;left:0;" alt="favicon"/>          <i class="fa-solid fa-link" style="display:flex;align-items:center;justify-content:center;width:100%;height:100%;"></i>        </div>        <div class="tag-link-right">            <div class="tag-link-title">使用 Ollama 为 Hexo 博客部署 AI 文章摘要</div>            <div class="tag-link-sitename">本站文章</div>        </div>        <i class="eucalyptus icon-angle-right"></i>    </div>    </a></div><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br><span class="line">89</span><br><span class="line">90</span><br><span class="line">91</span><br><span class="line">92</span><br><span class="line">93</span><br><span class="line">94</span><br><span class="line">95</span><br><span class="line">96</span><br><span class="line">97</span><br><span class="line">98</span><br><span class="line">99</span><br><span class="line">100</span><br><span class="line">101</span><br><span class="line">102</span><br><span class="line">103</span><br><span class="line">104</span><br><span class="line">105</span><br><span class="line">106</span><br><span class="line">107</span><br><span class="line">108</span><br><span class="line">109</span><br><span class="line">110</span><br><span class="line">111</span><br><span class="line">112</span><br><span class="line">113</span><br><span class="line">114</span><br><span class="line">115</span><br><span class="line">116</span><br><span class="line">117</span><br><span class="line">118</span><br><span class="line">119</span><br><span class="line">120</span><br><span class="line">121</span><br><span class="line">122</span><br><span class="line">123</span><br><span class="line">124</span><br><span class="line">125</span><br><span class="line">126</span><br><span class="line">127</span><br><span class="line">128</span><br><span class="line">129</span><br><span class="line">130</span><br><span class="line">131</span><br><span class="line">132</span><br><span class="line">133</span><br><span class="line">134</span><br><span class="line">135</span><br><span class="line">136</span><br><span class="line">137</span><br><span class="line">138</span><br><span class="line">139</span><br><span class="line">140</span><br><span class="line">141</span><br><span class="line">142</span><br><span class="line">143</span><br><span class="line">144</span><br><span class="line">145</span><br><span class="line">146</span><br><span class="line">147</span><br><span class="line">148</span><br><span class="line">149</span><br><span class="line">150</span><br><span class="line">151</span><br><span class="line">152</span><br><span class="line">153</span><br><span class="line">154</span><br><span class="line">155</span><br><span class="line">156</span><br><span class="line">157</span><br><span class="line">158</span><br><span class="line">159</span><br><span class="line">160</span><br><span class="line">161</span><br><span class="line">162</span><br><span class="line">163</span><br><span class="line">164</span><br><span class="line">165</span><br><span class="line">166</span><br><span class="line">167</span><br><span class="line">168</span><br><span class="line">169</span><br><span class="line">170</span><br><span class="line">171</span><br><span class="line">172</span><br><span class="line">173</span><br><span class="line">174</span><br><span class="line">175</span><br><span class="line">176</span><br><span class="line">177</span><br><span class="line">178</span><br><span class="line">179</span><br><span class="line">180</span><br><span class="line">181</span><br><span class="line">182</span><br><span class="line">183</span><br><span class="line">184</span><br><span class="line">185</span><br><span class="line">186</span><br><span class="line">187</span><br><span class="line">188</span><br><span class="line">189</span><br><span class="line">190</span><br><span class="line">191</span><br><span class="line">192</span><br><span class="line">193</span><br><span class="line">194</span><br><span class="line">195</span><br><span class="line">196</span><br><span class="line">197</span><br><span class="line">198</span><br><span class="line">199</span><br><span class="line">200</span><br><span class="line">201</span><br><span class="line">202</span><br><span class="line">203</span><br><span class="line">204</span><br><span class="line">205</span><br><span class="line">206</span><br><span class="line">207</span><br><span class="line">208</span><br><span class="line">209</span><br><span class="line">210</span><br><span class="line">211</span><br><span class="line">212</span><br><span class="line">213</span><br><span class="line">214</span><br><span class="line">215</span><br><span class="line">216</span><br><span class="line">217</span><br><span class="line">218</span><br><span class="line">219</span><br><span class="line">220</span><br><span class="line">221</span><br><span class="line">222</span><br><span class="line">223</span><br><span class="line">224</span><br><span class="line">225</span><br><span class="line">226</span><br><span class="line">227</span><br><span class="line">228</span><br><span class="line">229</span><br><span class="line">230</span><br><span class="line">231</span><br><span class="line">232</span><br><span class="line">233</span><br><span class="line">234</span><br><span class="line">235</span><br><span class="line">236</span><br><span class="line">237</span><br><span class="line">238</span><br><span class="line">239</span><br><span class="line">240</span><br><span class="line">241</span><br><span class="line">242</span><br><span class="line">243</span><br><span class="line">244</span><br><span class="line">245</span><br><span class="line">246</span><br><span class="line">247</span><br><span class="line">248</span><br><span class="line">249</span><br><span class="line">250</span><br><span class="line">251</span><br><span class="line">252</span><br><span class="line">253</span><br><span class="line">254</span><br><span class="line">255</span><br><span class="line">256</span><br><span class="line">257</span><br><span class="line">258</span><br><span class="line">259</span><br><span class="line">260</span><br><span class="line">261</span><br><span class="line">262</span><br><span class="line">263</span><br><span class="line">264</span><br><span class="line">265</span><br><span class="line">266</span><br><span class="line">267</span><br><span class="line">268</span><br><span class="line">269</span><br><span class="line">270</span><br><span class="line">271</span><br><span class="line">272</span><br><span class="line">273</span><br><span class="line">274</span><br><span class="line">275</span><br><span class="line">276</span><br><span class="line">277</span><br><span class="line">278</span><br><span class="line">279</span><br><span class="line">280</span><br><span class="line">281</span><br><span class="line">282</span><br><span class="line">283</span><br><span class="line">284</span><br><span class="line">285</span><br><span class="line">286</span><br><span class="line">287</span><br><span class="line">288</span><br><span class="line">289</span><br><span class="line">290</span><br><span class="line">291</span><br><span class="line">292</span><br><span class="line">293</span><br><span class="line">294</span><br><span class="line">295</span><br><span class="line">296</span><br><span class="line">297</span><br><span class="line">298</span><br><span class="line">299</span><br><span class="line">300</span><br><span class="line">301</span><br><span class="line">302</span><br><span class="line">303</span><br><span class="line">304</span><br><span class="line">305</span><br><span class="line">306</span><br><span class="line">307</span><br><span class="line">308</span><br><span class="line">309</span><br><span class="line">310</span><br><span class="line">311</span><br><span class="line">312</span><br><span class="line">313</span><br><span class="line">314</span><br><span class="line">315</span><br><span class="line">316</span><br><span class="line">317</span><br><span class="line">318</span><br><span class="line">319</span><br><span class="line">320</span><br><span class="line">321</span><br><span class="line">322</span><br><span class="line">323</span><br><span class="line">324</span><br><span class="line">325</span><br><span class="line">326</span><br><span class="line">327</span><br><span class="line">328</span><br><span class="line">329</span><br><span class="line">330</span><br><span class="line">331</span><br><span class="line">332</span><br><span class="line">333</span><br><span class="line">334</span><br><span class="line">335</span><br><span class="line">336</span><br><span class="line">337</span><br><span class="line">338</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * 安知鱼主题 AI 摘要 - 代理版（无前端缓存）</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line">(<span class="keyword">function</span>(<span class="params"></span>) &#123;</span><br><span class="line">  <span class="string">&#x27;use strict&#x27;</span>;</span><br><span class="line"></span><br><span class="line">  <span class="keyword">const</span> <span class="variable constant_">CONFIG</span> = &#123;</span><br><span class="line">    <span class="attr">apiUrl</span>: <span class="string">&#x27;https://ai.eucalyptus.cc/api/ai-summary&#x27;</span>,</span><br><span class="line">    <span class="attr">typeSpeed</span>: <span class="number">35</span>,</span><br><span class="line">    <span class="attr">maxSummaryLength</span>: <span class="number">1000</span>,</span><br><span class="line">    <span class="attr">retryCount</span>: <span class="number">1</span></span><br><span class="line">  &#125;;</span><br><span class="line"></span><br><span class="line">  <span class="keyword">let</span> isProcessing = <span class="literal">false</span>;</span><br><span class="line">  <span class="keyword">let</span> typeWriterTimer = <span class="literal">null</span>;</span><br><span class="line">  <span class="keyword">let</span> abortController = <span class="literal">null</span>;</span><br><span class="line"></span><br><span class="line">  <span class="keyword">function</span> <span class="title function_">waitForElement</span>(<span class="params">selector, timeout = <span class="number">3000</span></span>) &#123;</span><br><span class="line">    <span class="keyword">return</span> <span class="keyword">new</span> <span class="title class_">Promise</span>(<span class="function">(<span class="params">resolve, reject</span>) =&gt;</span> &#123;</span><br><span class="line">      <span class="keyword">const</span> element = <span class="variable language_">document</span>.<span class="title function_">querySelector</span>(selector);</span><br><span class="line">      <span class="keyword">if</span> (element) <span class="keyword">return</span> <span class="title function_">resolve</span>(element);</span><br><span class="line">      <span class="keyword">const</span> observer = <span class="keyword">new</span> <span class="title class_">MutationObserver</span>(<span class="function">() =&gt;</span> &#123;</span><br><span class="line">        <span class="keyword">const</span> el = <span class="variable language_">document</span>.<span class="title function_">querySelector</span>(selector);</span><br><span class="line">        <span class="keyword">if</span> (el) &#123; observer.<span class="title function_">disconnect</span>(); <span class="title function_">resolve</span>(el); &#125;</span><br><span class="line">      &#125;);</span><br><span class="line">      observer.<span class="title function_">observe</span>(<span class="variable language_">document</span>.<span class="property">body</span>, &#123; <span class="attr">childList</span>: <span class="literal">true</span>, <span class="attr">subtree</span>: <span class="literal">true</span> &#125;);</span><br><span class="line">      <span class="built_in">setTimeout</span>(<span class="function">() =&gt;</span> &#123; observer.<span class="title function_">disconnect</span>(); <span class="title function_">reject</span>(<span class="keyword">new</span> <span class="title class_">Error</span>(<span class="string">`Timeout waiting for <span class="subst">$&#123;selector&#125;</span>`</span>)); &#125;, timeout);</span><br><span class="line">    &#125;);</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  <span class="keyword">function</span> <span class="title function_">escapeHtml</span>(<span class="params">text</span>) &#123;</span><br><span class="line">    <span class="keyword">if</span> (!text) <span class="keyword">return</span> <span class="string">&#x27;&#x27;</span>;</span><br><span class="line">    <span class="keyword">const</span> div = <span class="variable language_">document</span>.<span class="title function_">createElement</span>(<span class="string">&#x27;div&#x27;</span>);</span><br><span class="line">    div.<span class="property">textContent</span> = text;</span><br><span class="line">    <span class="keyword">return</span> div.<span class="property">innerHTML</span>;</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  <span class="keyword">function</span> <span class="title function_">typeWriter</span>(<span class="params">element, text, speed = CONFIG.typeSpeed</span>) &#123;</span><br><span class="line">    <span class="keyword">return</span> <span class="keyword">new</span> <span class="title class_">Promise</span>(<span class="function">(<span class="params">resolve</span>) =&gt;</span> &#123;</span><br><span class="line">      <span class="keyword">if</span> (typeWriterTimer) &#123; <span class="built_in">clearInterval</span>(typeWriterTimer); typeWriterTimer = <span class="literal">null</span>; &#125;</span><br><span class="line">      <span class="keyword">let</span> index = <span class="number">0</span>;</span><br><span class="line">      element.<span class="property">textContent</span> = <span class="string">&#x27;&#x27;</span>;</span><br><span class="line">      typeWriterTimer = <span class="built_in">setInterval</span>(<span class="function">() =&gt;</span> &#123;</span><br><span class="line">        <span class="keyword">if</span> (index &lt; text.<span class="property">length</span>) &#123; </span><br><span class="line">          element.<span class="property">textContent</span> += text.<span class="title function_">charAt</span>(index); </span><br><span class="line">          index++; </span><br><span class="line">        &#125; <span class="keyword">else</span> &#123; </span><br><span class="line">          <span class="built_in">clearInterval</span>(typeWriterTimer); </span><br><span class="line">          typeWriterTimer = <span class="literal">null</span>; </span><br><span class="line">          <span class="title function_">resolve</span>(); </span><br><span class="line">        &#125;</span><br><span class="line">      &#125;, speed);</span><br><span class="line">    &#125;);</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  <span class="keyword">function</span> <span class="title function_">getArticleText</span>(<span class="params"></span>) &#123;</span><br><span class="line">    <span class="keyword">const</span> selectors = [</span><br><span class="line">      <span class="string">&#x27;#article-container .post-content&#x27;</span>,</span><br><span class="line">      <span class="string">&#x27;#article-container #post&#x27;</span>,</span><br><span class="line">      <span class="string">&#x27;.post-content&#x27;</span>,</span><br><span class="line">      <span class="string">&#x27;article .entry-content&#x27;</span>,</span><br><span class="line">      <span class="string">&#x27;#post-content&#x27;</span>,</span><br><span class="line">      <span class="string">&#x27;.article-content&#x27;</span>,</span><br><span class="line">      <span class="string">&#x27;.markdown-body&#x27;</span>,</span><br><span class="line">      <span class="string">&#x27;#content article&#x27;</span></span><br><span class="line">    ];</span><br><span class="line"></span><br><span class="line">    <span class="keyword">let</span> article = <span class="literal">null</span>;</span><br><span class="line">    <span class="keyword">for</span> (<span class="keyword">const</span> sel <span class="keyword">of</span> selectors) &#123;</span><br><span class="line">      article = <span class="variable language_">document</span>.<span class="title function_">querySelector</span>(sel);</span><br><span class="line">      <span class="keyword">if</span> (article &amp;&amp; article.<span class="property">innerText</span>.<span class="title function_">trim</span>().<span class="property">length</span> &gt; <span class="number">200</span>) <span class="keyword">break</span>;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">if</span> (!article) &#123; </span><br><span class="line">      <span class="variable language_">console</span>.<span class="title function_">warn</span>(<span class="string">&#x27;[AI Summary] Article content not found.&#x27;</span>); </span><br><span class="line">      <span class="keyword">return</span> <span class="string">&#x27;&#x27;</span>; </span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">const</span> clone = article.<span class="title function_">cloneNode</span>(<span class="literal">true</span>);</span><br><span class="line"></span><br><span class="line">    <span class="keyword">const</span> removeSelectors = [</span><br><span class="line">      <span class="string">&#x27;h1&#x27;</span>, <span class="string">&#x27;h2.post-title&#x27;</span>, <span class="string">&#x27;.post-title&#x27;</span>, <span class="string">&#x27;.page-title&#x27;</span>,</span><br><span class="line">      <span class="string">&#x27;.post-meta&#x27;</span>, <span class="string">&#x27;.meta&#x27;</span>, <span class="string">&#x27;.entry-meta&#x27;</span>, <span class="string">&#x27;.article-meta&#x27;</span>,</span><br><span class="line">      <span class="string">&#x27;.post-date&#x27;</span>, <span class="string">&#x27;.post-time&#x27;</span>, <span class="string">&#x27;.published&#x27;</span>, <span class="string">&#x27;.updated&#x27;</span>,</span><br><span class="line">      <span class="string">&#x27;.author&#x27;</span>, <span class="string">&#x27;.byline&#x27;</span>, <span class="string">&#x27;.post-author&#x27;</span>,</span><br><span class="line">      <span class="string">&#x27;.post-tags&#x27;</span>, <span class="string">&#x27;.tags&#x27;</span>, <span class="string">&#x27;.tag-cloud&#x27;</span>, <span class="string">&#x27;.post-categories&#x27;</span>,</span><br><span class="line">      <span class="string">&#x27;.categories&#x27;</span>, <span class="string">&#x27;.post-category&#x27;</span>, <span class="string">&#x27;.article-tags&#x27;</span>,</span><br><span class="line">      <span class="string">&#x27;nav&#x27;</span>, <span class="string">&#x27;.nav&#x27;</span>, <span class="string">&#x27;.breadcrumb&#x27;</span>, <span class="string">&#x27;.crumbs&#x27;</span>,</span><br><span class="line">            <span class="string">&#x27;aside&#x27;</span>, <span class="string">&#x27;.sidebar&#x27;</span>, <span class="string">&#x27;.widget&#x27;</span>, <span class="string">&#x27;.toc&#x27;</span>, <span class="string">&#x27;#toc&#x27;</span>,</span><br><span class="line">            <span class="string">&#x27;header&#x27;</span>, <span class="string">&#x27;.post-header&#x27;</span>, <span class="string">&#x27;.entry-header&#x27;</span>, <span class="string">&#x27;.article-header&#x27;</span>,</span><br><span class="line">            <span class="string">&#x27;footer&#x27;</span>, <span class="string">&#x27;.post-footer&#x27;</span>, <span class="string">&#x27;.entry-footer&#x27;</span>, <span class="string">&#x27;.article-footer&#x27;</span>,</span><br><span class="line">            <span class="string">&#x27;#post-comment&#x27;</span>, <span class="string">&#x27;.comments&#x27;</span>, <span class="string">&#x27;.comment-area&#x27;</span>,</span><br><span class="line">            <span class="string">&#x27;.share&#x27;</span>, <span class="string">&#x27;.social-share&#x27;</span>, <span class="string">&#x27;.reward&#x27;</span>, <span class="string">&#x27;.donate&#x27;</span>,</span><br><span class="line">            <span class="string">&#x27;.post-copyright&#x27;</span>, <span class="string">&#x27;.copyright&#x27;</span>, <span class="string">&#x27;.license&#x27;</span>, <span class="string">&#x27;.declaration&#x27;</span>,</span><br><span class="line">            <span class="string">&#x27;.relatedPosts&#x27;</span>, <span class="string">&#x27;.related-posts&#x27;</span>, <span class="string">&#x27;.related&#x27;</span>, <span class="string">&#x27;.recommend&#x27;</span>,</span><br><span class="line">            <span class="string">&#x27;.anzhiyu-ai-summary&#x27;</span>, <span class="string">&#x27;#ai-summary&#x27;</span>,</span><br><span class="line">            <span class="string">&#x27;script&#x27;</span>, <span class="string">&#x27;style&#x27;</span>, <span class="string">&#x27;pre&#x27;</span>, <span class="string">&#x27;code&#x27;</span>, <span class="string">&#x27;table&#x27;</span>, </span><br><span class="line">            <span class="string">&#x27;img&#x27;</span>, <span class="string">&#x27;figure&#x27;</span>, <span class="string">&#x27;figcaption&#x27;</span>, <span class="string">&#x27;svg&#x27;</span>,</span><br><span class="line">            <span class="string">&#x27;blockquote&#x27;</span>,</span><br><span class="line">            <span class="string">&#x27;.ads-wrap&#x27;</span>, <span class="string">&#x27;.ad&#x27;</span>, <span class="string">&#x27;.advertisement&#x27;</span>,</span><br><span class="line">            <span class="string">&#x27;.highlight&#x27;</span>, <span class="string">&#x27;.mermaid&#x27;</span>, <span class="string">&#x27;.katex&#x27;</span>, <span class="string">&#x27;.mathjax&#x27;</span>,</span><br><span class="line">            <span class="string">&#x27;.pagination&#x27;</span>, <span class="string">&#x27;.pager&#x27;</span>, <span class="string">&#x27;.page-nav&#x27;</span>,</span><br><span class="line">            <span class="string">&#x27;.notice&#x27;</span>, <span class="string">&#x27;.alert&#x27;</span>, <span class="string">&#x27;.tips&#x27;</span>, <span class="string">&#x27;.warning&#x27;</span></span><br><span class="line">        ];</span><br><span class="line">        </span><br><span class="line">        removeSelectors.<span class="title function_">forEach</span>(<span class="function"><span class="params">sel</span> =&gt;</span> &#123;</span><br><span class="line">            clone.<span class="title function_">querySelectorAll</span>(sel).<span class="title function_">forEach</span>(<span class="function"><span class="params">el</span> =&gt;</span> el.<span class="title function_">remove</span>());</span><br><span class="line">        &#125;);</span><br><span class="line">        </span><br><span class="line">        <span class="keyword">let</span> text = clone.<span class="property">innerText</span></span><br><span class="line">            .<span class="title function_">replace</span>(<span class="regexp">/\s+/g</span>, <span class="string">&#x27; &#x27;</span>)</span><br><span class="line">            .<span class="title function_">replace</span>(<span class="regexp">/[*#\-_`~\[\]()&gt;|]/g</span>, <span class="string">&#x27; &#x27;</span>)</span><br><span class="line">            .<span class="title function_">replace</span>(<span class="regexp">/\b[A-Z]&#123;5,&#125;\b/g</span>, <span class="string">&#x27; &#x27;</span>)</span><br><span class="line">            .<span class="title function_">replace</span>(<span class="regexp">/\d&#123;4&#125;[年/-]\d&#123;1,2&#125;[月/-]\d&#123;1,2&#125;[日]?/g</span>, <span class="string">&#x27; &#x27;</span>)</span><br><span class="line">            .<span class="title function_">replace</span>(<span class="regexp">/[^\u4e00-\u9fa5a-zA-Z0-9，。！？；：&quot;&quot;&#x27;&#x27;（）【】《》.,;:!?&#x27;&quot;()\[\]&lt;&gt;、\-—\n]/g</span>, <span class="string">&#x27; &#x27;</span>)</span><br><span class="line">            .<span class="title function_">replace</span>(<span class="regexp">/\s+/g</span>, <span class="string">&#x27; &#x27;</span>)</span><br><span class="line">            .<span class="title function_">trim</span>();</span><br><span class="line">        </span><br><span class="line">        <span class="keyword">if</span> (text.<span class="property">length</span> &lt; <span class="number">100</span>) &#123;</span><br><span class="line">            <span class="variable language_">console</span>.<span class="title function_">warn</span>(<span class="string">&#x27;[AI Summary] Content too short, trying fallback...&#x27;</span>);</span><br><span class="line">            <span class="keyword">const</span> paragraphs = <span class="title class_">Array</span>.<span class="title function_">from</span>(<span class="variable language_">document</span>.<span class="title function_">querySelectorAll</span>(<span class="string">&#x27;p&#x27;</span>));</span><br><span class="line">            <span class="keyword">const</span> longestP = paragraphs</span><br><span class="line">                .<span class="title function_">filter</span>(<span class="function"><span class="params">p</span> =&gt;</span> &#123;</span><br><span class="line">                    <span class="keyword">const</span> t = p.<span class="property">innerText</span>.<span class="title function_">trim</span>();</span><br><span class="line">                    <span class="keyword">return</span> t.<span class="property">length</span> &gt; <span class="number">50</span> &amp;&amp; !t.<span class="title function_">includes</span>(<span class="string">&#x27;标签&#x27;</span>) &amp;&amp; !t.<span class="title function_">includes</span>(<span class="string">&#x27;分类&#x27;</span>) &amp;&amp; !t.<span class="title function_">includes</span>(<span class="string">&#x27;作者&#x27;</span>);</span><br><span class="line">                &#125;)</span><br><span class="line">                .<span class="title function_">sort</span>(<span class="function">(<span class="params">a, b</span>) =&gt;</span> b.<span class="property">innerText</span>.<span class="property">length</span> - a.<span class="property">innerText</span>.<span class="property">length</span>)</span><br><span class="line">                .<span class="title function_">slice</span>(<span class="number">0</span>, <span class="number">10</span>);</span><br><span class="line">            </span><br><span class="line">            <span class="keyword">if</span> (longestP.<span class="property">length</span> &gt; <span class="number">0</span>) &#123;</span><br><span class="line">                text = longestP.<span class="title function_">map</span>(<span class="function"><span class="params">p</span> =&gt;</span> p.<span class="property">innerText</span>).<span class="title function_">join</span>(<span class="string">&#x27; &#x27;</span>);</span><br><span class="line">                text = text.<span class="title function_">replace</span>(<span class="regexp">/\s+/g</span>, <span class="string">&#x27; &#x27;</span>).<span class="title function_">trim</span>();</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">        </span><br><span class="line">        <span class="keyword">return</span> text;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">async</span> <span class="keyword">function</span> <span class="title function_">callSummaryAPI</span>(<span class="params">text, retry = <span class="number">0</span></span>) &#123;</span><br><span class="line">        <span class="keyword">if</span> (abortController) abortController.<span class="title function_">abort</span>();</span><br><span class="line">        abortController = <span class="keyword">new</span> <span class="title class_">AbortController</span>();</span><br><span class="line">        <span class="keyword">const</span> timeoutId = <span class="built_in">setTimeout</span>(<span class="function">() =&gt;</span> abortController.<span class="title function_">abort</span>(), <span class="number">120000</span>);</span><br><span class="line">        </span><br><span class="line">        <span class="keyword">try</span> &#123;</span><br><span class="line">            <span class="keyword">const</span> response = <span class="keyword">await</span> <span class="title function_">fetch</span>(<span class="variable constant_">CONFIG</span>.<span class="property">apiUrl</span>, &#123;</span><br><span class="line">                <span class="attr">method</span>: <span class="string">&#x27;POST&#x27;</span>,</span><br><span class="line">                <span class="attr">headers</span>: &#123; </span><br><span class="line">                    <span class="string">&#x27;Content-Type&#x27;</span>: <span class="string">&#x27;application/json&#x27;</span></span><br><span class="line">                &#125;,</span><br><span class="line">                <span class="attr">body</span>: <span class="title class_">JSON</span>.<span class="title function_">stringify</span>(&#123;</span><br><span class="line">                    <span class="attr">text</span>: text.<span class="title function_">slice</span>(<span class="number">0</span>, <span class="number">4000</span>)</span><br><span class="line">                &#125;),</span><br><span class="line">                <span class="attr">signal</span>: abortController.<span class="property">signal</span></span><br><span class="line">            &#125;);</span><br><span class="line">            </span><br><span class="line">            <span class="built_in">clearTimeout</span>(timeoutId);</span><br><span class="line">            </span><br><span class="line">            <span class="keyword">if</span> (!response.<span class="property">ok</span>) &#123;</span><br><span class="line">                <span class="keyword">const</span> errorText = <span class="keyword">await</span> response.<span class="title function_">text</span>();</span><br><span class="line">                <span class="keyword">throw</span> <span class="keyword">new</span> <span class="title class_">Error</span>(<span class="string">`HTTP <span class="subst">$&#123;response.status&#125;</span>: <span class="subst">$&#123;errorText&#125;</span>`</span>);</span><br><span class="line">            &#125;</span><br><span class="line">            </span><br><span class="line">            <span class="keyword">const</span> data = <span class="keyword">await</span> response.<span class="title function_">json</span>();</span><br><span class="line">            <span class="keyword">let</span> summary = data.<span class="property">summary</span> || <span class="string">&#x27;&#x27;</span>;</span><br><span class="line">            </span><br><span class="line">            <span class="keyword">if</span> (!summary) <span class="keyword">throw</span> <span class="keyword">new</span> <span class="title class_">Error</span>(<span class="string">&#x27;Empty response&#x27;</span>);</span><br><span class="line">            </span><br><span class="line">            summary = summary</span><br><span class="line">                .<span class="title function_">replace</span>(<span class="regexp">/^\s*(摘要|总结|概括)[：:]\s*/i</span>, <span class="string">&#x27;&#x27;</span>)</span><br><span class="line">                .<span class="title function_">replace</span>(<span class="regexp">/^\s*[&quot;&#x27;]?|[&quot;&#x27;]?\s*$/g</span>, <span class="string">&#x27;&#x27;</span>)</span><br><span class="line">                .<span class="title function_">replace</span>(<span class="regexp">/\*\*/g</span>, <span class="string">&#x27;&#x27;</span>)</span><br><span class="line">                .<span class="title function_">replace</span>(<span class="regexp">/^\s*[-*]\s+/gm</span>, <span class="string">&#x27;&#x27;</span>)</span><br><span class="line">                .<span class="title function_">replace</span>(<span class="regexp">/^#&#123;1,6&#125;\s+/gm</span>, <span class="string">&#x27;&#x27;</span>)</span><br><span class="line">                .<span class="title function_">replace</span>(<span class="regexp">/这篇文章主要(介绍|讲述|讨论|分析)了/g</span>, <span class="string">&#x27;本文介绍了&#x27;</span>)</span><br><span class="line">                .<span class="title function_">replace</span>(<span class="regexp">/本文主要(介绍|讲述|讨论|分析)了/g</span>, <span class="string">&#x27;本文介绍了&#x27;</span>)</span><br><span class="line">                .<span class="title function_">replace</span>(<span class="regexp">/该文章(介绍|讲述|讨论|分析)了/g</span>, <span class="string">&#x27;本文介绍了&#x27;</span>)</span><br><span class="line">                .<span class="title function_">replace</span>(<span class="regexp">/此文章(介绍|讲述|讨论|分析)了/g</span>, <span class="string">&#x27;本文介绍了&#x27;</span>)</span><br><span class="line">                .<span class="title function_">replace</span>(<span class="regexp">/综上所述[，。]/g</span>, <span class="string">&#x27;&#x27;</span>)</span><br><span class="line">                .<span class="title function_">replace</span>(<span class="regexp">/总之[，。]/g</span>, <span class="string">&#x27;&#x27;</span>)</span><br><span class="line">                .<span class="title function_">replace</span>(<span class="regexp">/总而言之[，。]/g</span>, <span class="string">&#x27;&#x27;</span>)</span><br><span class="line">                .<span class="title function_">trim</span>();</span><br><span class="line"></span><br><span class="line">            <span class="keyword">if</span> (!<span class="regexp">/^本文(主要)?介绍了/</span>.<span class="title function_">test</span>(summary)) &#123;</span><br><span class="line">                summary = <span class="string">&#x27;本文介绍了&#x27;</span> + summary.<span class="title function_">replace</span>(<span class="regexp">/^[，。！？\s]+/</span>, <span class="string">&#x27;&#x27;</span>);</span><br><span class="line">            &#125;</span><br><span class="line"></span><br><span class="line">            <span class="keyword">const</span> sentences = summary.<span class="title function_">split</span>(<span class="regexp">/[。！？.!?]/</span>).<span class="title function_">filter</span>(<span class="function"><span class="params">s</span> =&gt;</span> s.<span class="title function_">trim</span>());</span><br><span class="line">            <span class="keyword">if</span> (sentences.<span class="property">length</span> &gt; <span class="number">3</span>) &#123;</span><br><span class="line">                summary = sentences.<span class="title function_">slice</span>(<span class="number">0</span>, <span class="number">3</span>).<span class="title function_">join</span>(<span class="string">&#x27;。&#x27;</span>) + <span class="string">&#x27;。&#x27;</span>;</span><br><span class="line">            &#125;</span><br><span class="line">            </span><br><span class="line">            <span class="keyword">if</span> (summary.<span class="property">length</span> &gt; <span class="variable constant_">CONFIG</span>.<span class="property">maxSummaryLength</span>) &#123;</span><br><span class="line">                summary = summary.<span class="title function_">slice</span>(<span class="number">0</span>, <span class="variable constant_">CONFIG</span>.<span class="property">maxSummaryLength</span>);</span><br><span class="line">                <span class="keyword">const</span> lastPunct = <span class="title class_">Math</span>.<span class="title function_">max</span>(</span><br><span class="line">                    summary.<span class="title function_">lastIndexOf</span>(<span class="string">&#x27;。&#x27;</span>),</span><br><span class="line">                    summary.<span class="title function_">lastIndexOf</span>(<span class="string">&#x27;！&#x27;</span>),</span><br><span class="line">                    summary.<span class="title function_">lastIndexOf</span>(<span class="string">&#x27;？&#x27;</span>)</span><br><span class="line">                );</span><br><span class="line">                <span class="keyword">if</span> (lastPunct &gt; <span class="variable constant_">CONFIG</span>.<span class="property">maxSummaryLength</span> * <span class="number">0.7</span>) &#123;</span><br><span class="line">                    summary = summary.<span class="title function_">slice</span>(<span class="number">0</span>, lastPunct + <span class="number">1</span>);</span><br><span class="line">                &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">                    summary = summary.<span class="title function_">replace</span>(<span class="regexp">/[^。！？]*$/</span>, <span class="string">&#x27;&#x27;</span>) + <span class="string">&#x27;。&#x27;</span>;</span><br><span class="line">                &#125;</span><br><span class="line">            &#125;</span><br><span class="line">            </span><br><span class="line">            <span class="keyword">if</span> (!<span class="regexp">/[。！？.!?]$/</span>.<span class="title function_">test</span>(summary)) &#123;</span><br><span class="line">                summary += <span class="string">&#x27;。&#x27;</span>;</span><br><span class="line">            &#125;</span><br><span class="line">            </span><br><span class="line">            <span class="keyword">return</span> summary;</span><br><span class="line">            </span><br><span class="line">        &#125; <span class="keyword">catch</span> (err) &#123;</span><br><span class="line">            <span class="built_in">clearTimeout</span>(timeoutId);</span><br><span class="line">            <span class="keyword">if</span> (retry &lt; <span class="variable constant_">CONFIG</span>.<span class="property">retryCount</span>) &#123;</span><br><span class="line">                <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">`[AI Summary] Retrying... (<span class="subst">$&#123;retry + <span class="number">1</span>&#125;</span>/<span class="subst">$&#123;CONFIG.retryCount&#125;</span>)`</span>);</span><br><span class="line">                <span class="keyword">await</span> <span class="keyword">new</span> <span class="title class_">Promise</span>(<span class="function"><span class="params">r</span> =&gt;</span> <span class="built_in">setTimeout</span>(r, <span class="number">2000</span>));</span><br><span class="line">                <span class="keyword">return</span> <span class="title function_">callSummaryAPI</span>(text, retry + <span class="number">1</span>);</span><br><span class="line">            &#125;</span><br><span class="line">            <span class="keyword">throw</span> err;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">function</span> <span class="title function_">renderContainer</span>(<span class="params">targetContainer, isLoading = <span class="literal">false</span>, content = <span class="string">&#x27;&#x27;</span></span>) &#123;</span><br><span class="line">        <span class="keyword">if</span> (!targetContainer) <span class="keyword">return</span> <span class="literal">null</span>;</span><br><span class="line">        </span><br><span class="line">        <span class="keyword">const</span> loadingHtml = <span class="string">`</span></span><br><span class="line"><span class="string">            &lt;span class=&quot;ai-loading-inline&quot;&gt;</span></span><br><span class="line"><span class="string">                &lt;svg class=&quot;ai-loading-svg&quot; width=&quot;16&quot; height=&quot;16&quot; viewBox=&quot;0 0 50 50&quot;&gt;</span></span><br><span class="line"><span class="string">                    &lt;circle cx=&quot;25&quot; cy=&quot;25&quot; r=&quot;20&quot; fill=&quot;none&quot; stroke=&quot;currentColor&quot; stroke-width=&quot;3&quot; </span></span><br><span class="line"><span class="string">                        stroke-linecap=&quot;round&quot; stroke-dasharray=&quot;80&quot; stroke-dashoffset=&quot;60&quot;&gt;</span></span><br><span class="line"><span class="string">                        &lt;animateTransform attributeName=&quot;transform&quot; type=&quot;rotate&quot; from=&quot;0 25 25&quot; to=&quot;360 25 25&quot; dur=&quot;1s&quot; repeatCount=&quot;indefinite&quot;/&gt;</span></span><br><span class="line"><span class="string">                    &lt;/circle&gt;</span></span><br><span class="line"><span class="string">                &lt;/svg&gt;</span></span><br><span class="line"><span class="string">                &lt;span class=&quot;ai-loading-dots&quot;&gt;AI 正在分析文章&lt;/span&gt;</span></span><br><span class="line"><span class="string">            &lt;/span&gt;</span></span><br><span class="line"><span class="string">        `</span>;</span><br><span class="line">        </span><br><span class="line">        targetContainer.<span class="property">innerHTML</span> = <span class="string">`</span></span><br><span class="line"><span class="string">            &lt;div class=&quot;ai-content&quot;&gt;</span></span><br><span class="line"><span class="string">                &lt;div class=&quot;ai-header&quot;&gt;</span></span><br><span class="line"><span class="string">                    &lt;i class=&quot;eucalyptus icon-bilibili&quot;&gt;&lt;/i&gt;&lt;span class=&quot;ai-titles&quot;&gt;AI 摘要&lt;/span&gt;&lt;span class=&quot;ai-badge&quot;&gt;AGENT&lt;/span&gt;</span></span><br><span class="line"><span class="string">                &lt;/div&gt;</span></span><br><span class="line"><span class="string">                &lt;div class=&quot;ai-text-box&quot;&gt;</span></span><br><span class="line"><span class="string">                    &lt;p id=&quot;ai-summary-text&quot;&gt;<span class="subst">$&#123;isLoading ? loadingHtml : escapeHtml(content)&#125;</span>&lt;/p&gt;</span></span><br><span class="line"><span class="string">                &lt;/div&gt;</span></span><br><span class="line"><span class="string">            &lt;/div&gt;</span></span><br><span class="line"><span class="string">        `</span>;</span><br><span class="line">        <span class="keyword">return</span> targetContainer.<span class="title function_">querySelector</span>(<span class="string">&#x27;#ai-summary-text&#x27;</span>);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">function</span> <span class="title function_">renderError</span>(<span class="params">targetContainer, message</span>) &#123;</span><br><span class="line">        <span class="keyword">if</span> (!targetContainer) <span class="keyword">return</span>;</span><br><span class="line">        targetContainer.<span class="property">innerHTML</span> = <span class="string">`</span></span><br><span class="line"><span class="string">            &lt;div class=&quot;ai-content&quot;&gt;</span></span><br><span class="line"><span class="string">                &lt;div class=&quot;ai-header&quot;&gt;</span></span><br><span class="line"><span class="string">                    &lt;i class=&quot;eucalyptus icon-bilibili&quot;&gt;&lt;/i&gt;&lt;span class=&quot;ai-titles&quot;&gt;AI 摘要&lt;/span&gt;&lt;span class=&quot;ai-badge&quot;&gt;AGENT&lt;/span&gt;</span></span><br><span class="line"><span class="string">                &lt;/div&gt;</span></span><br><span class="line"><span class="string">                &lt;div class=&quot;ai-text-box&quot;&gt;</span></span><br><span class="line"><span class="string">                    &lt;p style=&quot;color: #999;&quot;&gt;<span class="subst">$&#123;escapeHtml(message)&#125;</span>&lt;/p&gt;</span></span><br><span class="line"><span class="string">                &lt;/div&gt;</span></span><br><span class="line"><span class="string">            &lt;/div&gt;</span></span><br><span class="line"><span class="string">        `</span>;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">function</span> <span class="title function_">bootstrap</span>(<span class="params"></span>) &#123;</span><br><span class="line">        <span class="keyword">if</span> (abortController) &#123;</span><br><span class="line">            abortController.<span class="title function_">abort</span>();</span><br><span class="line">            abortController = <span class="literal">null</span>;</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">if</span> (typeWriterTimer) &#123; </span><br><span class="line">            <span class="built_in">clearInterval</span>(typeWriterTimer); </span><br><span class="line">            typeWriterTimer = <span class="literal">null</span>; </span><br><span class="line">        &#125;</span><br><span class="line">        isProcessing = <span class="literal">false</span>;</span><br><span class="line">        </span><br><span class="line">        <span class="keyword">if</span> (<span class="variable language_">document</span>.<span class="title function_">getElementById</span>(<span class="string">&#x27;ai-summary&#x27;</span>)) &#123;</span><br><span class="line">            <span class="built_in">setTimeout</span>(initAISummary, <span class="number">100</span>);</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">async</span> <span class="keyword">function</span> <span class="title function_">initAISummary</span>(<span class="params"></span>) &#123;</span><br><span class="line">        <span class="keyword">if</span> (isProcessing) <span class="keyword">return</span>;</span><br><span class="line">        isProcessing = <span class="literal">true</span>;</span><br><span class="line">        </span><br><span class="line">        <span class="keyword">try</span> &#123;</span><br><span class="line">            <span class="keyword">const</span> container = <span class="keyword">await</span> <span class="title function_">waitForElement</span>(<span class="string">&#x27;#ai-summary&#x27;</span>);</span><br><span class="line">            <span class="keyword">const</span> text = <span class="title function_">getArticleText</span>();</span><br><span class="line">            </span><br><span class="line">            <span class="keyword">if</span> (!text || text.<span class="property">length</span> &lt; <span class="number">50</span>) &#123;</span><br><span class="line">                <span class="title function_">renderContainer</span>(container, <span class="literal">false</span>, <span class="string">&#x27;本文篇幅较短，暂无详细摘要。&#x27;</span>);</span><br><span class="line">                isProcessing = <span class="literal">false</span>;</span><br><span class="line">                <span class="keyword">return</span>;</span><br><span class="line">            &#125;</span><br><span class="line">            </span><br><span class="line">            <span class="title function_">renderContainer</span>(container, <span class="literal">true</span>);</span><br><span class="line">            </span><br><span class="line">            <span class="keyword">let</span> summary;</span><br><span class="line">            <span class="keyword">try</span> &#123; </span><br><span class="line">                summary = <span class="keyword">await</span> <span class="title function_">callSummaryAPI</span>(text); </span><br><span class="line">            &#125;</span><br><span class="line">            <span class="keyword">catch</span> (err) &#123; </span><br><span class="line">                <span class="variable language_">console</span>.<span class="title function_">error</span>(<span class="string">&#x27;[AI Summary] API failed:&#x27;</span>, err); </span><br><span class="line">                <span class="title function_">renderError</span>(container, <span class="string">`AI 服务暂时不可用：<span class="subst">$&#123;err.message&#125;</span>`</span>); </span><br><span class="line">                isProcessing = <span class="literal">false</span>; </span><br><span class="line">                <span class="keyword">return</span>; </span><br><span class="line">            &#125;</span><br><span class="line">            </span><br><span class="line">            <span class="keyword">const</span> textEl = <span class="title function_">renderContainer</span>(container, <span class="literal">false</span>, <span class="string">&#x27;&#x27;</span>);</span><br><span class="line">            <span class="keyword">await</span> <span class="title function_">typeWriter</span>(textEl, summary);</span><br><span class="line">            </span><br><span class="line">        &#125; <span class="keyword">catch</span> (err) &#123;</span><br><span class="line">            <span class="variable language_">console</span>.<span class="title function_">error</span>(<span class="string">&#x27;[AI Summary] Init Error:&#x27;</span>, err);</span><br><span class="line">            <span class="keyword">const</span> container = <span class="variable language_">document</span>.<span class="title function_">getElementById</span>(<span class="string">&#x27;ai-summary&#x27;</span>);</span><br><span class="line">            <span class="keyword">if</span> (container) <span class="title function_">renderError</span>(container, <span class="string">&#x27;初始化失败&#x27;</span>);</span><br><span class="line">        &#125; <span class="keyword">finally</span> &#123; </span><br><span class="line">            isProcessing = <span class="literal">false</span>; </span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">if</span> (<span class="variable language_">document</span>.<span class="property">readyState</span> === <span class="string">&#x27;loading&#x27;</span>) &#123;</span><br><span class="line">        <span class="variable language_">document</span>.<span class="title function_">addEventListener</span>(<span class="string">&#x27;DOMContentLoaded&#x27;</span>, bootstrap);</span><br><span class="line">    &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">        <span class="title function_">bootstrap</span>();</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="variable language_">document</span>.<span class="title function_">addEventListener</span>(<span class="string">&#x27;pjax:complete&#x27;</span>, bootstrap);</span><br><span class="line">    <span class="variable language_">document</span>.<span class="title function_">addEventListener</span>(<span class="string">&#x27;turbolinks:load&#x27;</span>, bootstrap);</span><br><span class="line"></span><br><span class="line">    <span class="keyword">let</span> lastUrl = location.<span class="property">pathname</span>;</span><br><span class="line">    <span class="keyword">new</span> <span class="title class_">MutationObserver</span>(<span class="function">() =&gt;</span> &#123; </span><br><span class="line">        <span class="keyword">const</span> url = location.<span class="property">pathname</span>; </span><br><span class="line">        <span class="keyword">if</span> (url !== lastUrl) &#123; </span><br><span class="line">            lastUrl = url; </span><br><span class="line">            <span class="title function_">bootstrap</span>(); </span><br><span class="line">        &#125; </span><br><span class="line">    &#125;).<span class="title function_">observe</span>(<span class="variable language_">document</span>, &#123; <span class="attr">subtree</span>: <span class="literal">true</span>, <span class="attr">childList</span>: <span class="literal">true</span> &#125;);</span><br><span class="line"></span><br><span class="line">&#125;)();</span><br></pre></td></tr></table></figure><h3 id="重新构建Hexo"><a href="#重新构建Hexo" class="headerlink" title="重新构建Hexo"></a>重新构建Hexo</h3><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">hexo clean</span><br><span class="line">hexo generate</span><br></pre></td></tr></table></figure><h3 id="查看Redis缓存"><a href="#查看Redis缓存" class="headerlink" title="查看Redis缓存"></a>查看Redis缓存</h3><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">#查看所有 AI 摘要缓存</span></span><br><span class="line">docker <span class="built_in">exec</span> redis-ai redis-cli KEYS <span class="string">&quot;ai:summary:*&quot;</span></span><br><span class="line"></span><br><span class="line"><span class="comment">#查看缓存数量</span></span><br><span class="line">docker <span class="built_in">exec</span> redis-ai redis-cli DBSIZE</span><br><span class="line"></span><br><span class="line"><span class="comment">#查看某个缓存内容</span></span><br><span class="line">docker <span class="built_in">exec</span> redis-ai redis-cli GET <span class="string">&quot;ai:summary:具体的hash值&quot;</span></span><br><span class="line"></span><br><span class="line"><span class="comment">#查看缓存剩余过期时间（秒）</span></span><br><span class="line">docker <span class="built_in">exec</span> redis-ai redis-cli TTL <span class="string">&quot;ai:summary:具体的hash值&quot;</span></span><br><span class="line"></span><br><span class="line"><span class="comment">#查看缓存详细信息（长度、类型等）</span></span><br><span class="line">docker <span class="built_in">exec</span> redis-ai redis-cli DEBUG OBJECT <span class="string">&quot;ai:summary:具体的hash值&quot;</span></span><br></pre></td></tr></table></figure><h3 id="删除Redis缓存"><a href="#删除Redis缓存" class="headerlink" title="删除Redis缓存"></a>删除Redis缓存</h3><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">#删除单个缓存</span></span><br><span class="line">docker <span class="built_in">exec</span> redis-ai redis-cli DEL <span class="string">&quot;ai:summary:具体的hash值&quot;</span></span><br><span class="line"></span><br><span class="line"><span class="comment">#批量删除所有 AI 摘要缓存</span></span><br><span class="line">docker <span class="built_in">exec</span> redis-ai redis-cli EVAL <span class="string">&quot;return redis.call(&#x27;del&#x27;, unpack(redis.call(&#x27;keys&#x27;, &#x27;ai:summary:*&#x27;)))&quot;</span> 0</span><br><span class="line"></span><br><span class="line"><span class="comment">#删除所有缓存（慎用）</span></span><br><span class="line">docker <span class="built_in">exec</span> redis-ai redis-cli FLUSHDB</span><br></pre></td></tr></table></figure><h3 id="进入Redis-CLI交互式模式"><a href="#进入Redis-CLI交互式模式" class="headerlink" title="进入Redis CLI交互式模式"></a>进入Redis CLI交互式模式</h3><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line">docker <span class="built_in">exec</span> -it redis-ai redis-cli</span><br><span class="line"></span><br><span class="line"><span class="comment"># 然后可以执行各种命令</span></span><br><span class="line">127.0.0.1:6379&gt; KEYS ai:summary:*</span><br><span class="line">127.0.0.1:6379&gt; GET ai:summary:5bc22daf2d8a9026</span><br><span class="line">127.0.0.1:6379&gt; TTL ai:summary:5bc22daf2d8a9026</span><br><span class="line">127.0.0.1:6379&gt; DEL ai:summary:5bc22daf2d8a9026</span><br><span class="line">127.0.0.1:6379&gt; <span class="built_in">exit</span></span><br></pre></td></tr></table></figure><h2 id="效果对比"><a href="#效果对比" class="headerlink" title="效果对比"></a>效果对比</h2><table><thead><tr><th>指标</th><th>Ollama (3b)</th><th>新方案 (日日新)</th></tr></thead><tbody><tr><td>生成速度</td><td>5-10 秒</td><td>1-3 秒</td></tr><tr><td>摘要质量</td><td>一般</td><td>较好</td></tr><tr><td>资源占用</td><td>500MB-1GB 内存</td><td>20MB 内存</td></tr><tr><td>并发能力</td><td>单用户</td><td>多用户共享缓存</td></tr><tr><td>成本</td><td>免费</td><td>按 API 调用计费</td></tr></tbody></table><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>从 Ollama 到云端代理，解决了三个核心问题：</p><ol><li><strong>质量</strong>：云端大模型效果优于本地小模型</li><li><strong>速度</strong>：缓存命中时直接返回，无需等待生成</li><li><strong>成本</strong>：缓存减少重复调用，降低 API 费用</li></ol><p>对于 2C4G 的服务器，这套方案更轻量、更稳定。如果服务器资源充足，Ollama 仍是免费的不错选择。</p>]]></content:encoded>
      
      
      <category domain="https://blog.eucalyptus.cc/categories/AI/">AI</category>
      
      
      <category domain="https://blog.eucalyptus.cc/tags/Ollama/">Ollama</category>
      
      
      <comments>https://blog.eucalyptus.cc/2026/06/01/%E4%BB%8E-Ollama-%E5%88%B0%E4%BA%91%E7%AB%AF%EF%BC%9AHexo-AI-%E6%91%98%E8%A6%81%E7%9A%84%E5%A4%9A%E5%B9%B3%E5%8F%B0%E8%BF%9B%E5%8C%96%E4%B9%8B%E8%B7%AF/#disqus_thread</comments>
      
    </item>
    
    <item>
      <title>Ollama访问限制</title>
      <link>https://blog.eucalyptus.cc/2026/05/21/Ollama%E8%AE%BF%E9%97%AE%E9%99%90%E5%88%B6/</link>
      <guid>https://blog.eucalyptus.cc/2026/05/21/Ollama%E8%AE%BF%E9%97%AE%E9%99%90%E5%88%B6/</guid>
      <pubDate>Thu, 21 May 2026 15:43:41 GMT</pubDate>
      
        
        
          
          
      <description>&lt;h2 id=&quot;Nginx-Basic-Auth认证&quot;&gt;&lt;a href=&quot;#Nginx-Basic-Auth认证&quot; class=&quot;headerlink&quot; title=&quot;Nginx+Basic Auth认证&quot;&gt;&lt;/a&gt;Nginx+Basic Auth认证&lt;/h2&gt;&lt;h3</description>
          
        
      
      
      
      <content:encoded><![CDATA[<h2 id="Nginx-Basic-Auth认证"><a href="#Nginx-Basic-Auth认证" class="headerlink" title="Nginx+Basic Auth认证"></a>Nginx+Basic Auth认证</h2><h3 id="生成密码文件"><a href="#生成密码文件" class="headerlink" title="生成密码文件"></a>生成密码文件</h3><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># 安装工具</span></span><br><span class="line">yum install httpd-tools -y</span><br><span class="line"></span><br><span class="line"><span class="comment"># 创建密码文件（用户名 admin）</span></span><br><span class="line">htpasswd -c /www/server/pass/ollama.pass admin</span><br><span class="line"></span><br><span class="line"><span class="comment"># 输入密码，比如 MySecurePass123</span></span><br></pre></td></tr></table></figure><h3 id="修改nginx配置文件"><a href="#修改nginx配置文件" class="headerlink" title="修改nginx配置文件"></a>修改nginx配置文件</h3><p>在之前配置ollama的反向代理的nginx配置文件中设置Basic Auth认证实现访问限制</p><figure class="highlight nginx"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">#BASICAUTH START</span></span><br><span class="line"><span class="attribute">auth_basic</span> <span class="string">&quot;Ollama API Auth&quot;</span>;</span><br><span class="line"><span class="attribute">auth_basic_user_file</span> /www/server/pass/ollama.pass;</span><br><span class="line"><span class="comment">#BASICAUTH END</span></span><br></pre></td></tr></table></figure><h3 id="重启nginx"><a href="#重启nginx" class="headerlink" title="重启nginx"></a>重启nginx</h3><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">nginx -t</span><br><span class="line">systemctl reload nginx</span><br></pre></td></tr></table></figure><h3 id="测试"><a href="#测试" class="headerlink" title="测试"></a>测试</h3><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># 不带认证 → 401 Unauthorized</span></span><br><span class="line">curl https://ollama.eucalyptus.cc/api/tags</span><br><span class="line"></span><br><span class="line"><span class="comment"># 带认证 → 成功</span></span><br><span class="line">curl https://admin:MySecurePass123@ollama.eucalyptus.cc/api/tags</span><br></pre></td></tr></table></figure><h2 id="后端Node-js代理"><a href="#后端Node-js代理" class="headerlink" title="后端Node.js代理"></a>后端Node.js代理</h2><p>但是这样的话再前端调用的时候需要把账号和密码写到前端js中，还是有泄露的风险，这时我们可以使用后端代理。前端 JS 直接调用后端代理，后端代理内部带 Basic Auth 访问 Ollama。</p><p>架构图</p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line">用户浏览器 → Hexo 博客页面 → JS 调用 https://ollama.eucalyptus.cc</span><br><span class="line">                                              ↓</span><br><span class="line">                                    ECS Nginx (无认证，或简单限流)</span><br><span class="line">                                              ↓</span><br><span class="line">                                    Node.js 代理 (localhost:3001)</span><br><span class="line">                                              ↓</span><br><span class="line">                                    带 Basic Auth 调 Ollama (127.0.0.1:11434)</span><br><span class="line">                                              ↓</span><br><span class="line">                                    frps → frp隧道 → 本地虚拟机 Ollama</span><br></pre></td></tr></table></figure><h3 id="部署步骤"><a href="#部署步骤" class="headerlink" title="部署步骤"></a>部署步骤</h3><h4 id="ecs安装node-js"><a href="#ecs安装node-js" class="headerlink" title="ecs安装node.js"></a>ecs安装node.js</h4><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># CentOS 7</span></span><br><span class="line">curl -fsSL https://rpm.nodesource.com/setup_18.x | bash -</span><br><span class="line">yum install -y nodejs</span><br><span class="line"></span><br><span class="line"><span class="comment"># 验证</span></span><br><span class="line">node -v</span><br><span class="line">npm -v</span><br></pre></td></tr></table></figure><h4 id="创建代理服务"><a href="#创建代理服务" class="headerlink" title="创建代理服务"></a>创建代理服务</h4><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">mkdir</span> -p /opt/ollama-proxy</span><br><span class="line"><span class="built_in">cd</span> /opt/ollama-proxy</span><br><span class="line">npm init -y</span><br><span class="line">npm install express node-fetch@2</span><br></pre></td></tr></table></figure><h4 id="创建proxy-js"><a href="#创建proxy-js" class="headerlink" title="创建proxy.js"></a>创建proxy.js</h4><p>记得配置CORS，不然会出现跨域访问的问题</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> express = <span class="built_in">require</span>(<span class="string">&#x27;express&#x27;</span>);</span><br><span class="line"><span class="keyword">const</span> fetch = <span class="built_in">require</span>(<span class="string">&#x27;node-fetch&#x27;</span>);</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> app = <span class="title function_">express</span>();</span><br><span class="line"></span><br><span class="line"><span class="comment">// ========== CORS 中间件 ==========</span></span><br><span class="line">app.<span class="title function_">use</span>(<span class="function">(<span class="params">req, res, next</span>) =&gt;</span> &#123;</span><br><span class="line">    res.<span class="title function_">header</span>(<span class="string">&#x27;Access-Control-Allow-Origin&#x27;</span>, <span class="string">&#x27;https://blog.eucalyptus.cc&#x27;</span>);</span><br><span class="line">    res.<span class="title function_">header</span>(<span class="string">&#x27;Access-Control-Allow-Methods&#x27;</span>, <span class="string">&#x27;GET, POST, OPTIONS&#x27;</span>);</span><br><span class="line">    res.<span class="title function_">header</span>(<span class="string">&#x27;Access-Control-Allow-Headers&#x27;</span>, <span class="string">&#x27;Origin, X-Requested-With, Content-Type, Accept, Authorization&#x27;</span>);</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">if</span> (req.<span class="property">method</span> === <span class="string">&#x27;OPTIONS&#x27;</span>) &#123;</span><br><span class="line">        <span class="keyword">return</span> res.<span class="title function_">sendStatus</span>(<span class="number">204</span>);</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="title function_">next</span>();</span><br><span class="line">&#125;);</span><br><span class="line"><span class="comment">// =================================</span></span><br><span class="line"></span><br><span class="line">app.<span class="title function_">use</span>(express.<span class="title function_">json</span>(&#123; <span class="attr">limit</span>: <span class="string">&#x27;10mb&#x27;</span> &#125;));</span><br><span class="line"></span><br><span class="line"><span class="comment">// Ollama 配置</span></span><br><span class="line"><span class="keyword">const</span> <span class="variable constant_">OLLAMA_HOST</span> = <span class="string">&#x27;http://127.0.0.1:11434&#x27;</span>;</span><br><span class="line"><span class="keyword">const</span> <span class="variable constant_">AUTH_USER</span> = <span class="string">&#x27;admin&#x27;</span>;</span><br><span class="line"><span class="keyword">const</span> <span class="variable constant_">AUTH_PASS</span> = <span class="string">&#x27;MySecurePass123&#x27;</span>;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 生成 Basic Auth 头</span></span><br><span class="line"><span class="keyword">const</span> basicAuth = <span class="string">&#x27;Basic &#x27;</span> + <span class="title class_">Buffer</span>.<span class="title function_">from</span>(<span class="string">`<span class="subst">$&#123;AUTH_USER&#125;</span>:<span class="subst">$&#123;AUTH_PASS&#125;</span>`</span>).<span class="title function_">toString</span>(<span class="string">&#x27;base64&#x27;</span>);</span><br><span class="line"></span><br><span class="line"><span class="comment">// 健康检查</span></span><br><span class="line">app.<span class="title function_">get</span>(<span class="string">&#x27;/health&#x27;</span>, <span class="function">(<span class="params">req, res</span>) =&gt;</span> &#123;</span><br><span class="line">    res.<span class="title function_">json</span>(&#123; <span class="attr">status</span>: <span class="string">&#x27;ok&#x27;</span> &#125;);</span><br><span class="line">&#125;);</span><br><span class="line"></span><br><span class="line"><span class="comment">// 代理 /api/generate</span></span><br><span class="line">app.<span class="title function_">post</span>(<span class="string">&#x27;/api/generate&#x27;</span>, <span class="title function_">async</span> (req, res) =&gt; &#123;</span><br><span class="line">    <span class="keyword">try</span> &#123;</span><br><span class="line">        <span class="keyword">const</span> response = <span class="keyword">await</span> <span class="title function_">fetch</span>(<span class="string">`<span class="subst">$&#123;OLLAMA_HOST&#125;</span>/api/generate`</span>, &#123;</span><br><span class="line">            <span class="attr">method</span>: <span class="string">&#x27;POST&#x27;</span>,</span><br><span class="line">            <span class="attr">headers</span>: &#123;</span><br><span class="line">                <span class="string">&#x27;Content-Type&#x27;</span>: <span class="string">&#x27;application/json&#x27;</span>,</span><br><span class="line">                <span class="string">&#x27;Authorization&#x27;</span>: basicAuth</span><br><span class="line">            &#125;,</span><br><span class="line">            <span class="attr">body</span>: <span class="title class_">JSON</span>.<span class="title function_">stringify</span>(req.<span class="property">body</span>),</span><br><span class="line">            <span class="attr">timeout</span>: <span class="number">60000</span></span><br><span class="line">        &#125;);</span><br><span class="line"></span><br><span class="line">        <span class="keyword">if</span> (!response.<span class="property">ok</span>) &#123;</span><br><span class="line">            <span class="keyword">const</span> text = <span class="keyword">await</span> response.<span class="title function_">text</span>();</span><br><span class="line">            <span class="keyword">return</span> res.<span class="title function_">status</span>(response.<span class="property">status</span>).<span class="title function_">json</span>(&#123; <span class="attr">error</span>: text &#125;);</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        <span class="keyword">const</span> data = <span class="keyword">await</span> response.<span class="title function_">json</span>();</span><br><span class="line">        res.<span class="title function_">json</span>(data);</span><br><span class="line"></span><br><span class="line">    &#125; <span class="keyword">catch</span> (err) &#123;</span><br><span class="line">        <span class="variable language_">console</span>.<span class="title function_">error</span>(<span class="string">&#x27;Proxy error:&#x27;</span>, err);</span><br><span class="line">        res.<span class="title function_">status</span>(<span class="number">500</span>).<span class="title function_">json</span>(&#123; <span class="attr">error</span>: err.<span class="property">message</span> &#125;);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;);</span><br><span class="line"></span><br><span class="line"><span class="comment">// 代理 /api/tags</span></span><br><span class="line">app.<span class="title function_">get</span>(<span class="string">&#x27;/api/tags&#x27;</span>, <span class="title function_">async</span> (req, res) =&gt; &#123;</span><br><span class="line">    <span class="keyword">try</span> &#123;</span><br><span class="line">        <span class="keyword">const</span> response = <span class="keyword">await</span> <span class="title function_">fetch</span>(<span class="string">`<span class="subst">$&#123;OLLAMA_HOST&#125;</span>/api/tags`</span>, &#123;</span><br><span class="line">            <span class="attr">headers</span>: &#123; <span class="string">&#x27;Authorization&#x27;</span>: basicAuth &#125;</span><br><span class="line">        &#125;);</span><br><span class="line">        <span class="keyword">const</span> data = <span class="keyword">await</span> response.<span class="title function_">json</span>();</span><br><span class="line">        res.<span class="title function_">json</span>(data);</span><br><span class="line">    &#125; <span class="keyword">catch</span> (err) &#123;</span><br><span class="line">        res.<span class="title function_">status</span>(<span class="number">500</span>).<span class="title function_">json</span>(&#123; <span class="attr">error</span>: err.<span class="property">message</span> &#125;);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;);</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> <span class="variable constant_">PORT</span> = <span class="number">3001</span>;</span><br><span class="line">app.<span class="title function_">listen</span>(<span class="variable constant_">PORT</span>, <span class="string">&#x27;127.0.0.1&#x27;</span>, <span class="function">() =&gt;</span> &#123;</span><br><span class="line">    <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">`Ollama proxy running on http://127.0.0.1:<span class="subst">$&#123;PORT&#125;</span>`</span>);</span><br><span class="line">&#125;);</span><br></pre></td></tr></table></figure><h4 id="用PM2守护进程"><a href="#用PM2守护进程" class="headerlink" title="用PM2守护进程"></a>用PM2守护进程</h4><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line">npm install -g pm2</span><br><span class="line"></span><br><span class="line"><span class="comment"># 启动</span></span><br><span class="line">pm2 start proxy.js --name ollama-proxy</span><br><span class="line"></span><br><span class="line"><span class="comment"># 开机自启</span></span><br><span class="line">pm2 startup</span><br><span class="line">pm2 save</span><br><span class="line"></span><br><span class="line"><span class="comment"># 查看状态</span></span><br><span class="line">pm2 status</span><br><span class="line">pm2 logs ollama-proxy</span><br></pre></td></tr></table></figure><h4 id="修改ollama反向代理nginx的配置文件"><a href="#修改ollama反向代理nginx的配置文件" class="headerlink" title="修改ollama反向代理nginx的配置文件"></a>修改ollama反向代理nginx的配置文件</h4><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">#PROXY-CONF-START</span></span><br><span class="line">location ^~ / &#123;</span><br><span class="line">  proxy_pass http://127.0.0.1:3001;   <span class="comment"># 改成代理端口</span></span><br><span class="line">  proxy_set_header Host <span class="variable">$http_host</span>;</span><br><span class="line">  proxy_set_header X-Real-IP <span class="variable">$remote_addr</span>;</span><br><span class="line">  proxy_set_header X-Real-Port <span class="variable">$remote_port</span>;</span><br><span class="line">  proxy_set_header X-Forwarded-For <span class="variable">$proxy_add_x_forwarded_for</span>;</span><br><span class="line">  proxy_set_header X-Forwarded-Proto <span class="variable">$scheme</span>;</span><br><span class="line">  proxy_set_header X-Forwarded-Host <span class="variable">$host</span>;</span><br><span class="line">  proxy_set_header X-Forwarded-Port <span class="variable">$server_port</span>;</span><br><span class="line">  proxy_set_header REMOTE-HOST <span class="variable">$remote_addr</span>;</span><br><span class="line">  </span><br><span class="line">  proxy_connect_timeout 60s;</span><br><span class="line">  proxy_send_timeout 600s;</span><br><span class="line">  proxy_read_timeout 600s;</span><br><span class="line">  proxy_http_version 1.1;</span><br><span class="line">  proxy_set_header Upgrade <span class="variable">$http_upgrade</span>;</span><br><span class="line">  proxy_set_header Connection <span class="variable">$connection_upgrade</span>;</span><br><span class="line">&#125;</span><br><span class="line"><span class="comment">#PROXY-CONF-END</span></span><br></pre></td></tr></table></figure><h4 id="重启nginx-1"><a href="#重启nginx-1" class="headerlink" title="重启nginx"></a>重启nginx</h4><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">nginx -t</span><br><span class="line">systemctl reload nginx</span><br></pre></td></tr></table></figure><h4 id="测试验证"><a href="#测试验证" class="headerlink" title="测试验证"></a>测试验证</h4><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># 测试代理健康</span></span><br><span class="line">curl http://127.0.0.1:3001/health</span><br><span class="line"></span><br><span class="line"><span class="comment"># 测试生成</span></span><br><span class="line">curl -X POST http://127.0.0.1:3001/api/generate \</span><br><span class="line">  -H <span class="string">&quot;Content-Type: application/json&quot;</span> \</span><br><span class="line">  -d <span class="string">&#x27;&#123;&quot;model&quot;:&quot;qwen2.5:0.5b&quot;,&quot;prompt&quot;:&quot;你好&quot;,&quot;stream&quot;:false&#125;&#x27;</span></span><br></pre></td></tr></table></figure>]]></content:encoded>
      
      
      <category domain="https://blog.eucalyptus.cc/categories/AI/">AI</category>
      
      
      <category domain="https://blog.eucalyptus.cc/tags/Ollama/">Ollama</category>
      
      
      <comments>https://blog.eucalyptus.cc/2026/05/21/Ollama%E8%AE%BF%E9%97%AE%E9%99%90%E5%88%B6/#disqus_thread</comments>
      
    </item>
    
    <item>
      <title>frp 配置反向代理实现 内网穿透</title>
      <link>https://blog.eucalyptus.cc/2026/05/20/frp-%E9%85%8D%E7%BD%AE%E5%8F%8D%E5%90%91%E4%BB%A3%E7%90%86%E5%AE%9E%E7%8E%B0-%E5%86%85%E7%BD%91%E7%A9%BF%E9%80%8F/</link>
      <guid>https://blog.eucalyptus.cc/2026/05/20/frp-%E9%85%8D%E7%BD%AE%E5%8F%8D%E5%90%91%E4%BB%A3%E7%90%86%E5%AE%9E%E7%8E%B0-%E5%86%85%E7%BD%91%E7%A9%BF%E9%80%8F/</guid>
      <pubDate>Wed, 20 May 2026 14:30:10 GMT</pubDate>
      
        
        
          
          
      <description>&lt;h2 id=&quot;什么是内网穿透&quot;&gt;&lt;a href=&quot;#什么是内网穿透&quot; class=&quot;headerlink&quot; title=&quot;什么是内网穿透&quot;&gt;&lt;/a&gt;什么是内网穿透&lt;/h2&gt;&lt;h3 id=&quot;核心原理&quot;&gt;&lt;a href=&quot;#核心原理&quot; class=&quot;headerlink&quot;</description>
          
        
      
      
      
      <content:encoded><![CDATA[<h2 id="什么是内网穿透"><a href="#什么是内网穿透" class="headerlink" title="什么是内网穿透"></a>什么是内网穿透</h2><h3 id="核心原理"><a href="#核心原理" class="headerlink" title="核心原理"></a>核心原理</h3><p>你的本地设备（如家里的电脑、公司的服务器）通常通过路由器&#x2F;NAT设备上网，只拥有（如192.168.x.x）。外网无法直接访问这些内网IP，因为：</p><ul><li>路由器有防火墙功能，会拦截外部主动发起的连接</li><li>内网IP在公网上不可路由</li></ul><p><strong>内网穿透就是打通这个”墙”，让外网流量能够到达内网设备。</strong></p><h3 id="常见实现方式"><a href="#常见实现方式" class="headerlink" title="常见实现方式"></a>常见实现方式</h3><table><thead><tr><th>方式</th><th>原理</th><th>例子</th></tr></thead><tbody><tr><td><strong>反向代理</strong></td><td>内网主动连接公网服务器，建立隧道，流量通过隧道转发</td><td>FRP、Ngrok、花生壳</td></tr><tr><td><strong>VPN&#x2F;虚拟组网</strong></td><td>组建虚拟局域网，让外网设备获得内网身份</td><td>WireGuard、ZeroTier、Tailscale</td></tr><tr><td><strong>DDNS+端口映射</strong></td><td>如果有公网IP，直接做路由器端口转发</td><td>路由器设置虚拟服务器</td></tr><tr><td><strong>云服务器中转</strong></td><td>数据先发到云服务器，再转发到内网</td><td>自建转发服务</td></tr></tbody></table><h2 id="配置方案"><a href="#配置方案" class="headerlink" title="配置方案"></a>配置方案</h2><p>本文章需要配置的是 实现内网穿透：</p><ul><li><strong>frps</strong>（服务端）：部署在有公网IP的ECS上（如阿里云）</li><li><strong>frpc</strong>（客户端）：部署在本地CentOS虚拟机中</li><li><strong>流程</strong>：本地frpc主动连接ECS上的frps → 建立加密隧道 → 外网用户访问ECS的端口 → frps通过隧道转发给frpc → 到达本地服务</li></ul><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">[外网用户] → [ECS公网IP:7500] → [frps] ═══隧道═══ [frpc] → [本地Ollama:11434]</span><br></pre></td></tr></table></figure><h3 id="下载并安装frps（ECS-本地Centos虚拟机）"><a href="#下载并安装frps（ECS-本地Centos虚拟机）" class="headerlink" title="下载并安装frps（ECS+本地Centos虚拟机）"></a>下载并安装frps（ECS+本地Centos虚拟机）</h3><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">#下载</span></span><br><span class="line">wget https://github.com/fatedier/frp/releases/download/v0.61.0/frp_0.61.0_linux_amd64.tar.gz</span><br><span class="line"><span class="comment">#解压</span></span><br><span class="line">tar -zxvf frp_0.61.0_linux_amd64.tar.gz</span><br><span class="line"><span class="built_in">cd</span> frp_0.61.0_linux_amd64</span><br></pre></td></tr></table></figure><h3 id="编写服务端配置文件frps-toml（ECS）"><a href="#编写服务端配置文件frps-toml（ECS）" class="headerlink" title="编写服务端配置文件frps.toml（ECS）"></a>编写服务端配置文件frps.toml（ECS）</h3><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">cat</span> &gt; frpc.toml &lt;&lt; <span class="string">&#x27;EOF&#x27;</span></span><br><span class="line"><span class="comment"># 服务端监听地址（0.0.0.0表示监听所有网卡）</span></span><br><span class="line">bindAddr = <span class="string">&quot;0.0.0.0&quot;</span></span><br><span class="line"><span class="comment"># 服务端监听端口（frpc会连接这个端口）</span></span><br><span class="line">bindPort = 7000</span><br><span class="line"></span><br><span class="line"><span class="comment"># 身份验证（必须设置，防止被他人滥用）</span></span><br><span class="line">auth.method = <span class="string">&quot;token&quot;</span></span><br><span class="line">auth.token = <span class="string">&quot;your_secure_token_here&quot;</span>  <span class="comment"># 改成复杂密码</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># Web 管理面板（可选，用于查看连接状态）</span></span><br><span class="line">webServer.addr = <span class="string">&quot;0.0.0.0&quot;</span></span><br><span class="line">webServer.port = 7500</span><br><span class="line">webServer.user = <span class="string">&quot;admin&quot;</span></span><br><span class="line">webServer.password = <span class="string">&quot;admin123&quot;</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># 允许代理的端口范围（根据你的服务调整）</span></span><br><span class="line">allowPorts = [</span><br><span class="line">  &#123; start = 6000, end = 7000 &#125;</span><br><span class="line">]</span><br><span class="line"></span><br><span class="line">EOF</span><br></pre></td></tr></table></figure><p>auth.token可以使用openssl生成</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># 生成32位随机字符串</span></span><br><span class="line">openssl rand -<span class="built_in">base64</span> 32</span><br><span class="line"></span><br><span class="line"><span class="comment"># 示例输出：</span></span><br><span class="line"><span class="comment"># W8vX2kLmN9pQr4StUv3WxYz5AbCdEfGhIjKlMnOpQr=</span></span><br></pre></td></tr></table></figure><h3 id="放开ecs上安全组和防火墙的相应端口"><a href="#放开ecs上安全组和防火墙的相应端口" class="headerlink" title="放开ecs上安全组和防火墙的相应端口"></a>放开ecs上安全组和防火墙的相应端口</h3><h4 id="安全组"><a href="#安全组" class="headerlink" title="安全组"></a>安全组</h4><table><thead><tr><th>端口</th><th>用途</th><th>授权对象</th></tr></thead><tbody><tr><td>7000</td><td>frp服务端通信端口</td><td>0.0.0.0&#x2F;0</td></tr><tr><td>7500</td><td>Web管理面板（可选）</td><td>0.0.0.0</td></tr><tr><td>6000-7000</td><td>代理端口范围</td><td>0.0.0.0&#x2F;0</td></tr></tbody></table><h4 id="防火墙"><a href="#防火墙" class="headerlink" title="防火墙"></a>防火墙</h4><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">sudo</span> firewall-cmd --permanent --add-port=7000/tcp</span><br><span class="line"><span class="built_in">sudo</span> firewall-cmd --permanent --add-port=7500/tcp</span><br><span class="line"><span class="built_in">sudo</span> firewall-cmd --permanent --add-port=6000-7000/tcp</span><br><span class="line"><span class="built_in">sudo</span> firewall-cmd --reload</span><br></pre></td></tr></table></figure><h3 id="启动fprs"><a href="#启动fprs" class="headerlink" title="启动fprs"></a>启动fprs</h3><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># 前台启动（测试用）</span></span><br><span class="line">./frps -c ./frps.toml</span><br><span class="line"></span><br><span class="line"><span class="comment"># 后台启动（生产用）</span></span><br><span class="line"><span class="built_in">nohup</span> ./frps -c ./frps.toml &gt; frps.log 2&gt;&amp;1 &amp;</span><br><span class="line"></span><br><span class="line"><span class="comment"># 查看日志</span></span><br><span class="line"><span class="built_in">tail</span> -f frps.log</span><br></pre></td></tr></table></figure><h3 id="编写客户端配置文件frpc-toml（本地Centos虚拟机）"><a href="#编写客户端配置文件frpc-toml（本地Centos虚拟机）" class="headerlink" title="编写客户端配置文件frpc.toml（本地Centos虚拟机）"></a>编写客户端配置文件frpc.toml（本地Centos虚拟机）</h3><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">cat</span> &gt; frps.toml &lt;&lt; <span class="string">&#x27;EOF&#x27;</span></span><br><span class="line"><span class="comment"># 服务端地址（ECS的公网IP）</span></span><br><span class="line">serverAddr = <span class="string">&quot;x.x.x.x&quot;</span>  <span class="comment"># 你的ECS公网IP</span></span><br><span class="line">serverPort = 7000</span><br><span class="line"></span><br><span class="line"><span class="comment"># 身份验证（必须与服务端一致）</span></span><br><span class="line">auth.method = <span class="string">&quot;token&quot;</span></span><br><span class="line">auth.token = <span class="string">&quot;your_secure_token_here&quot;</span>  <span class="comment"># 必须和服务端相同</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># 给这个客户端起个名字</span></span><br><span class="line">user = <span class="string">&quot;local-ollama&quot;</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># 代理配置：将本地Ollama暴露到公网</span></span><br><span class="line">[[proxies]]</span><br><span class="line">name = <span class="string">&quot;ollama&quot;</span></span><br><span class="line"><span class="built_in">type</span> = <span class="string">&quot;tcp&quot;</span></span><br><span class="line">localIP = <span class="string">&quot;127.0.0.1&quot;</span></span><br><span class="line">localPort = 11434          <span class="comment"># 本地Ollama端口</span></span><br><span class="line">remotePort = 6000          <span class="comment"># 公网访问端口（ECS的6000端口）</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># 可选：再暴露SSH</span></span><br><span class="line">[[proxies]]</span><br><span class="line">name = <span class="string">&quot;ssh&quot;</span></span><br><span class="line"><span class="built_in">type</span> = <span class="string">&quot;tcp&quot;</span></span><br><span class="line">localIP = <span class="string">&quot;127.0.0.1&quot;</span></span><br><span class="line">localPort = 22</span><br><span class="line">remotePort = 6001</span><br><span class="line"></span><br><span class="line">EOF</span><br></pre></td></tr></table></figure><h3 id="启动frpc"><a href="#启动frpc" class="headerlink" title="启动frpc"></a>启动frpc</h3><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># 前台启动（测试）</span></span><br><span class="line">./frpc -c ./frpc.toml</span><br><span class="line"></span><br><span class="line"><span class="comment"># 后台启动</span></span><br><span class="line"><span class="built_in">nohup</span> ./frpc -c ./frpc.toml &gt; frpc.log 2&gt;&amp;1 &amp;</span><br><span class="line"></span><br><span class="line"><span class="comment"># 查看日志</span></span><br><span class="line"><span class="built_in">tail</span> -f frpc.log</span><br></pre></td></tr></table></figure><h2 id="测试（ollama）"><a href="#测试（ollama）" class="headerlink" title="测试（ollama）"></a>测试（ollama）</h2><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># 直接访问本地Ollama</span></span><br><span class="line">curl http://127.0.0.1:11434/api/tags  </span><br><span class="line"></span><br><span class="line"><span class="comment"># 通过FRP访问</span></span><br><span class="line">curl http://ECS公网IP:6000/api/tags  </span><br></pre></td></tr></table></figure>]]></content:encoded>
      
      
      <category domain="https://blog.eucalyptus.cc/categories/%E5%86%85%E7%BD%91%E7%A9%BF%E9%80%8F/">内网穿透</category>
      
      
      <category domain="https://blog.eucalyptus.cc/tags/frp/">frp</category>
      
      
      <comments>https://blog.eucalyptus.cc/2026/05/20/frp-%E9%85%8D%E7%BD%AE%E5%8F%8D%E5%90%91%E4%BB%A3%E7%90%86%E5%AE%9E%E7%8E%B0-%E5%86%85%E7%BD%91%E7%A9%BF%E9%80%8F/#disqus_thread</comments>
      
    </item>
    
    <item>
      <title>使用 Ollama 为 Hexo 博客部署 AI 文章摘要</title>
      <link>https://blog.eucalyptus.cc/2026/05/16/%E4%BD%BF%E7%94%A8-Ollama-%E4%B8%BA-Hexo-%E5%8D%9A%E5%AE%A2%E9%83%A8%E7%BD%B2-AI-%E6%96%87%E7%AB%A0%E6%91%98%E8%A6%81/</link>
      <guid>https://blog.eucalyptus.cc/2026/05/16/%E4%BD%BF%E7%94%A8-Ollama-%E4%B8%BA-Hexo-%E5%8D%9A%E5%AE%A2%E9%83%A8%E7%BD%B2-AI-%E6%96%87%E7%AB%A0%E6%91%98%E8%A6%81/</guid>
      <pubDate>Sat, 16 May 2026 09:43:12 GMT</pubDate>
      
        
        
          
          
      <description>&lt;h1 id=&quot;使用-Ollama-为-Hexo-博客部署-AI-文章摘要&quot;&gt;&lt;a href=&quot;#使用-Ollama-为-Hexo-博客部署-AI-文章摘要&quot; class=&quot;headerlink&quot; title=&quot;使用 Ollama 为 Hexo 博客部署 AI</description>
          
        
      
      
      
      <content:encoded><![CDATA[<h1 id="使用-Ollama-为-Hexo-博客部署-AI-文章摘要"><a href="#使用-Ollama-为-Hexo-博客部署-AI-文章摘要" class="headerlink" title="使用 Ollama 为 Hexo 博客部署 AI 文章摘要"></a>使用 Ollama 为 Hexo 博客部署 AI 文章摘要</h1><h2 id="架构概述"><a href="#架构概述" class="headerlink" title="架构概述"></a>架构概述</h2><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line">┌─────────────────┐      ┌─────────────────┐      ┌─────────────────┐</span><br><span class="line">│   用户浏览器     │ ──→  │  Hexo 服务器    │ ──→  │  Ollama 服务器  │</span><br><span class="line">│                 │      │  (Nginx 代理)   │      │  (192.168.0.2)│</span><br><span class="line">│  blog.mingliang │      │  192.168.1.2 │      │   :11434        │</span><br><span class="line">│     star.com    │      │                 │      │                 │</span><br><span class="line">└─────────────────┘      └─────────────────┘      └─────────────────┘</span><br><span class="line">        ↑                                              ↑</span><br><span class="line">        └──────────── 仅允许 192.168.1.2 访问 ──────┘</span><br></pre></td></tr></table></figure><hr><h2 id="Ollama-服务端部署"><a href="#Ollama-服务端部署" class="headerlink" title="Ollama 服务端部署"></a>Ollama 服务端部署</h2><h3 id="1-拉起ollama的容器并设置环境变量防止CORS跨域访问"><a href="#1-拉起ollama的容器并设置环境变量防止CORS跨域访问" class="headerlink" title="1. 拉起ollama的容器并设置环境变量防止CORS跨域访问"></a>1. 拉起ollama的容器并设置环境变量防止CORS跨域访问</h3><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">docker run -d \</span><br><span class="line">  -v ollama:/root/.ollama \</span><br><span class="line">  -p 11434:11434 \</span><br><span class="line">  -e OLLAMA_ORIGINS=<span class="string">&quot;https://blog.eucalyptus.cc,https://eucalyptus.cc,http://localhost,https://localhost&quot;</span> \</span><br><span class="line">  -e OLLAMA_HOST=<span class="string">&quot;0.0.0.0:11434&quot;</span> \</span><br><span class="line">  --name ollama \</span><br><span class="line">  ollama/ollama</span><br></pre></td></tr></table></figure><h3 id="2-为ollama下载qwen2-5-0-5b模型"><a href="#2-为ollama下载qwen2-5-0-5b模型" class="headerlink" title="2. 为ollama下载qwen2.5:0.5b模型"></a>2. 为ollama下载qwen2.5:0.5b模型</h3><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">docker <span class="built_in">exec</span> ollama ollama pull qwen2.5:0.5b</span><br></pre></td></tr></table></figure><h3 id="3-安全加固：配置防火墙"><a href="#3-安全加固：配置防火墙" class="headerlink" title="3. 安全加固：配置防火墙"></a>3. 安全加固：配置防火墙</h3><p>在阿里云安全组中配置：</p><ul><li>入方向 <code>11434</code> 端口：授权对象 <code>192.168.1.2</code></li><li>删除所有 <code>0.0.0.0/0</code> 访问 11434 的规则</li></ul><hr><h2 id="Hexo-服务器配置反向代理"><a href="#Hexo-服务器配置反向代理" class="headerlink" title="Hexo 服务器配置反向代理"></a>Hexo 服务器配置反向代理</h2><p>在 Hexo 服务器（<code>192.168.1.2</code>）的 Nginx 中配置：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">代理目录：/</span><br><span class="line">目标：url地址 http://192.168.0.2:11434</span><br><span class="line">发送域名：<span class="variable">$http_host</span></span><br></pre></td></tr></table></figure><p><strong>为何需要反向代理？</strong></p><ul><li>浏览器直接请求 Ollama 存在 CORS 限制</li><li>通过同域名代理，前端无跨域问题</li><li>隐藏真实 Ollama IP，增强安全性</li></ul><hr><h2 id="Hexo-主题集成"><a href="#Hexo-主题集成" class="headerlink" title="Hexo 主题集成"></a>Hexo 主题集成</h2><h3 id="1-添加挂载点"><a href="#1-添加挂载点" class="headerlink" title="1. 添加挂载点"></a>1. 添加挂载点</h3><p>编辑 <code>/www/wwwroot/myblog/themes/anzhiyu/layout/post.pug</code>，插入：</p><figure class="highlight html"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line">...</span><br><span class="line"></span><br><span class="line">//- AI 摘要区域（由 ai-summary.js 动态渲染）</span><br><span class="line">#ai-summary.anzhiyu-ai-summary</span><br><span class="line"></span><br><span class="line">!=page.content</span><br><span class="line"></span><br><span class="line">...</span><br></pre></td></tr></table></figure><h3 id="2-前端核心代码"><a href="#2-前端核心代码" class="headerlink" title="2. 前端核心代码"></a>2. 前端核心代码</h3><p><code>ai-summary.js</code> —— 纯前端 Ollama 调用，支持打字机效果和本地缓存：</p><ul><li>自动提取文章正文，过滤代码块、图片等干扰内容</li><li>调用 Ollama <code>/api/generate</code> 接口生成摘要</li><li><strong>localStorage 缓存</strong>：同篇文章 7 天内不再重复请求</li><li><strong>打字机效果</strong>：逐字输出，增强交互体验</li><li>适配 PJAX&#x2F;Turbolinks 无刷新页面切换</li></ul><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br><span class="line">89</span><br><span class="line">90</span><br><span class="line">91</span><br><span class="line">92</span><br><span class="line">93</span><br><span class="line">94</span><br><span class="line">95</span><br><span class="line">96</span><br><span class="line">97</span><br><span class="line">98</span><br><span class="line">99</span><br><span class="line">100</span><br><span class="line">101</span><br><span class="line">102</span><br><span class="line">103</span><br><span class="line">104</span><br><span class="line">105</span><br><span class="line">106</span><br><span class="line">107</span><br><span class="line">108</span><br><span class="line">109</span><br><span class="line">110</span><br><span class="line">111</span><br><span class="line">112</span><br><span class="line">113</span><br><span class="line">114</span><br><span class="line">115</span><br><span class="line">116</span><br><span class="line">117</span><br><span class="line">118</span><br><span class="line">119</span><br><span class="line">120</span><br><span class="line">121</span><br><span class="line">122</span><br><span class="line">123</span><br><span class="line">124</span><br><span class="line">125</span><br><span class="line">126</span><br><span class="line">127</span><br><span class="line">128</span><br><span class="line">129</span><br><span class="line">130</span><br><span class="line">131</span><br><span class="line">132</span><br><span class="line">133</span><br><span class="line">134</span><br><span class="line">135</span><br><span class="line">136</span><br><span class="line">137</span><br><span class="line">138</span><br><span class="line">139</span><br><span class="line">140</span><br><span class="line">141</span><br><span class="line">142</span><br><span class="line">143</span><br><span class="line">144</span><br><span class="line">145</span><br><span class="line">146</span><br><span class="line">147</span><br><span class="line">148</span><br><span class="line">149</span><br><span class="line">150</span><br><span class="line">151</span><br><span class="line">152</span><br><span class="line">153</span><br><span class="line">154</span><br><span class="line">155</span><br><span class="line">156</span><br><span class="line">157</span><br><span class="line">158</span><br><span class="line">159</span><br><span class="line">160</span><br><span class="line">161</span><br><span class="line">162</span><br><span class="line">163</span><br><span class="line">164</span><br><span class="line">165</span><br><span class="line">166</span><br><span class="line">167</span><br><span class="line">168</span><br><span class="line">169</span><br><span class="line">170</span><br><span class="line">171</span><br><span class="line">172</span><br><span class="line">173</span><br><span class="line">174</span><br><span class="line">175</span><br><span class="line">176</span><br><span class="line">177</span><br><span class="line">178</span><br><span class="line">179</span><br><span class="line">180</span><br><span class="line">181</span><br><span class="line">182</span><br><span class="line">183</span><br><span class="line">184</span><br><span class="line">185</span><br><span class="line">186</span><br><span class="line">187</span><br><span class="line">188</span><br><span class="line">189</span><br><span class="line">190</span><br><span class="line">191</span><br><span class="line">192</span><br><span class="line">193</span><br><span class="line">194</span><br><span class="line">195</span><br><span class="line">196</span><br><span class="line">197</span><br><span class="line">198</span><br><span class="line">199</span><br><span class="line">200</span><br><span class="line">201</span><br><span class="line">202</span><br><span class="line">203</span><br><span class="line">204</span><br><span class="line">205</span><br><span class="line">206</span><br><span class="line">207</span><br><span class="line">208</span><br><span class="line">209</span><br><span class="line">210</span><br><span class="line">211</span><br><span class="line">212</span><br><span class="line">213</span><br><span class="line">214</span><br><span class="line">215</span><br><span class="line">216</span><br><span class="line">217</span><br><span class="line">218</span><br><span class="line">219</span><br><span class="line">220</span><br><span class="line">221</span><br><span class="line">222</span><br><span class="line">223</span><br><span class="line">224</span><br><span class="line">225</span><br><span class="line">226</span><br><span class="line">227</span><br><span class="line">228</span><br><span class="line">229</span><br><span class="line">230</span><br><span class="line">231</span><br><span class="line">232</span><br><span class="line">233</span><br><span class="line">234</span><br><span class="line">235</span><br><span class="line">236</span><br><span class="line">237</span><br><span class="line">238</span><br><span class="line">239</span><br><span class="line">240</span><br><span class="line">241</span><br><span class="line">242</span><br><span class="line">243</span><br><span class="line">244</span><br><span class="line">245</span><br><span class="line">246</span><br><span class="line">247</span><br><span class="line">248</span><br><span class="line">249</span><br><span class="line">250</span><br><span class="line">251</span><br><span class="line">252</span><br><span class="line">253</span><br><span class="line">254</span><br><span class="line">255</span><br><span class="line">256</span><br><span class="line">257</span><br><span class="line">258</span><br><span class="line">259</span><br><span class="line">260</span><br><span class="line">261</span><br><span class="line">262</span><br><span class="line">263</span><br><span class="line">264</span><br><span class="line">265</span><br><span class="line">266</span><br><span class="line">267</span><br><span class="line">268</span><br><span class="line">269</span><br><span class="line">270</span><br><span class="line">271</span><br><span class="line">272</span><br><span class="line">273</span><br><span class="line">274</span><br><span class="line">275</span><br><span class="line">276</span><br><span class="line">277</span><br><span class="line">278</span><br><span class="line">279</span><br><span class="line">280</span><br><span class="line">281</span><br><span class="line">282</span><br><span class="line">283</span><br><span class="line">284</span><br><span class="line">285</span><br><span class="line">286</span><br><span class="line">287</span><br><span class="line">288</span><br><span class="line">289</span><br><span class="line">290</span><br><span class="line">291</span><br><span class="line">292</span><br><span class="line">293</span><br><span class="line">294</span><br><span class="line">295</span><br><span class="line">296</span><br><span class="line">297</span><br><span class="line">298</span><br><span class="line">299</span><br><span class="line">300</span><br><span class="line">301</span><br><span class="line">302</span><br><span class="line">303</span><br><span class="line">304</span><br><span class="line">305</span><br><span class="line">306</span><br><span class="line">307</span><br><span class="line">308</span><br><span class="line">309</span><br><span class="line">310</span><br><span class="line">311</span><br><span class="line">312</span><br><span class="line">313</span><br><span class="line">314</span><br><span class="line">315</span><br><span class="line">316</span><br><span class="line">317</span><br><span class="line">318</span><br><span class="line">319</span><br><span class="line">320</span><br><span class="line">321</span><br><span class="line">322</span><br><span class="line">323</span><br><span class="line">324</span><br><span class="line">325</span><br><span class="line">326</span><br><span class="line">327</span><br><span class="line">328</span><br><span class="line">329</span><br><span class="line">330</span><br><span class="line">331</span><br><span class="line">332</span><br><span class="line">333</span><br><span class="line">334</span><br><span class="line">335</span><br><span class="line">336</span><br><span class="line">337</span><br><span class="line">338</span><br><span class="line">339</span><br><span class="line">340</span><br><span class="line">341</span><br><span class="line">342</span><br><span class="line">343</span><br><span class="line">344</span><br><span class="line">345</span><br><span class="line">346</span><br><span class="line">347</span><br><span class="line">348</span><br><span class="line">349</span><br><span class="line">350</span><br><span class="line">351</span><br><span class="line">352</span><br><span class="line">353</span><br><span class="line">354</span><br><span class="line">355</span><br><span class="line">356</span><br><span class="line">357</span><br><span class="line">358</span><br><span class="line">359</span><br><span class="line">360</span><br><span class="line">361</span><br><span class="line">362</span><br><span class="line">363</span><br><span class="line">364</span><br><span class="line">365</span><br><span class="line">366</span><br><span class="line">367</span><br><span class="line">368</span><br><span class="line">369</span><br><span class="line">370</span><br><span class="line">371</span><br><span class="line">372</span><br><span class="line">373</span><br><span class="line">374</span><br><span class="line">375</span><br><span class="line">376</span><br><span class="line">377</span><br><span class="line">378</span><br><span class="line">379</span><br><span class="line">380</span><br><span class="line">381</span><br><span class="line">382</span><br><span class="line">383</span><br><span class="line">384</span><br><span class="line">385</span><br><span class="line">386</span><br><span class="line">387</span><br><span class="line">388</span><br><span class="line">389</span><br><span class="line">390</span><br><span class="line">391</span><br><span class="line">392</span><br><span class="line">393</span><br><span class="line">394</span><br><span class="line">395</span><br><span class="line">396</span><br><span class="line">397</span><br><span class="line">398</span><br><span class="line">399</span><br><span class="line">400</span><br><span class="line">401</span><br><span class="line">402</span><br><span class="line">403</span><br><span class="line">404</span><br><span class="line">405</span><br><span class="line">406</span><br><span class="line">407</span><br><span class="line">408</span><br><span class="line">409</span><br><span class="line">410</span><br><span class="line">411</span><br><span class="line">412</span><br><span class="line">413</span><br><span class="line">414</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> *  AI 摘要 - Ollama 直连版 </span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line">(<span class="keyword">function</span>(<span class="params"></span>) &#123;</span><br><span class="line">    <span class="string">&#x27;use strict&#x27;</span>;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">const</span> <span class="variable constant_">CONFIG</span> = &#123;</span><br><span class="line">        <span class="attr">apiUrl</span>: <span class="string">&#x27;https://ollama.eucalyptus.cc/api/generate&#x27;</span>,</span><br><span class="line">        <span class="attr">model</span>: <span class="string">&#x27;qwen2.5:3b&#x27;</span>,</span><br><span class="line">        <span class="attr">typeSpeed</span>: <span class="number">35</span>,</span><br><span class="line">        <span class="attr">maxSummaryLength</span>: <span class="number">500</span>,</span><br><span class="line">        <span class="attr">retryCount</span>: <span class="number">1</span></span><br><span class="line">    &#125;;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">const</span> <span class="variable constant_">FRONTEND_CACHE_KEY</span> = <span class="string">&#x27;ai_summary_frontend_v7&#x27;</span>;</span><br><span class="line">    <span class="keyword">const</span> <span class="variable constant_">FRONTEND_CACHE_MAX_AGE</span> = <span class="number">7</span> * <span class="number">24</span> * <span class="number">60</span> * <span class="number">60</span> * <span class="number">1000</span>;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">let</span> isProcessing = <span class="literal">false</span>;</span><br><span class="line">    <span class="keyword">let</span> typeWriterTimer = <span class="literal">null</span>;</span><br><span class="line">    <span class="keyword">let</span> abortController = <span class="literal">null</span>;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">function</span> <span class="title function_">waitForElement</span>(<span class="params">selector, timeout = <span class="number">3000</span></span>) &#123;</span><br><span class="line">        <span class="keyword">return</span> <span class="keyword">new</span> <span class="title class_">Promise</span>(<span class="function">(<span class="params">resolve, reject</span>) =&gt;</span> &#123;</span><br><span class="line">            <span class="keyword">const</span> element = <span class="variable language_">document</span>.<span class="title function_">querySelector</span>(selector);</span><br><span class="line">            <span class="keyword">if</span> (element) <span class="keyword">return</span> <span class="title function_">resolve</span>(element);</span><br><span class="line">            <span class="keyword">const</span> observer = <span class="keyword">new</span> <span class="title class_">MutationObserver</span>(<span class="function">() =&gt;</span> &#123;</span><br><span class="line">                <span class="keyword">const</span> el = <span class="variable language_">document</span>.<span class="title function_">querySelector</span>(selector);</span><br><span class="line">                <span class="keyword">if</span> (el) &#123; observer.<span class="title function_">disconnect</span>(); <span class="title function_">resolve</span>(el); &#125;</span><br><span class="line">            &#125;);</span><br><span class="line">            observer.<span class="title function_">observe</span>(<span class="variable language_">document</span>.<span class="property">body</span>, &#123; <span class="attr">childList</span>: <span class="literal">true</span>, <span class="attr">subtree</span>: <span class="literal">true</span> &#125;);</span><br><span class="line">            <span class="built_in">setTimeout</span>(<span class="function">() =&gt;</span> &#123; observer.<span class="title function_">disconnect</span>(); <span class="title function_">reject</span>(<span class="keyword">new</span> <span class="title class_">Error</span>(<span class="string">`Timeout waiting for <span class="subst">$&#123;selector&#125;</span>`</span>)); &#125;, timeout);</span><br><span class="line">        &#125;);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">function</span> <span class="title function_">escapeHtml</span>(<span class="params">text</span>) &#123;</span><br><span class="line">        <span class="keyword">if</span> (!text) <span class="keyword">return</span> <span class="string">&#x27;&#x27;</span>;</span><br><span class="line">        <span class="keyword">const</span> div = <span class="variable language_">document</span>.<span class="title function_">createElement</span>(<span class="string">&#x27;div&#x27;</span>);</span><br><span class="line">        div.<span class="property">textContent</span> = text;</span><br><span class="line">        <span class="keyword">return</span> div.<span class="property">innerHTML</span>;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">function</span> <span class="title function_">typeWriter</span>(<span class="params">element, text, speed = CONFIG.typeSpeed</span>) &#123;</span><br><span class="line">        <span class="keyword">return</span> <span class="keyword">new</span> <span class="title class_">Promise</span>(<span class="function">(<span class="params">resolve</span>) =&gt;</span> &#123;</span><br><span class="line">            <span class="keyword">if</span> (typeWriterTimer) &#123; <span class="built_in">clearInterval</span>(typeWriterTimer); typeWriterTimer = <span class="literal">null</span>; &#125;</span><br><span class="line">            <span class="keyword">let</span> index = <span class="number">0</span>;</span><br><span class="line">            element.<span class="property">textContent</span> = <span class="string">&#x27;&#x27;</span>;</span><br><span class="line">            typeWriterTimer = <span class="built_in">setInterval</span>(<span class="function">() =&gt;</span> &#123;</span><br><span class="line">                <span class="keyword">if</span> (index &lt; text.<span class="property">length</span>) &#123; </span><br><span class="line">                    element.<span class="property">textContent</span> += text.<span class="title function_">charAt</span>(index); </span><br><span class="line">                    index++; </span><br><span class="line">                &#125; <span class="keyword">else</span> &#123; </span><br><span class="line">                    <span class="built_in">clearInterval</span>(typeWriterTimer); </span><br><span class="line">                    typeWriterTimer = <span class="literal">null</span>; </span><br><span class="line">                    <span class="title function_">resolve</span>(); </span><br><span class="line">                &#125;</span><br><span class="line">            &#125;, speed);</span><br><span class="line">        &#125;);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">function</span> <span class="title function_">getFrontendCache</span>(<span class="params">path</span>) &#123;</span><br><span class="line">        <span class="keyword">try</span> &#123;</span><br><span class="line">            <span class="keyword">const</span> data = <span class="variable language_">localStorage</span>.<span class="title function_">getItem</span>(<span class="variable constant_">FRONTEND_CACHE_KEY</span>);</span><br><span class="line">            <span class="keyword">if</span> (!data) <span class="keyword">return</span> <span class="literal">null</span>;</span><br><span class="line">            <span class="keyword">const</span> cache = <span class="title class_">JSON</span>.<span class="title function_">parse</span>(data);</span><br><span class="line">            <span class="keyword">const</span> item = cache[path];</span><br><span class="line">            <span class="keyword">if</span> (!item) <span class="keyword">return</span> <span class="literal">null</span>;</span><br><span class="line">            <span class="keyword">if</span> (<span class="title class_">Date</span>.<span class="title function_">now</span>() - item.<span class="property">time</span> &gt; <span class="variable constant_">FRONTEND_CACHE_MAX_AGE</span>) &#123;</span><br><span class="line">                <span class="keyword">delete</span> cache[path];</span><br><span class="line">                <span class="variable language_">localStorage</span>.<span class="title function_">setItem</span>(<span class="variable constant_">FRONTEND_CACHE_KEY</span>, <span class="title class_">JSON</span>.<span class="title function_">stringify</span>(cache));</span><br><span class="line">                <span class="keyword">return</span> <span class="literal">null</span>;</span><br><span class="line">            &#125;</span><br><span class="line">            <span class="keyword">return</span> item.<span class="property">summary</span>;</span><br><span class="line">        &#125; <span class="keyword">catch</span> &#123;</span><br><span class="line">            <span class="keyword">return</span> <span class="literal">null</span>;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">function</span> <span class="title function_">setFrontendCache</span>(<span class="params">path, summary</span>) &#123;</span><br><span class="line">        <span class="keyword">try</span> &#123;</span><br><span class="line">            <span class="keyword">const</span> data = <span class="variable language_">localStorage</span>.<span class="title function_">getItem</span>(<span class="variable constant_">FRONTEND_CACHE_KEY</span>);</span><br><span class="line">            <span class="keyword">const</span> cache = data ? <span class="title class_">JSON</span>.<span class="title function_">parse</span>(data) : &#123;&#125;;</span><br><span class="line">            cache[path] = &#123; summary, <span class="attr">time</span>: <span class="title class_">Date</span>.<span class="title function_">now</span>() &#125;;</span><br><span class="line">            <span class="variable language_">localStorage</span>.<span class="title function_">setItem</span>(<span class="variable constant_">FRONTEND_CACHE_KEY</span>, <span class="title class_">JSON</span>.<span class="title function_">stringify</span>(cache));</span><br><span class="line">        &#125; <span class="keyword">catch</span> &#123;&#125;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">function</span> <span class="title function_">getArticleText</span>(<span class="params"></span>) &#123;</span><br><span class="line">        <span class="keyword">const</span> selectors = [</span><br><span class="line">            <span class="string">&#x27;#article-container .post-content&#x27;</span>,</span><br><span class="line">            <span class="string">&#x27;#article-container #post&#x27;</span>,</span><br><span class="line">            <span class="string">&#x27;.post-content&#x27;</span>,</span><br><span class="line">            <span class="string">&#x27;article .entry-content&#x27;</span>,</span><br><span class="line">            <span class="string">&#x27;#post-content&#x27;</span>,</span><br><span class="line">            <span class="string">&#x27;.article-content&#x27;</span>,</span><br><span class="line">            <span class="string">&#x27;.markdown-body&#x27;</span>,</span><br><span class="line">            <span class="string">&#x27;#content article&#x27;</span></span><br><span class="line">        ];</span><br><span class="line">        </span><br><span class="line">        <span class="keyword">let</span> article = <span class="literal">null</span>;</span><br><span class="line">        <span class="keyword">for</span> (<span class="keyword">const</span> sel <span class="keyword">of</span> selectors) &#123;</span><br><span class="line">            article = <span class="variable language_">document</span>.<span class="title function_">querySelector</span>(sel);</span><br><span class="line">            <span class="keyword">if</span> (article &amp;&amp; article.<span class="property">innerText</span>.<span class="title function_">trim</span>().<span class="property">length</span> &gt; <span class="number">200</span>) <span class="keyword">break</span>;</span><br><span class="line">        &#125;</span><br><span class="line">        </span><br><span class="line">        <span class="keyword">if</span> (!article) &#123; </span><br><span class="line">            <span class="variable language_">console</span>.<span class="title function_">warn</span>(<span class="string">&#x27;[AI Summary] Article content not found.&#x27;</span>); </span><br><span class="line">            <span class="keyword">return</span> <span class="string">&#x27;&#x27;</span>; </span><br><span class="line">        &#125;</span><br><span class="line">        </span><br><span class="line">        <span class="keyword">const</span> clone = article.<span class="title function_">cloneNode</span>(<span class="literal">true</span>);</span><br><span class="line">        </span><br><span class="line">        <span class="keyword">const</span> removeSelectors = [</span><br><span class="line">            <span class="string">&#x27;h1&#x27;</span>, <span class="string">&#x27;h2.post-title&#x27;</span>, <span class="string">&#x27;.post-title&#x27;</span>, <span class="string">&#x27;.page-title&#x27;</span>,</span><br><span class="line">            <span class="string">&#x27;.post-meta&#x27;</span>, <span class="string">&#x27;.meta&#x27;</span>, <span class="string">&#x27;.entry-meta&#x27;</span>, <span class="string">&#x27;.article-meta&#x27;</span>,</span><br><span class="line">            <span class="string">&#x27;.post-date&#x27;</span>, <span class="string">&#x27;.post-time&#x27;</span>, <span class="string">&#x27;.published&#x27;</span>, <span class="string">&#x27;.updated&#x27;</span>,</span><br><span class="line">            <span class="string">&#x27;.author&#x27;</span>, <span class="string">&#x27;.byline&#x27;</span>, <span class="string">&#x27;.post-author&#x27;</span>,</span><br><span class="line">            <span class="string">&#x27;.post-tags&#x27;</span>, <span class="string">&#x27;.tags&#x27;</span>, <span class="string">&#x27;.tag-cloud&#x27;</span>, <span class="string">&#x27;.post-categories&#x27;</span>,</span><br><span class="line">            <span class="string">&#x27;.categories&#x27;</span>, <span class="string">&#x27;.post-category&#x27;</span>, <span class="string">&#x27;.article-tags&#x27;</span>,</span><br><span class="line">            <span class="string">&#x27;nav&#x27;</span>, <span class="string">&#x27;.nav&#x27;</span>, <span class="string">&#x27;.breadcrumb&#x27;</span>, <span class="string">&#x27;.crumbs&#x27;</span>,</span><br><span class="line">            <span class="string">&#x27;aside&#x27;</span>, <span class="string">&#x27;.sidebar&#x27;</span>, <span class="string">&#x27;.widget&#x27;</span>, <span class="string">&#x27;.toc&#x27;</span>, <span class="string">&#x27;#toc&#x27;</span>,</span><br><span class="line">            <span class="string">&#x27;header&#x27;</span>, <span class="string">&#x27;.post-header&#x27;</span>, <span class="string">&#x27;.entry-header&#x27;</span>, <span class="string">&#x27;.article-header&#x27;</span>,</span><br><span class="line">            <span class="string">&#x27;footer&#x27;</span>, <span class="string">&#x27;.post-footer&#x27;</span>, <span class="string">&#x27;.entry-footer&#x27;</span>, <span class="string">&#x27;.article-footer&#x27;</span>,</span><br><span class="line">            <span class="string">&#x27;#post-comment&#x27;</span>, <span class="string">&#x27;.comments&#x27;</span>, <span class="string">&#x27;.comment-area&#x27;</span>,</span><br><span class="line">            <span class="string">&#x27;.share&#x27;</span>, <span class="string">&#x27;.social-share&#x27;</span>, <span class="string">&#x27;.reward&#x27;</span>, <span class="string">&#x27;.donate&#x27;</span>,</span><br><span class="line">            <span class="string">&#x27;.post-copyright&#x27;</span>, <span class="string">&#x27;.copyright&#x27;</span>, <span class="string">&#x27;.license&#x27;</span>, <span class="string">&#x27;.declaration&#x27;</span>,</span><br><span class="line">            <span class="string">&#x27;.relatedPosts&#x27;</span>, <span class="string">&#x27;.related-posts&#x27;</span>, <span class="string">&#x27;.related&#x27;</span>, <span class="string">&#x27;.recommend&#x27;</span>,</span><br><span class="line">            <span class="string">&#x27;.anzhiyu-ai-summary&#x27;</span>, <span class="string">&#x27;#ai-summary&#x27;</span>,</span><br><span class="line">            <span class="string">&#x27;script&#x27;</span>, <span class="string">&#x27;style&#x27;</span>, <span class="string">&#x27;pre&#x27;</span>, <span class="string">&#x27;code&#x27;</span>, <span class="string">&#x27;table&#x27;</span>, </span><br><span class="line">            <span class="string">&#x27;img&#x27;</span>, <span class="string">&#x27;figure&#x27;</span>, <span class="string">&#x27;figcaption&#x27;</span>, <span class="string">&#x27;svg&#x27;</span>,</span><br><span class="line">            <span class="string">&#x27;blockquote&#x27;</span>,</span><br><span class="line">            <span class="string">&#x27;.ads-wrap&#x27;</span>, <span class="string">&#x27;.ad&#x27;</span>, <span class="string">&#x27;.advertisement&#x27;</span>,</span><br><span class="line">            <span class="string">&#x27;.highlight&#x27;</span>, <span class="string">&#x27;.mermaid&#x27;</span>, <span class="string">&#x27;.katex&#x27;</span>, <span class="string">&#x27;.mathjax&#x27;</span>,</span><br><span class="line">            <span class="string">&#x27;.pagination&#x27;</span>, <span class="string">&#x27;.pager&#x27;</span>, <span class="string">&#x27;.page-nav&#x27;</span>,</span><br><span class="line">            <span class="string">&#x27;.notice&#x27;</span>, <span class="string">&#x27;.alert&#x27;</span>, <span class="string">&#x27;.tips&#x27;</span>, <span class="string">&#x27;.warning&#x27;</span></span><br><span class="line">        ];</span><br><span class="line">        </span><br><span class="line">        removeSelectors.<span class="title function_">forEach</span>(<span class="function"><span class="params">sel</span> =&gt;</span> &#123;</span><br><span class="line">            clone.<span class="title function_">querySelectorAll</span>(sel).<span class="title function_">forEach</span>(<span class="function"><span class="params">el</span> =&gt;</span> el.<span class="title function_">remove</span>());</span><br><span class="line">        &#125;);</span><br><span class="line">        </span><br><span class="line">        <span class="keyword">let</span> text = clone.<span class="property">innerText</span></span><br><span class="line">            .<span class="title function_">replace</span>(<span class="regexp">/\s+/g</span>, <span class="string">&#x27; &#x27;</span>)</span><br><span class="line">            .<span class="title function_">replace</span>(<span class="regexp">/[*#\-_`~\[\]()&gt;|]/g</span>, <span class="string">&#x27; &#x27;</span>)</span><br><span class="line">            .<span class="title function_">replace</span>(<span class="regexp">/\b[A-Z]&#123;5,&#125;\b/g</span>, <span class="string">&#x27; &#x27;</span>)</span><br><span class="line">            .<span class="title function_">replace</span>(<span class="regexp">/\d&#123;4&#125;[年/-]\d&#123;1,2&#125;[月/-]\d&#123;1,2&#125;[日]?/g</span>, <span class="string">&#x27; &#x27;</span>)</span><br><span class="line">            .<span class="title function_">replace</span>(<span class="regexp">/[^\u4e00-\u9fa5a-zA-Z0-9，。！？；：&quot;&quot;&#x27;&#x27;（）【】《》.,;:!?&#x27;&quot;()\[\]&lt;&gt;、\-—\n]/g</span>, <span class="string">&#x27; &#x27;</span>)</span><br><span class="line">            .<span class="title function_">replace</span>(<span class="regexp">/\s+/g</span>, <span class="string">&#x27; &#x27;</span>)</span><br><span class="line">            .<span class="title function_">trim</span>();</span><br><span class="line">        </span><br><span class="line">        <span class="keyword">if</span> (text.<span class="property">length</span> &lt; <span class="number">100</span>) &#123;</span><br><span class="line">            <span class="variable language_">console</span>.<span class="title function_">warn</span>(<span class="string">&#x27;[AI Summary] Content too short, trying fallback...&#x27;</span>);</span><br><span class="line">            <span class="keyword">const</span> paragraphs = <span class="title class_">Array</span>.<span class="title function_">from</span>(<span class="variable language_">document</span>.<span class="title function_">querySelectorAll</span>(<span class="string">&#x27;p&#x27;</span>));</span><br><span class="line">            <span class="keyword">const</span> longestP = paragraphs</span><br><span class="line">                .<span class="title function_">filter</span>(<span class="function"><span class="params">p</span> =&gt;</span> &#123;</span><br><span class="line">                    <span class="keyword">const</span> t = p.<span class="property">innerText</span>.<span class="title function_">trim</span>();</span><br><span class="line">                    <span class="keyword">return</span> t.<span class="property">length</span> &gt; <span class="number">50</span> &amp;&amp; !t.<span class="title function_">includes</span>(<span class="string">&#x27;标签&#x27;</span>) &amp;&amp; !t.<span class="title function_">includes</span>(<span class="string">&#x27;分类&#x27;</span>) &amp;&amp; !t.<span class="title function_">includes</span>(<span class="string">&#x27;作者&#x27;</span>);</span><br><span class="line">                &#125;)</span><br><span class="line">                .<span class="title function_">sort</span>(<span class="function">(<span class="params">a, b</span>) =&gt;</span> b.<span class="property">innerText</span>.<span class="property">length</span> - a.<span class="property">innerText</span>.<span class="property">length</span>)</span><br><span class="line">                .<span class="title function_">slice</span>(<span class="number">0</span>, <span class="number">10</span>);</span><br><span class="line">            </span><br><span class="line">            <span class="keyword">if</span> (longestP.<span class="property">length</span> &gt; <span class="number">0</span>) &#123;</span><br><span class="line">                text = longestP.<span class="title function_">map</span>(<span class="function"><span class="params">p</span> =&gt;</span> p.<span class="property">innerText</span>).<span class="title function_">join</span>(<span class="string">&#x27; &#x27;</span>);</span><br><span class="line">                text = text.<span class="title function_">replace</span>(<span class="regexp">/\s+/g</span>, <span class="string">&#x27; &#x27;</span>).<span class="title function_">trim</span>();</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">        </span><br><span class="line">        <span class="keyword">return</span> text;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">async</span> <span class="keyword">function</span> <span class="title function_">callSummaryAPI</span>(<span class="params">text, retry = <span class="number">0</span></span>) &#123;</span><br><span class="line">        <span class="keyword">if</span> (abortController) abortController.<span class="title function_">abort</span>();</span><br><span class="line">        abortController = <span class="keyword">new</span> <span class="title class_">AbortController</span>();</span><br><span class="line">        <span class="keyword">const</span> timeoutId = <span class="built_in">setTimeout</span>(<span class="function">() =&gt;</span> abortController.<span class="title function_">abort</span>(), <span class="number">120000</span>);</span><br><span class="line">        </span><br><span class="line">        <span class="keyword">try</span> &#123;</span><br><span class="line">            <span class="comment">// ========== 优化后的提示词 ==========</span></span><br><span class="line">            <span class="keyword">const</span> prompt = <span class="string">`你是一个专业的博客文章摘要生成助手。</span></span><br><span class="line"><span class="string"></span></span><br><span class="line"><span class="string">请阅读以下文章，用一段话概括其核心内容。</span></span><br><span class="line"><span class="string"></span></span><br><span class="line"><span class="string">要求：</span></span><br><span class="line"><span class="string">- 自然流畅，像人写的读后感</span></span><br><span class="line"><span class="string">- 开头用&quot;本文介绍了&quot;或&quot;本文主要介绍了&quot;</span></span><br><span class="line"><span class="string">- 不要分点，不要列条目</span></span><br><span class="line"><span class="string">- 不要复述标题、作者、日期、标签等元信息</span></span><br><span class="line"><span class="string">- 不要输出 markdown 格式（如 **、-、# 等）</span></span><br><span class="line"><span class="string">- 不要包含&quot;摘要：&quot;&quot;总结：&quot;&quot;综上所述&quot;等前缀</span></span><br><span class="line"><span class="string">- 直接输出正文</span></span><br><span class="line"><span class="string"></span></span><br><span class="line"><span class="string">文章内容：</span></span><br><span class="line"><span class="string">&quot;&quot;&quot;</span></span><br><span class="line"><span class="string"><span class="subst">$&#123;text.slice(<span class="number">0</span>, <span class="number">4000</span>)&#125;</span></span></span><br><span class="line"><span class="string">&quot;&quot;&quot;</span></span><br><span class="line"><span class="string"></span></span><br><span class="line"><span class="string">概括：`</span>;</span><br><span class="line">            <span class="comment">// ====================================</span></span><br><span class="line">            </span><br><span class="line">            <span class="keyword">const</span> response = <span class="keyword">await</span> <span class="title function_">fetch</span>(<span class="variable constant_">CONFIG</span>.<span class="property">apiUrl</span>, &#123;</span><br><span class="line">                <span class="attr">method</span>: <span class="string">&#x27;POST&#x27;</span>,</span><br><span class="line">                <span class="attr">headers</span>: &#123; <span class="string">&#x27;Content-Type&#x27;</span>: <span class="string">&#x27;application/json&#x27;</span> &#125;,</span><br><span class="line">                <span class="attr">body</span>: <span class="title class_">JSON</span>.<span class="title function_">stringify</span>(&#123;</span><br><span class="line">                    <span class="attr">model</span>: <span class="variable constant_">CONFIG</span>.<span class="property">model</span>,</span><br><span class="line">                    <span class="attr">prompt</span>: prompt,</span><br><span class="line">                    <span class="attr">stream</span>: <span class="literal">false</span>,</span><br><span class="line">                    <span class="attr">options</span>: &#123;</span><br><span class="line">                        <span class="attr">temperature</span>: <span class="number">0.3</span>,</span><br><span class="line">                        <span class="attr">num_predict</span>: <span class="number">800</span></span><br><span class="line">                    &#125;</span><br><span class="line">                &#125;),</span><br><span class="line">                <span class="attr">signal</span>: abortController.<span class="property">signal</span></span><br><span class="line">            &#125;);</span><br><span class="line">            <span class="built_in">clearTimeout</span>(timeoutId);</span><br><span class="line">            </span><br><span class="line">            <span class="keyword">if</span> (!response.<span class="property">ok</span>) &#123;</span><br><span class="line">                <span class="keyword">const</span> errorText = <span class="keyword">await</span> response.<span class="title function_">text</span>();</span><br><span class="line">                <span class="keyword">throw</span> <span class="keyword">new</span> <span class="title class_">Error</span>(<span class="string">`HTTP <span class="subst">$&#123;response.status&#125;</span>: <span class="subst">$&#123;errorText&#125;</span>`</span>);</span><br><span class="line">            &#125;</span><br><span class="line">            </span><br><span class="line">            <span class="keyword">const</span> data = <span class="keyword">await</span> response.<span class="title function_">json</span>();</span><br><span class="line">            </span><br><span class="line">            <span class="keyword">let</span> summary = data.<span class="property">response</span> || <span class="string">&#x27;&#x27;</span>;</span><br><span class="line">            <span class="keyword">if</span> (!summary) <span class="keyword">throw</span> <span class="keyword">new</span> <span class="title class_">Error</span>(<span class="string">&#x27;Empty response&#x27;</span>);</span><br><span class="line">            </span><br><span class="line">            <span class="comment">// ========== 清理输出 ==========</span></span><br><span class="line">            summary = summary</span><br><span class="line">                <span class="comment">// 去掉纯前缀词（不要删&quot;本文介绍了&quot;）</span></span><br><span class="line">                .<span class="title function_">replace</span>(<span class="regexp">/^\s*(摘要|总结|概括)[：:]\s*/i</span>, <span class="string">&#x27;&#x27;</span>)</span><br><span class="line">                .<span class="title function_">replace</span>(<span class="regexp">/^\s*[&quot;&#x27;]?|[&quot;&#x27;]?\s*$/g</span>, <span class="string">&#x27;&#x27;</span>)</span><br><span class="line">                <span class="comment">// 去掉 markdown</span></span><br><span class="line">                .<span class="title function_">replace</span>(<span class="regexp">/\*\*/g</span>, <span class="string">&#x27;&#x27;</span>)</span><br><span class="line">                .<span class="title function_">replace</span>(<span class="regexp">/^\s*[-*]\s+/gm</span>, <span class="string">&#x27;&#x27;</span>)</span><br><span class="line">                .<span class="title function_">replace</span>(<span class="regexp">/^#&#123;1,6&#125;\s+/gm</span>, <span class="string">&#x27;&#x27;</span>)</span><br><span class="line">                <span class="comment">// 统一&quot;本文介绍了&quot;格式</span></span><br><span class="line">                .<span class="title function_">replace</span>(<span class="regexp">/这篇文章主要(介绍|讲述|讨论|分析)了/g</span>, <span class="string">&#x27;本文介绍了&#x27;</span>)</span><br><span class="line">                .<span class="title function_">replace</span>(<span class="regexp">/本文主要(介绍|讲述|讨论|分析)了/g</span>, <span class="string">&#x27;本文介绍了&#x27;</span>)</span><br><span class="line">                .<span class="title function_">replace</span>(<span class="regexp">/该文章(介绍|讲述|讨论|分析)了/g</span>, <span class="string">&#x27;本文介绍了&#x27;</span>)</span><br><span class="line">                .<span class="title function_">replace</span>(<span class="regexp">/此文章(介绍|讲述|讨论|分析)了/g</span>, <span class="string">&#x27;本文介绍了&#x27;</span>)</span><br><span class="line">                <span class="comment">// 去掉常见废话</span></span><br><span class="line">                .<span class="title function_">replace</span>(<span class="regexp">/综上所述[，。]/g</span>, <span class="string">&#x27;&#x27;</span>)</span><br><span class="line">                .<span class="title function_">replace</span>(<span class="regexp">/总之[，。]/g</span>, <span class="string">&#x27;&#x27;</span>)</span><br><span class="line">                .<span class="title function_">replace</span>(<span class="regexp">/总而言之[，。]/g</span>, <span class="string">&#x27;&#x27;</span>)</span><br><span class="line">                .<span class="title function_">trim</span>();</span><br><span class="line"></span><br><span class="line">            <span class="comment">// 确保以&quot;本文介绍了&quot;开头</span></span><br><span class="line">            <span class="keyword">if</span> (!<span class="regexp">/^本文(主要)?介绍了/</span>.<span class="title function_">test</span>(summary)) &#123;</span><br><span class="line">                summary = <span class="string">&#x27;本文介绍了&#x27;</span> + summary.<span class="title function_">replace</span>(<span class="regexp">/^[，。！？\s]+/</span>, <span class="string">&#x27;&#x27;</span>);</span><br><span class="line">            &#125;</span><br><span class="line"></span><br><span class="line">            <span class="comment">// 句子太多时取前3句，避免太长</span></span><br><span class="line">            <span class="keyword">const</span> sentences = summary.<span class="title function_">split</span>(<span class="regexp">/[。！？.!?]/</span>).<span class="title function_">filter</span>(<span class="function"><span class="params">s</span> =&gt;</span> s.<span class="title function_">trim</span>());</span><br><span class="line">            <span class="keyword">if</span> (sentences.<span class="property">length</span> &gt; <span class="number">3</span>) &#123;</span><br><span class="line">                summary = sentences.<span class="title function_">slice</span>(<span class="number">0</span>, <span class="number">3</span>).<span class="title function_">join</span>(<span class="string">&#x27;。&#x27;</span>) + <span class="string">&#x27;。&#x27;</span>;</span><br><span class="line">            &#125;</span><br><span class="line">            </span><br><span class="line">            <span class="comment">// 显示长度截断（语义完整）</span></span><br><span class="line">            <span class="keyword">if</span> (summary.<span class="property">length</span> &gt; <span class="variable constant_">CONFIG</span>.<span class="property">maxSummaryLength</span>) &#123;</span><br><span class="line">                summary = summary.<span class="title function_">slice</span>(<span class="number">0</span>, <span class="variable constant_">CONFIG</span>.<span class="property">maxSummaryLength</span>);</span><br><span class="line">                <span class="keyword">const</span> lastPunct = <span class="title class_">Math</span>.<span class="title function_">max</span>(</span><br><span class="line">                    summary.<span class="title function_">lastIndexOf</span>(<span class="string">&#x27;。&#x27;</span>),</span><br><span class="line">                    summary.<span class="title function_">lastIndexOf</span>(<span class="string">&#x27;！&#x27;</span>),</span><br><span class="line">                    summary.<span class="title function_">lastIndexOf</span>(<span class="string">&#x27;？&#x27;</span>)</span><br><span class="line">                );</span><br><span class="line">                <span class="keyword">if</span> (lastPunct &gt; <span class="variable constant_">CONFIG</span>.<span class="property">maxSummaryLength</span> * <span class="number">0.7</span>) &#123;</span><br><span class="line">                    summary = summary.<span class="title function_">slice</span>(<span class="number">0</span>, lastPunct + <span class="number">1</span>);</span><br><span class="line">                &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">                    summary = summary.<span class="title function_">replace</span>(<span class="regexp">/[^。！？]*$/</span>, <span class="string">&#x27;&#x27;</span>) + <span class="string">&#x27;。&#x27;</span>;</span><br><span class="line">                &#125;</span><br><span class="line">            &#125;</span><br><span class="line">            </span><br><span class="line">            <span class="keyword">if</span> (!<span class="regexp">/[。！？.!?]$/</span>.<span class="title function_">test</span>(summary)) &#123;</span><br><span class="line">                summary += <span class="string">&#x27;。&#x27;</span>;</span><br><span class="line">            &#125;</span><br><span class="line">            </span><br><span class="line">            <span class="keyword">return</span> summary;</span><br><span class="line">            </span><br><span class="line">        &#125; <span class="keyword">catch</span> (err) &#123;</span><br><span class="line">            <span class="built_in">clearTimeout</span>(timeoutId);</span><br><span class="line">            <span class="keyword">if</span> (retry &lt; <span class="variable constant_">CONFIG</span>.<span class="property">retryCount</span>) &#123;</span><br><span class="line">                <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">`[AI Summary] Retrying... (<span class="subst">$&#123;retry + <span class="number">1</span>&#125;</span>/<span class="subst">$&#123;CONFIG.retryCount&#125;</span>)`</span>);</span><br><span class="line">                <span class="keyword">await</span> <span class="keyword">new</span> <span class="title class_">Promise</span>(<span class="function"><span class="params">r</span> =&gt;</span> <span class="built_in">setTimeout</span>(r, <span class="number">2000</span>));</span><br><span class="line">                <span class="keyword">return</span> <span class="title function_">callSummaryAPI</span>(text, retry + <span class="number">1</span>);</span><br><span class="line">            &#125;</span><br><span class="line">            <span class="keyword">throw</span> err;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">function</span> <span class="title function_">renderContainer</span>(<span class="params">targetContainer, isLoading = <span class="literal">false</span>, content = <span class="string">&#x27;&#x27;</span></span>) &#123;</span><br><span class="line">        <span class="keyword">if</span> (!targetContainer) <span class="keyword">return</span> <span class="literal">null</span>;</span><br><span class="line">        </span><br><span class="line">        <span class="keyword">const</span> loadingHtml = <span class="string">`</span></span><br><span class="line"><span class="string">            &lt;span class=&quot;ai-loading-inline&quot;&gt;</span></span><br><span class="line"><span class="string">                &lt;svg class=&quot;ai-loading-svg&quot; width=&quot;16&quot; height=&quot;16&quot; viewBox=&quot;0 0 50 50&quot;&gt;</span></span><br><span class="line"><span class="string">                    &lt;circle cx=&quot;25&quot; cy=&quot;25&quot; r=&quot;20&quot; fill=&quot;none&quot; stroke=&quot;currentColor&quot; stroke-width=&quot;3&quot; </span></span><br><span class="line"><span class="string">                        stroke-linecap=&quot;round&quot; stroke-dasharray=&quot;80&quot; stroke-dashoffset=&quot;60&quot;&gt;</span></span><br><span class="line"><span class="string">                        &lt;animateTransform attributeName=&quot;transform&quot; type=&quot;rotate&quot; from=&quot;0 25 25&quot; to=&quot;360 25 25&quot; dur=&quot;1s&quot; repeatCount=&quot;indefinite&quot;/&gt;</span></span><br><span class="line"><span class="string">                    &lt;/circle&gt;</span></span><br><span class="line"><span class="string">                &lt;/svg&gt;</span></span><br><span class="line"><span class="string">                &lt;span class=&quot;ai-loading-dots&quot;&gt;AI 正在分析文章&lt;/span&gt;</span></span><br><span class="line"><span class="string">            &lt;/span&gt;</span></span><br><span class="line"><span class="string">        `</span>;</span><br><span class="line">        </span><br><span class="line">        targetContainer.<span class="property">innerHTML</span> = <span class="string">`</span></span><br><span class="line"><span class="string">            &lt;div class=&quot;ai-content&quot;&gt;</span></span><br><span class="line"><span class="string">                &lt;div class=&quot;ai-header&quot;&gt;</span></span><br><span class="line"><span class="string">                    &lt;i class=&quot;eucalyptus icon-bilibili&quot;&gt;&lt;/i&gt;&lt;span class=&quot;ai-titles&quot;&gt;AI 摘要&lt;/span&gt;&lt;span class=&quot;ai-badge&quot;&gt;AGENT&lt;/span&gt;</span></span><br><span class="line"><span class="string">                &lt;/div&gt;</span></span><br><span class="line"><span class="string">                &lt;div class=&quot;ai-text-box&quot;&gt;</span></span><br><span class="line"><span class="string">                    &lt;p id=&quot;ai-summary-text&quot;&gt;<span class="subst">$&#123;isLoading ? loadingHtml : escapeHtml(content)&#125;</span>&lt;/p&gt;</span></span><br><span class="line"><span class="string">                &lt;/div&gt;</span></span><br><span class="line"><span class="string">            &lt;/div&gt;</span></span><br><span class="line"><span class="string">        `</span>;</span><br><span class="line">        <span class="keyword">return</span> targetContainer.<span class="title function_">querySelector</span>(<span class="string">&#x27;#ai-summary-text&#x27;</span>);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">function</span> <span class="title function_">renderError</span>(<span class="params">targetContainer, message</span>) &#123;</span><br><span class="line">        <span class="keyword">if</span> (!targetContainer) <span class="keyword">return</span>;</span><br><span class="line">        targetContainer.<span class="property">innerHTML</span> = <span class="string">`</span></span><br><span class="line"><span class="string">            &lt;div class=&quot;ai-content&quot;&gt;</span></span><br><span class="line"><span class="string">                &lt;div class=&quot;ai-header&quot;&gt;</span></span><br><span class="line"><span class="string">                    &lt;i class=&quot;eucalyptus icon-bilibili&quot;&gt;&lt;/i&gt;&lt;span class=&quot;ai-titles&quot;&gt;AI 摘要&lt;/span&gt;&lt;span class=&quot;ai-badge&quot;&gt;AGENT&lt;/span&gt;</span></span><br><span class="line"><span class="string">                &lt;/div&gt;</span></span><br><span class="line"><span class="string">                &lt;div class=&quot;ai-text-box&quot;&gt;</span></span><br><span class="line"><span class="string">                    &lt;p style=&quot;color: #999;&quot;&gt;<span class="subst">$&#123;escapeHtml(message)&#125;</span>&lt;/p&gt;</span></span><br><span class="line"><span class="string">                &lt;/div&gt;</span></span><br><span class="line"><span class="string">            &lt;/div&gt;</span></span><br><span class="line"><span class="string">        `</span>;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">function</span> <span class="title function_">bootstrap</span>(<span class="params"></span>) &#123;</span><br><span class="line">        <span class="keyword">if</span> (abortController) &#123;</span><br><span class="line">            abortController.<span class="title function_">abort</span>();</span><br><span class="line">            abortController = <span class="literal">null</span>;</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">if</span> (typeWriterTimer) &#123; </span><br><span class="line">            <span class="built_in">clearInterval</span>(typeWriterTimer); </span><br><span class="line">            typeWriterTimer = <span class="literal">null</span>; </span><br><span class="line">        &#125;</span><br><span class="line">        isProcessing = <span class="literal">false</span>;</span><br><span class="line">        </span><br><span class="line">        <span class="keyword">if</span> (<span class="variable language_">document</span>.<span class="title function_">getElementById</span>(<span class="string">&#x27;ai-summary&#x27;</span>)) &#123;</span><br><span class="line">            <span class="built_in">setTimeout</span>(initAISummary, <span class="number">100</span>);</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">async</span> <span class="keyword">function</span> <span class="title function_">initAISummary</span>(<span class="params"></span>) &#123;</span><br><span class="line">        <span class="keyword">if</span> (isProcessing) <span class="keyword">return</span>;</span><br><span class="line">        isProcessing = <span class="literal">true</span>;</span><br><span class="line">        </span><br><span class="line">        <span class="keyword">try</span> &#123;</span><br><span class="line">            <span class="keyword">const</span> container = <span class="keyword">await</span> <span class="title function_">waitForElement</span>(<span class="string">&#x27;#ai-summary&#x27;</span>);</span><br><span class="line">            <span class="keyword">const</span> text = <span class="title function_">getArticleText</span>();</span><br><span class="line">            </span><br><span class="line">            <span class="keyword">if</span> (!text || text.<span class="property">length</span> &lt; <span class="number">50</span>) &#123;</span><br><span class="line">                <span class="title function_">renderContainer</span>(container, <span class="literal">false</span>, <span class="string">&#x27;本文篇幅较短，暂无详细摘要。&#x27;</span>);</span><br><span class="line">                isProcessing = <span class="literal">false</span>;</span><br><span class="line">                <span class="keyword">return</span>;</span><br><span class="line">            &#125;</span><br><span class="line">            </span><br><span class="line">            <span class="keyword">const</span> cacheKey = <span class="variable language_">window</span>.<span class="property">location</span>.<span class="property">pathname</span>;</span><br><span class="line">            </span><br><span class="line">            <span class="keyword">const</span> frontendCached = <span class="title function_">getFrontendCache</span>(cacheKey);</span><br><span class="line">            <span class="keyword">if</span> (frontendCached) &#123;</span><br><span class="line">                <span class="keyword">const</span> textEl = <span class="title function_">renderContainer</span>(container, <span class="literal">false</span>, <span class="string">&#x27;&#x27;</span>);</span><br><span class="line">                <span class="keyword">await</span> <span class="title function_">typeWriter</span>(textEl, frontendCached);</span><br><span class="line">                isProcessing = <span class="literal">false</span>;</span><br><span class="line">                <span class="keyword">return</span>;</span><br><span class="line">            &#125;</span><br><span class="line">            </span><br><span class="line">            <span class="title function_">renderContainer</span>(container, <span class="literal">true</span>);</span><br><span class="line">            </span><br><span class="line">            <span class="keyword">let</span> summary;</span><br><span class="line">            <span class="keyword">try</span> &#123; </span><br><span class="line">                summary = <span class="keyword">await</span> <span class="title function_">callSummaryAPI</span>(text); </span><br><span class="line">            &#125;</span><br><span class="line">            <span class="keyword">catch</span> (err) &#123; </span><br><span class="line">                <span class="variable language_">console</span>.<span class="title function_">error</span>(<span class="string">&#x27;[AI Summary] API failed:&#x27;</span>, err); </span><br><span class="line">                <span class="title function_">renderError</span>(container, <span class="string">`AI 服务暂时不可用：<span class="subst">$&#123;err.message&#125;</span>`</span>); </span><br><span class="line">                isProcessing = <span class="literal">false</span>; </span><br><span class="line">                <span class="keyword">return</span>; </span><br><span class="line">            &#125;</span><br><span class="line">            </span><br><span class="line">            <span class="title function_">setFrontendCache</span>(cacheKey, summary);</span><br><span class="line">            </span><br><span class="line">            <span class="keyword">const</span> textEl = <span class="title function_">renderContainer</span>(container, <span class="literal">false</span>, <span class="string">&#x27;&#x27;</span>);</span><br><span class="line">            <span class="keyword">await</span> <span class="title function_">typeWriter</span>(textEl, summary);</span><br><span class="line">            </span><br><span class="line">        &#125; <span class="keyword">catch</span> (err) &#123;</span><br><span class="line">            <span class="variable language_">console</span>.<span class="title function_">error</span>(<span class="string">&#x27;[AI Summary] Init Error:&#x27;</span>, err);</span><br><span class="line">            <span class="keyword">const</span> container = <span class="variable language_">document</span>.<span class="title function_">getElementById</span>(<span class="string">&#x27;ai-summary&#x27;</span>);</span><br><span class="line">            <span class="keyword">if</span> (container) <span class="title function_">renderError</span>(container, <span class="string">&#x27;初始化失败&#x27;</span>);</span><br><span class="line">        &#125; <span class="keyword">finally</span> &#123; </span><br><span class="line">            isProcessing = <span class="literal">false</span>; </span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">if</span> (<span class="variable language_">document</span>.<span class="property">readyState</span> === <span class="string">&#x27;loading&#x27;</span>) &#123;</span><br><span class="line">        <span class="variable language_">document</span>.<span class="title function_">addEventListener</span>(<span class="string">&#x27;DOMContentLoaded&#x27;</span>, bootstrap);</span><br><span class="line">    &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">        <span class="title function_">bootstrap</span>();</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="variable language_">document</span>.<span class="title function_">addEventListener</span>(<span class="string">&#x27;pjax:complete&#x27;</span>, bootstrap);</span><br><span class="line">    <span class="variable language_">document</span>.<span class="title function_">addEventListener</span>(<span class="string">&#x27;turbolinks:load&#x27;</span>, bootstrap);</span><br><span class="line"></span><br><span class="line">    <span class="keyword">let</span> lastUrl = location.<span class="property">pathname</span>;</span><br><span class="line">    <span class="keyword">new</span> <span class="title class_">MutationObserver</span>(<span class="function">() =&gt;</span> &#123; </span><br><span class="line">        <span class="keyword">const</span> url = location.<span class="property">pathname</span>; </span><br><span class="line">        <span class="keyword">if</span> (url !== lastUrl) &#123; </span><br><span class="line">            lastUrl = url; </span><br><span class="line">            <span class="title function_">bootstrap</span>(); </span><br><span class="line">        &#125; </span><br><span class="line">    &#125;).<span class="title function_">observe</span>(<span class="variable language_">document</span>, &#123; <span class="attr">subtree</span>: <span class="literal">true</span>, <span class="attr">childList</span>: <span class="literal">true</span> &#125;);</span><br><span class="line"></span><br><span class="line">&#125;)();</span><br></pre></td></tr></table></figure><p><code>ai-summary.css</code> —— 固定配色方案，支持明暗模式切换：</p><ul><li>白天模式：浅灰背景 + 白色正文框</li><li>暗色模式：深色背景 + 深色正文框</li><li>机器人图标 + 状态指示器（绿色呼吸灯）</li><li>移动端自适应</li></ul><figure class="highlight css"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br><span class="line">89</span><br><span class="line">90</span><br><span class="line">91</span><br><span class="line">92</span><br><span class="line">93</span><br><span class="line">94</span><br><span class="line">95</span><br><span class="line">96</span><br><span class="line">97</span><br><span class="line">98</span><br><span class="line">99</span><br><span class="line">100</span><br><span class="line">101</span><br><span class="line">102</span><br><span class="line">103</span><br><span class="line">104</span><br><span class="line">105</span><br><span class="line">106</span><br><span class="line">107</span><br><span class="line">108</span><br><span class="line">109</span><br><span class="line">110</span><br><span class="line">111</span><br><span class="line">112</span><br><span class="line">113</span><br><span class="line">114</span><br><span class="line">115</span><br><span class="line">116</span><br><span class="line">117</span><br><span class="line">118</span><br><span class="line">119</span><br><span class="line">120</span><br><span class="line">121</span><br><span class="line">122</span><br><span class="line">123</span><br><span class="line">124</span><br><span class="line">125</span><br><span class="line">126</span><br><span class="line">127</span><br><span class="line">128</span><br><span class="line">129</span><br><span class="line">130</span><br><span class="line">131</span><br><span class="line">132</span><br><span class="line">133</span><br><span class="line">134</span><br><span class="line">135</span><br><span class="line">136</span><br><span class="line">137</span><br><span class="line">138</span><br><span class="line">139</span><br><span class="line">140</span><br><span class="line">141</span><br><span class="line">142</span><br><span class="line">143</span><br><span class="line">144</span><br><span class="line">145</span><br><span class="line">146</span><br><span class="line">147</span><br><span class="line">148</span><br><span class="line">149</span><br><span class="line">150</span><br><span class="line">151</span><br><span class="line">152</span><br><span class="line">153</span><br><span class="line">154</span><br><span class="line">155</span><br><span class="line">156</span><br><span class="line">157</span><br><span class="line">158</span><br><span class="line">159</span><br><span class="line">160</span><br><span class="line">161</span><br><span class="line">162</span><br><span class="line">163</span><br><span class="line">164</span><br><span class="line">165</span><br><span class="line">166</span><br><span class="line">167</span><br><span class="line">168</span><br><span class="line">169</span><br><span class="line">170</span><br><span class="line">171</span><br><span class="line">172</span><br><span class="line">173</span><br><span class="line">174</span><br><span class="line">175</span><br><span class="line">176</span><br><span class="line">177</span><br><span class="line">178</span><br><span class="line">179</span><br><span class="line">180</span><br><span class="line">181</span><br><span class="line">182</span><br><span class="line">183</span><br><span class="line">184</span><br><span class="line">185</span><br><span class="line">186</span><br><span class="line">187</span><br><span class="line">188</span><br><span class="line">189</span><br><span class="line">190</span><br><span class="line">191</span><br><span class="line">192</span><br><span class="line">193</span><br><span class="line">194</span><br><span class="line">195</span><br><span class="line">196</span><br><span class="line">197</span><br><span class="line">198</span><br><span class="line">199</span><br><span class="line">200</span><br><span class="line">201</span><br><span class="line">202</span><br><span class="line">203</span><br><span class="line">204</span><br><span class="line">205</span><br><span class="line">206</span><br><span class="line">207</span><br><span class="line">208</span><br><span class="line">209</span><br><span class="line">210</span><br><span class="line">211</span><br><span class="line">212</span><br><span class="line">213</span><br><span class="line">214</span><br><span class="line">215</span><br><span class="line">216</span><br><span class="line">217</span><br><span class="line">218</span><br><span class="line">219</span><br><span class="line">220</span><br><span class="line">221</span><br><span class="line">222</span><br><span class="line">223</span><br><span class="line">224</span><br><span class="line">225</span><br><span class="line">226</span><br><span class="line">227</span><br><span class="line">228</span><br><span class="line">229</span><br><span class="line">230</span><br><span class="line">231</span><br><span class="line">232</span><br><span class="line">233</span><br><span class="line">234</span><br><span class="line">235</span><br><span class="line">236</span><br><span class="line">237</span><br><span class="line">238</span><br><span class="line">239</span><br><span class="line">240</span><br><span class="line">241</span><br><span class="line">242</span><br><span class="line">243</span><br><span class="line">244</span><br><span class="line">245</span><br><span class="line">246</span><br><span class="line">247</span><br><span class="line">248</span><br><span class="line">249</span><br><span class="line">250</span><br><span class="line">251</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">/* ============================================</span></span><br><span class="line"><span class="comment">   AI 摘要样式 - 固定配色方案</span></span><br><span class="line"><span class="comment">   ============================================ */</span></span><br><span class="line"></span><br><span class="line"><span class="comment">/* ========== 白天模式 ========== */</span></span><br><span class="line"><span class="selector-class">.anzhiyu-ai-summary</span> &#123;</span><br><span class="line">  <span class="attribute">position</span>: relative;</span><br><span class="line">  <span class="attribute">margin</span>: <span class="number">20px</span> <span class="number">0</span>;</span><br><span class="line">  <span class="attribute">padding</span>: <span class="number">0</span>;</span><br><span class="line">  <span class="attribute">border-radius</span>: <span class="number">12px</span>;</span><br><span class="line">  <span class="attribute">background</span>: <span class="number">#f5f5f7</span>;</span><br><span class="line">  <span class="attribute">color</span>: <span class="number">#1d1d1f</span>;</span><br><span class="line">  <span class="attribute">overflow</span>: hidden;</span><br><span class="line">  <span class="attribute">font-family</span>: -apple-system, BlinkMacSystemFont, <span class="string">&quot;Segoe UI&quot;</span>, Roboto, <span class="string">&quot;Helvetica Neue&quot;</span>, Arial, sans-serif;</span><br><span class="line">  <span class="attribute">box-shadow</span>: <span class="number">0</span> <span class="number">4px</span> <span class="number">12px</span> <span class="built_in">rgba</span>(<span class="number">0</span>, <span class="number">0</span>, <span class="number">0</span>, <span class="number">0.06</span>);</span><br><span class="line">  <span class="attribute">border</span>: <span class="number">1px</span> solid <span class="number">#e8e8ed</span>;</span><br><span class="line">  <span class="attribute">transition</span>: all <span class="number">0.3s</span> ease;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="selector-class">.anzhiyu-ai-summary</span><span class="selector-pseudo">:hover</span> &#123;</span><br><span class="line">  <span class="attribute">transform</span>: <span class="built_in">translateY</span>(-<span class="number">2px</span>);</span><br><span class="line">  <span class="attribute">box-shadow</span>: <span class="number">0</span> <span class="number">8px</span> <span class="number">24px</span> <span class="built_in">rgba</span>(<span class="number">0</span>, <span class="number">0</span>, <span class="number">0</span>, <span class="number">0.1</span>);</span><br><span class="line">  <span class="attribute">border-color</span>: <span class="number">#d2d2d7</span>;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">/* 内容区域 */</span></span><br><span class="line"><span class="selector-class">.ai-content</span> &#123;</span><br><span class="line">  <span class="attribute">padding</span>: <span class="number">20px</span> <span class="number">24px</span>;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">/* 头部区域 */</span></span><br><span class="line"><span class="selector-class">.ai-header</span> &#123;</span><br><span class="line">  <span class="attribute">display</span>: flex;</span><br><span class="line">  <span class="attribute">align-items</span>: center;</span><br><span class="line">  <span class="attribute">gap</span>: <span class="number">10px</span>;</span><br><span class="line">  <span class="attribute">margin-bottom</span>: <span class="number">16px</span>;</span><br><span class="line">  <span class="attribute">padding-bottom</span>: <span class="number">12px</span>;</span><br><span class="line">  <span class="attribute">border-bottom</span>: <span class="number">1px</span> solid <span class="number">#e8e8ed</span>;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">/* 机器人图标 */</span></span><br><span class="line"><span class="selector-class">.ai-robot</span> &#123;</span><br><span class="line">  <span class="attribute">flex-shrink</span>: <span class="number">0</span>;</span><br><span class="line">  <span class="attribute">vertical-align</span>: middle;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">/* 左侧标签 - AI 摘要 */</span></span><br><span class="line"><span class="selector-class">.ai-titles</span> &#123;</span><br><span class="line">  <span class="attribute">flex-shrink</span>: <span class="number">0</span>;</span><br><span class="line">  <span class="attribute">font-size</span>: <span class="number">16px</span>;</span><br><span class="line">  <span class="attribute">font-weight</span>: <span class="number">800</span>;</span><br><span class="line">  <span class="attribute">color</span>: <span class="number">#1d1d1f</span>;</span><br><span class="line">  <span class="attribute">letter-spacing</span>: <span class="number">0.5px</span>;</span><br><span class="line">  <span class="attribute">white-space</span>: nowrap;</span><br><span class="line">  <span class="attribute">background</span>: none;</span><br><span class="line">  <span class="attribute">padding</span>: <span class="number">0</span>;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">/* 右侧标签 - Eucalyptus AGENT */</span></span><br><span class="line"><span class="selector-class">.ai-badge</span> &#123;</span><br><span class="line">  <span class="attribute">margin-left</span>: auto;</span><br><span class="line">  <span class="attribute">padding</span>: <span class="number">0</span>;</span><br><span class="line">  <span class="attribute">font-size</span>: <span class="number">12px</span>;</span><br><span class="line">  <span class="attribute">font-weight</span>: <span class="number">500</span>;</span><br><span class="line">  <span class="attribute">color</span>: <span class="number">#86868b</span>;</span><br><span class="line">  <span class="attribute">letter-spacing</span>: <span class="number">0.8px</span>;</span><br><span class="line">  <span class="attribute">white-space</span>: nowrap;</span><br><span class="line">  <span class="attribute">background</span>: none;</span><br><span class="line">  <span class="attribute">border-radius</span>: <span class="number">0</span>;</span><br><span class="line">  <span class="attribute">position</span>: relative;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">/* 小圆点装饰 */</span></span><br><span class="line"><span class="selector-class">.ai-badge</span><span class="selector-pseudo">::before</span> &#123;</span><br><span class="line">  <span class="attribute">content</span>: <span class="string">&#x27;&#x27;</span>;</span><br><span class="line">  <span class="attribute">display</span>: inline-block;</span><br><span class="line">  <span class="attribute">width</span>: <span class="number">6px</span>;</span><br><span class="line">  <span class="attribute">height</span>: <span class="number">6px</span>;</span><br><span class="line">  <span class="attribute">border-radius</span>: <span class="number">50%</span>;</span><br><span class="line">  <span class="attribute">background</span>: <span class="number">#34c759</span>;</span><br><span class="line">  <span class="attribute">margin-right</span>: <span class="number">6px</span>;</span><br><span class="line">  <span class="attribute">vertical-align</span>: middle;</span><br><span class="line">  <span class="attribute">animation</span>: pulse <span class="number">2s</span> ease-in-out infinite;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">@keyframes</span> pulse &#123;</span><br><span class="line">  <span class="number">0%</span>, <span class="number">100%</span> &#123; <span class="attribute">opacity</span>: <span class="number">1</span>; <span class="attribute">transform</span>: <span class="built_in">scale</span>(<span class="number">1</span>); &#125;</span><br><span class="line">  <span class="number">50%</span> &#123; <span class="attribute">opacity</span>: <span class="number">0.6</span>; <span class="attribute">transform</span>: <span class="built_in">scale</span>(<span class="number">0.8</span>); &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">/* 摘要文本框 */</span></span><br><span class="line"><span class="selector-class">.ai-text-box</span> &#123;</span><br><span class="line">  <span class="attribute">padding</span>: <span class="number">16px</span> <span class="number">20px</span>;</span><br><span class="line">  <span class="attribute">background</span>: <span class="number">#ffffff</span>;</span><br><span class="line">  <span class="attribute">border</span>: <span class="number">1px</span> solid <span class="number">#e8e8ed</span>;</span><br><span class="line">  <span class="attribute">border-radius</span>: <span class="number">10px</span>;</span><br><span class="line">  <span class="attribute">font-size</span>: <span class="number">15px</span>;</span><br><span class="line">  <span class="attribute">line-height</span>: <span class="number">1.8</span>;</span><br><span class="line">  <span class="attribute">color</span>: <span class="number">#3a3a3c</span>;</span><br><span class="line">  <span class="attribute">text-align</span>: justify;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="selector-class">.ai-text-box</span> <span class="selector-tag">p</span> &#123;</span><br><span class="line">  <span class="attribute">margin</span>: <span class="number">0</span>;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">/* ============================================</span></span><br><span class="line"><span class="comment">   加载状态</span></span><br><span class="line"><span class="comment">   ============================================ */</span></span><br><span class="line"><span class="selector-class">.ai-loading</span> &#123;</span><br><span class="line">  <span class="attribute">display</span>: flex;</span><br><span class="line">  <span class="attribute">align-items</span>: center;</span><br><span class="line">  <span class="attribute">gap</span>: <span class="number">12px</span>;</span><br><span class="line">  <span class="attribute">padding</span>: <span class="number">10px</span> <span class="number">0</span>;</span><br><span class="line">  <span class="attribute">color</span>: <span class="number">#1d1d1f</span>;</span><br><span class="line">  <span class="attribute">font-size</span>: <span class="number">14px</span>;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="selector-class">.ai-loading</span> <span class="selector-tag">i</span> &#123;</span><br><span class="line">  <span class="attribute">animation</span>: spin <span class="number">1s</span> linear infinite;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">@keyframes</span> spin &#123;</span><br><span class="line">  <span class="selector-tag">from</span> &#123; <span class="attribute">transform</span>: <span class="built_in">rotate</span>(<span class="number">0deg</span>); &#125;</span><br><span class="line">  <span class="selector-tag">to</span> &#123; <span class="attribute">transform</span>: <span class="built_in">rotate</span>(<span class="number">360deg</span>); &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">/* ============================================</span></span><br><span class="line"><span class="comment">   暗色模式</span></span><br><span class="line"><span class="comment">   ============================================ */</span></span><br><span class="line"><span class="selector-attr">[data-theme=<span class="string">&quot;dark&quot;</span>]</span> <span class="selector-class">.anzhiyu-ai-summary</span> &#123;</span><br><span class="line">  <span class="attribute">background</span>: <span class="number">#1c1c1e</span>;</span><br><span class="line">  <span class="attribute">color</span>: <span class="number">#ffffff</span>;</span><br><span class="line">  <span class="attribute">border-color</span>: <span class="number">#2c2c2e</span>;</span><br><span class="line">  <span class="attribute">box-shadow</span>: <span class="number">0</span> <span class="number">4px</span> <span class="number">12px</span> <span class="built_in">rgba</span>(<span class="number">0</span>, <span class="number">0</span>, <span class="number">0</span>, <span class="number">0.3</span>);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="selector-attr">[data-theme=<span class="string">&quot;dark&quot;</span>]</span> <span class="selector-class">.anzhiyu-ai-summary</span><span class="selector-pseudo">:hover</span> &#123;</span><br><span class="line">  <span class="attribute">border-color</span>: <span class="number">#3a3a3c</span>;</span><br><span class="line">  <span class="attribute">box-shadow</span>: <span class="number">0</span> <span class="number">8px</span> <span class="number">24px</span> <span class="built_in">rgba</span>(<span class="number">0</span>, <span class="number">0</span>, <span class="number">0</span>, <span class="number">0.4</span>);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="selector-attr">[data-theme=<span class="string">&quot;dark&quot;</span>]</span> <span class="selector-class">.ai-header</span> &#123;</span><br><span class="line">  <span class="attribute">border-bottom-color</span>: <span class="number">#2c2c2e</span>;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="selector-attr">[data-theme=<span class="string">&quot;dark&quot;</span>]</span> <span class="selector-class">.ai-titles</span> &#123;</span><br><span class="line">  <span class="attribute">color</span>: <span class="number">#ffffff</span>;</span><br><span class="line">  <span class="attribute">background</span>: none;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="selector-attr">[data-theme=<span class="string">&quot;dark&quot;</span>]</span> <span class="selector-class">.ai-badge</span> &#123;</span><br><span class="line">  <span class="attribute">color</span>: <span class="number">#8e8e93</span>;</span><br><span class="line">  <span class="attribute">background</span>: none;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="selector-attr">[data-theme=<span class="string">&quot;dark&quot;</span>]</span> <span class="selector-class">.ai-badge</span><span class="selector-pseudo">::before</span> &#123;</span><br><span class="line">  <span class="attribute">background</span>: <span class="number">#30d158</span>;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="selector-attr">[data-theme=<span class="string">&quot;dark&quot;</span>]</span> <span class="selector-class">.ai-text-box</span> &#123;</span><br><span class="line">  <span class="attribute">background</span>: <span class="number">#2c2c2e</span>;</span><br><span class="line">  <span class="attribute">border-color</span>: <span class="number">#3a3a3c</span>;</span><br><span class="line">  <span class="attribute">color</span>: <span class="number">#ffffff</span>;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="selector-attr">[data-theme=<span class="string">&quot;dark&quot;</span>]</span> <span class="selector-class">.ai-loading</span> &#123;</span><br><span class="line">  <span class="attribute">color</span>: <span class="number">#ffffff</span>;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="selector-class">.ai-loading-inline</span> &#123;</span><br><span class="line">  <span class="attribute">display</span>: flex;</span><br><span class="line">  <span class="attribute">align-items</span>: center;</span><br><span class="line">  <span class="attribute">gap</span>: <span class="number">8px</span>;</span><br><span class="line">  <span class="attribute">color</span>: <span class="number">#86868b</span>;</span><br><span class="line">  <span class="attribute">font-size</span>: <span class="number">14px</span>;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="selector-class">.ai-loading-inline</span> <span class="selector-tag">i</span> &#123;</span><br><span class="line">  <span class="attribute">animation</span>: spin <span class="number">1s</span> linear infinite;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="selector-attr">[data-theme=<span class="string">&quot;dark&quot;</span>]</span> <span class="selector-class">.ai-loading-inline</span> &#123;</span><br><span class="line">  <span class="attribute">color</span>: <span class="number">#8e8e93</span>;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="comment">/* AI 摘要加载动画 */</span></span><br><span class="line"><span class="selector-class">.ai-loading-inline</span> &#123;</span><br><span class="line">  <span class="attribute">display</span>: inline-flex;</span><br><span class="line">  <span class="attribute">align-items</span>: center;</span><br><span class="line">  <span class="attribute">gap</span>: <span class="number">8px</span>;</span><br><span class="line">  <span class="attribute">color</span>: <span class="number">#999</span>;</span><br><span class="line">  <span class="attribute">font-size</span>: <span class="number">14px</span>;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="selector-class">.ai-loading-svg</span> &#123;</span><br><span class="line">  <span class="attribute">animation</span>: ai-loading-rotate <span class="number">1s</span> linear infinite;</span><br><span class="line">  <span class="attribute">color</span>: <span class="number">#999</span>;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">@keyframes</span> ai-loading-rotate &#123;</span><br><span class="line">  <span class="selector-tag">from</span> &#123; <span class="attribute">transform</span>: <span class="built_in">rotate</span>(<span class="number">0deg</span>); &#125;</span><br><span class="line">  <span class="selector-tag">to</span> &#123; <span class="attribute">transform</span>: <span class="built_in">rotate</span>(<span class="number">360deg</span>); &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">/* 文字后面的点动画 */</span></span><br><span class="line"><span class="selector-class">.ai-loading-dots</span><span class="selector-pseudo">::after</span> &#123;</span><br><span class="line">  <span class="attribute">content</span>: <span class="string">&#x27;&#x27;</span>;</span><br><span class="line">  <span class="attribute">animation</span>: ai-loading-dots <span class="number">1.5s</span> <span class="built_in">steps</span>(<span class="number">4</span>, end) infinite;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">@keyframes</span> ai-loading-dots &#123;</span><br><span class="line">  <span class="number">0%</span> &#123; <span class="attribute">content</span>: <span class="string">&#x27;&#x27;</span>; &#125;</span><br><span class="line">  <span class="number">25%</span> &#123; <span class="attribute">content</span>: <span class="string">&#x27;.&#x27;</span>; &#125;</span><br><span class="line">  <span class="number">50%</span> &#123; <span class="attribute">content</span>: <span class="string">&#x27;..&#x27;</span>; &#125;</span><br><span class="line">  <span class="number">75%</span> &#123; <span class="attribute">content</span>: <span class="string">&#x27;...&#x27;</span>; &#125;</span><br><span class="line">  <span class="number">100%</span> &#123; <span class="attribute">content</span>: <span class="string">&#x27;&#x27;</span>; &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="selector-class">.ai-header</span> <span class="selector-class">.icon-bilibili</span> &#123;</span><br><span class="line">  <span class="attribute">font-size</span>: <span class="number">20px</span>;</span><br><span class="line">  <span class="attribute">margin-right</span>: <span class="number">8px</span>;</span><br><span class="line">  <span class="attribute">vertical-align</span>: middle;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">/* ============================================</span></span><br><span class="line"><span class="comment">   移动端适配</span></span><br><span class="line"><span class="comment">   ============================================ */</span></span><br><span class="line"><span class="keyword">@media</span> (<span class="attribute">max-width</span>: <span class="number">768px</span>) &#123;</span><br><span class="line">  <span class="selector-class">.anzhiyu-ai-summary</span> &#123;</span><br><span class="line">    <span class="attribute">margin</span>: <span class="number">16px</span> -<span class="number">16px</span>;</span><br><span class="line">    <span class="attribute">border-radius</span>: <span class="number">0</span>;</span><br><span class="line">    <span class="attribute">border-left</span>: none;</span><br><span class="line">    <span class="attribute">border-right</span>: none;</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  <span class="selector-class">.ai-content</span> &#123;</span><br><span class="line">    <span class="attribute">padding</span>: <span class="number">16px</span> <span class="number">20px</span>;</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  <span class="selector-class">.ai-badge</span> &#123;</span><br><span class="line">    <span class="attribute">display</span>: none;</span><br><span class="line">  &#125;</span><br><span class="line">  </span><br><span class="line">  <span class="selector-class">.ai-text-box</span> &#123;</span><br><span class="line">    <span class="attribute">padding</span>: <span class="number">12px</span> <span class="number">14px</span>;</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"></span><br></pre></td></tr></table></figure><h3 id="3-引入资源文件"><a href="#3-引入资源文件" class="headerlink" title="3. 引入资源文件"></a>3. 引入资源文件</h3><p>在主题布局中引入 CSS 和 JS：</p><figure class="highlight yaml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="attr">inject:</span></span><br><span class="line">  <span class="attr">head:</span></span><br><span class="line">    <span class="comment"># 自定义css</span></span><br><span class="line">    <span class="bullet">-</span> <span class="string">&lt;link</span> <span class="string">rel=&quot;stylesheet&quot;</span> <span class="string">href=&quot;/cdn/css/ai-summary.css&quot;&gt;</span></span><br><span class="line"> </span><br><span class="line">  <span class="attr">bottom:</span></span><br><span class="line">    <span class="comment"># 自定义js</span></span><br><span class="line">    <span class="bullet">-</span> <span class="string">&lt;script</span> <span class="string">defer</span> <span class="string">src=&quot;/cdn/js/ai-summary.js&quot;&gt;&lt;/script&gt;</span></span><br></pre></td></tr></table></figure>]]></content:encoded>
      
      
      <category domain="https://blog.eucalyptus.cc/categories/AI/">AI</category>
      
      
      <category domain="https://blog.eucalyptus.cc/tags/Ollama/">Ollama</category>
      
      
      <comments>https://blog.eucalyptus.cc/2026/05/16/%E4%BD%BF%E7%94%A8-Ollama-%E4%B8%BA-Hexo-%E5%8D%9A%E5%AE%A2%E9%83%A8%E7%BD%B2-AI-%E6%96%87%E7%AB%A0%E6%91%98%E8%A6%81/#disqus_thread</comments>
      
    </item>
    
    <item>
      <title>anzhiyu主题新添文章卡片样式</title>
      <link>https://blog.eucalyptus.cc/2026/04/13/anzhiyu%E4%B8%BB%E9%A2%98%E6%96%B0%E6%B7%BB%E6%96%87%E7%AB%A0%E5%8D%A1%E7%89%87%E6%A0%B7%E5%BC%8F/</link>
      <guid>https://blog.eucalyptus.cc/2026/04/13/anzhiyu%E4%B8%BB%E9%A2%98%E6%96%B0%E6%B7%BB%E6%96%87%E7%AB%A0%E5%8D%A1%E7%89%87%E6%A0%B7%E5%BC%8F/</guid>
      <pubDate>Mon, 13 Apr 2026 06:19:46 GMT</pubDate>
      
        
        
          
          
      <description>&lt;h2 id=&quot;前言&quot;&gt;&lt;a href=&quot;#前言&quot; class=&quot;headerlink&quot; title=&quot;前言&quot;&gt;&lt;/a&gt;前言&lt;/h2&gt;&lt;p&gt;这份教程，记录了我为 AnZhiYu</description>
          
        
      
      
      
      <content:encoded><![CDATA[<h2 id="前言"><a href="#前言" class="headerlink" title="前言"></a>前言</h2><p>这份教程，记录了我为 AnZhiYu 主题添加横向卡片样式的全部过程。代码不多，但每一处都仔细调整过：斜切的视觉平衡、移动端的适配、分页后的稳定性……甚至强迫自己解决了偶数页布局错乱的 bug。</p><p>代码不是束缚，是自由的工具。愿你的博客，也能长出属于自己的姿态。</p><h2 id="正文"><a href="#正文" class="headerlink" title="正文"></a>正文</h2><p>文件路径分布</p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line">blog/                          # Hexo 博客根目录</span><br><span class="line">├── themes/</span><br><span class="line">│   └── anzhiyu/                    # AnZhiYu 主题目录</span><br><span class="line">│       ├── layout/                 # 布局模板目录</span><br><span class="line">│       │   ├── includes/           # 包含组件目录</span><br><span class="line">│       │   │   └── mixins/         # mixins 组件目录</span><br><span class="line">│       │   │       └── post-ui-horizontal.pug    # ← 横向卡片 Pug 模板</span><br><span class="line">│       │   └── index.pug           # ← 首页布局文件（调用横向卡片）</span><br><span class="line">│       └── ...                     # 其他主题文件</span><br><span class="line">├── source/                         # 源文件目录（会被复制到 public）</span><br><span class="line">│   └── cdn/                        # CDN 资源目录</span><br><span class="line">│       └── css/</span><br><span class="line">│           └── horizontal-card.css # ← 横向卡片样式文件（你提供的 CSS）</span><br><span class="line">└── ...                             # 其他博客文件</span><br></pre></td></tr></table></figure><p>新建post-ui-horizontal.pug</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br><span class="line">89</span><br><span class="line">90</span><br><span class="line">91</span><br><span class="line">92</span><br><span class="line">93</span><br><span class="line">94</span><br><span class="line">95</span><br><span class="line">96</span><br><span class="line">97</span><br><span class="line">98</span><br><span class="line">99</span><br><span class="line">100</span><br><span class="line">101</span><br><span class="line">102</span><br><span class="line">103</span><br><span class="line">104</span><br><span class="line">105</span><br><span class="line">106</span><br><span class="line">107</span><br><span class="line">108</span><br><span class="line">109</span><br><span class="line">110</span><br><span class="line">111</span><br><span class="line">112</span><br><span class="line">113</span><br><span class="line">114</span><br><span class="line">115</span><br><span class="line">116</span><br><span class="line">117</span><br><span class="line">118</span><br><span class="line">119</span><br><span class="line">120</span><br><span class="line">121</span><br><span class="line">122</span><br><span class="line">123</span><br><span class="line">124</span><br><span class="line">125</span><br><span class="line">126</span><br><span class="line">127</span><br><span class="line">128</span><br><span class="line">129</span><br><span class="line">130</span><br><span class="line">131</span><br><span class="line">132</span><br><span class="line">133</span><br><span class="line">134</span><br><span class="line">135</span><br><span class="line">136</span><br><span class="line">137</span><br><span class="line">138</span><br><span class="line">139</span><br></pre></td><td class="code"><pre><span class="line">mixin <span class="title function_">postUIHorizontal</span>()</span><br><span class="line">  - <span class="keyword">let</span> maxDate = <span class="number">0</span></span><br><span class="line">  each item <span class="keyword">in</span> site.<span class="property">posts</span>.<span class="property">data</span></span><br><span class="line">    <span class="keyword">if</span> item.<span class="property">date</span> &gt; maxDate</span><br><span class="line">      - maxDate = item.<span class="property">date</span></span><br><span class="line"></span><br><span class="line">  .<span class="property">horizontal</span>-post-list</span><br><span class="line">    each article, index <span class="keyword">in</span> page.<span class="property">posts</span>.<span class="property">data</span></span><br><span class="line">      - <span class="keyword">let</span> link = article.<span class="property">link</span> || article.<span class="property">path</span></span><br><span class="line">      - <span class="keyword">let</span> title = article.<span class="property">title</span> || <span class="title function_">_p</span>(<span class="string">&#x27;no_title&#x27;</span>)</span><br><span class="line">      - <span class="keyword">let</span> post_cover = article.<span class="property">cover</span></span><br><span class="line">      - <span class="keyword">let</span> desc = article.<span class="property">description</span> || article.<span class="property">excerpt</span> || <span class="string">&#x27;&#x27;</span></span><br><span class="line">      - <span class="keyword">let</span> category = article.<span class="property">categories</span>.<span class="property">data</span>[<span class="number">0</span>]</span><br><span class="line">      - <span class="keyword">let</span> dateStr = <span class="title function_">date</span>(article.<span class="property">date</span>, <span class="string">&#x27;MM-DD&#x27;</span>)</span><br><span class="line">      - <span class="keyword">let</span> wc = <span class="title function_">wordcount</span>(article.<span class="property">content</span>)</span><br><span class="line">      - <span class="keyword">const</span> new_post = <span class="title function_">is_current</span>(<span class="string">&#x27;/&#x27;</span>) &amp;&amp; (maxDate === article.<span class="property">date</span>)</span><br><span class="line">      <span class="comment">// 判断左右位置：偶数左边，奇数右边</span></span><br><span class="line">      - <span class="keyword">let</span> isLeft = index % <span class="number">2</span> === <span class="number">0</span></span><br><span class="line">      </span><br><span class="line">      article.<span class="property">recent</span>-post-item.<span class="property">horizontal</span>-<span class="title function_">card</span>(<span class="keyword">class</span>=isLeft ? <span class="string">&#x27;image-right&#x27;</span> : <span class="string">&#x27;image-left&#x27;</span> onclick=<span class="string">`pjax.loadUrl(&#x27;<span class="subst">$&#123;url_for(link)&#125;</span>&#x27;)`</span>)</span><br><span class="line">        <span class="comment">// 图片在左（奇数卡片）</span></span><br><span class="line">        <span class="keyword">if</span> !isLeft &amp;&amp; post_cover &amp;&amp; theme.<span class="property">cover</span>.<span class="property">index_enable</span></span><br><span class="line">          .<span class="property">post</span>-cover</span><br><span class="line">            <span class="title function_">a</span>(href=<span class="title function_">url_for</span>(link) title=title)</span><br><span class="line">              img.<span class="title function_">post_bg</span>(src=<span class="title function_">url_for</span>(post_cover) onerror=<span class="string">`this.onerror=null;this.src=&#x27;`</span>+ <span class="title function_">url_for</span>(theme.<span class="property">error_img</span>.<span class="property">post_page</span>) + <span class="string">`&#x27;`</span> alt=title loading=<span class="string">&quot;lazy&quot;</span>)</span><br><span class="line">        </span><br><span class="line">        <span class="comment">// 无图片占位（奇数卡片）</span></span><br><span class="line">        <span class="keyword">if</span> !isLeft &amp;&amp; !(post_cover &amp;&amp; theme.<span class="property">cover</span>.<span class="property">index_enable</span>)</span><br><span class="line">          .<span class="property">post</span>-cover.<span class="property">no</span>-image</span><br><span class="line">            <span class="title function_">a</span>(href=<span class="title function_">url_for</span>(link) title=title)</span><br><span class="line">              .<span class="property">cover</span>-placeholder</span><br><span class="line">        </span><br><span class="line">        <span class="comment">// 内容区</span></span><br><span class="line">        .<span class="property">post</span>-content</span><br><span class="line">          .<span class="property">post</span>-content-top</span><br><span class="line">            .<span class="property">post</span>-tips</span><br><span class="line">              <span class="keyword">if</span> (<span class="title function_">is_home</span>() &amp;&amp; (article.<span class="property">top</span> || article.<span class="property">sticky</span> &gt; <span class="number">0</span>))</span><br><span class="line">                span.<span class="property">article</span>-meta.<span class="property">sticky</span>-warp</span><br><span class="line">                  i.<span class="property">anzhiyufont</span>.<span class="property">anzhiyu</span>-icon-thumbtack.<span class="property">sticky</span></span><br><span class="line">                  span.<span class="property">sticky</span>= <span class="title function_">_p</span>(<span class="string">&#x27;sticky&#x27;</span>)</span><br><span class="line">            </span><br><span class="line">            a.<span class="property">post</span>-<span class="title function_">title</span>(href=<span class="title function_">url_for</span>(link) title=title)= title</span><br><span class="line"></span><br><span class="line">          .<span class="property">post</span>-meta-wrap</span><br><span class="line">            <span class="keyword">if</span> (theme.<span class="property">post_meta</span>.<span class="property">page</span>.<span class="property">date_type</span>)</span><br><span class="line">              span.<span class="property">post</span>-meta-date</span><br><span class="line">                <span class="keyword">if</span> (theme.<span class="property">post_meta</span>.<span class="property">page</span>.<span class="property">date_type</span> === <span class="string">&#x27;both&#x27;</span>)</span><br><span class="line">                  i.<span class="property">anzhiyufont</span>.<span class="property">anzhiyu</span>-icon-calendar-<span class="title function_">alt</span>(style=<span class="string">`<span class="subst">$&#123;theme.post_meta.page.date_format===<span class="string">&quot;simple&quot;</span> ? <span class="string">&quot;display:none&quot;</span>:<span class="string">&quot;&quot;</span>&#125;</span>`</span>)</span><br><span class="line">                  span.<span class="property">article</span>-meta-label=<span class="title function_">_p</span>(<span class="string">&#x27;post.created&#x27;</span>)</span><br><span class="line">                  time.<span class="property">post</span>-meta-date-<span class="title function_">created</span>(datetime=<span class="title function_">date_xml</span>(article.<span class="property">date</span>) title=<span class="title function_">_p</span>(<span class="string">&#x27;post.created&#x27;</span>) + <span class="string">&#x27; &#x27;</span> + <span class="title function_">full_date</span>(article.<span class="property">date</span>) time=<span class="title function_">full_date</span>(article.<span class="property">date</span>))=<span class="title function_">date</span>(article.<span class="property">date</span>, config.<span class="property">date_format</span>)</span><br><span class="line">                  span.<span class="property">article</span>-meta-separator</span><br><span class="line">                  i.<span class="property">anzhiyufont</span>.<span class="property">anzhiyu</span>-icon-<span class="title function_">history</span>(style=<span class="string">`font-size: 15px; <span class="subst">$&#123;theme.post_meta.page.date_format===<span class="string">&quot;simple&quot;</span> ? <span class="string">&quot;display:none&quot;</span>:<span class="string">&quot;&quot;</span>&#125;</span>`</span>)</span><br><span class="line">                  span.<span class="property">article</span>-meta-label=<span class="title function_">_p</span>(<span class="string">&#x27;post.updated&#x27;</span>)</span><br><span class="line">                  time.<span class="property">post</span>-meta-date-<span class="title function_">updated</span>(datetime=<span class="title function_">date_xml</span>(article.<span class="property">updated</span>) title=<span class="title function_">_p</span>(<span class="string">&#x27;post.updated&#x27;</span>) + <span class="string">&#x27; &#x27;</span> + <span class="title function_">full_date</span>(article.<span class="property">updated</span>) time=<span class="title function_">full_date</span>(article.<span class="property">updated</span>))=<span class="title function_">date</span>(article.<span class="property">updated</span>, config.<span class="property">date_format</span>)</span><br><span class="line">                <span class="keyword">else</span></span><br><span class="line">                  - <span class="keyword">let</span> data_type_updated = theme.<span class="property">post_meta</span>.<span class="property">page</span>.<span class="property">date_type</span> === <span class="string">&#x27;updated&#x27;</span></span><br><span class="line">                  - <span class="keyword">let</span> date_type = data_type_updated ? <span class="string">&#x27;updated&#x27;</span> : <span class="string">&#x27;date&#x27;</span></span><br><span class="line">                  - <span class="keyword">let</span> date_type_other = data_type_updated ? <span class="string">&#x27;date&#x27;</span> : <span class="string">&#x27;updated&#x27;</span></span><br><span class="line">                  - <span class="keyword">let</span> date_icon = data_type_updated ? <span class="string">&#x27;anzhiyu-icon-history&#x27;</span> :<span class="string">&#x27;anzhiyu-icon-calendar-days&#x27;</span></span><br><span class="line">                  - <span class="keyword">let</span> date_title = data_type_updated ? <span class="title function_">_p</span>(<span class="string">&#x27;post.updated&#x27;</span>) : <span class="title function_">_p</span>(<span class="string">&#x27;post.created&#x27;</span>)</span><br><span class="line">                  - <span class="keyword">let</span> date_title_other = data_type_updated ? <span class="title function_">_p</span>(<span class="string">&#x27;post.created&#x27;</span>) : <span class="title function_">_p</span>(<span class="string">&#x27;post.updated&#x27;</span>)</span><br><span class="line">                  i.<span class="title function_">anzhiyufont</span>(<span class="keyword">class</span>=date_icon style=<span class="string">`font-size: 15px; <span class="subst">$&#123;theme.post_meta.page.date_format===<span class="string">&quot;simple&quot;</span> ? <span class="string">&quot;display:none&quot;</span>:<span class="string">&quot;&quot;</span>&#125;</span>`</span>)</span><br><span class="line">                  span.<span class="property">article</span>-meta-label=date_title</span><br><span class="line">                  <span class="title function_">time</span>(datetime=<span class="title function_">date_xml</span>(article[date_type]) title=date_title + <span class="string">&#x27; &#x27;</span> + <span class="title function_">full_date</span>(article[date_type])  time=<span class="title function_">full_date</span>(article[date_type]))=<span class="title function_">date</span>(article[date_type], config.<span class="property">date_format</span>)</span><br><span class="line">                  <span class="title function_">time</span>(datetime=<span class="title function_">date_xml</span>(article[date_type_other]), <span class="keyword">class</span>=<span class="string">&quot;time_hidden&quot;</span>, title=date_title_other + <span class="string">&#x27; &#x27;</span> + <span class="title function_">full_date</span>(article[date_type_other]) time=<span class="title function_">full_date</span>(article[date_type_other]))=<span class="title function_">date</span>(article[date_type_other], config.<span class="property">date_format</span>)</span><br><span class="line">            </span><br><span class="line">            <span class="keyword">if</span> (theme.<span class="property">post_meta</span>.<span class="property">page</span>.<span class="property">tags</span> &amp;&amp; article.<span class="property">tags</span>.<span class="property">data</span>.<span class="property">length</span> &gt; <span class="number">0</span>)</span><br><span class="line">              span.<span class="property">article</span>-meta.<span class="property">tags</span></span><br><span class="line">                each item, index <span class="keyword">in</span> article.<span class="property">tags</span>.<span class="property">data</span></span><br><span class="line">                  <span class="title function_">a</span>(href=<span class="title function_">url_for</span>(item.<span class="property">path</span>) event.<span class="property">cancelbubble</span> onclick=<span class="string">&quot;window.event.cancelBubble=true;&quot;</span>).<span class="property">article</span>-meta__tags</span><br><span class="line">                    span </span><br><span class="line">                      i.<span class="property">anzhiyufont</span>.<span class="property">anzhiyu</span>-icon-hashtag</span><br><span class="line">                      =item.<span class="property">name</span></span><br><span class="line">            </span><br><span class="line">            mixin countBlockHorizontal</span><br><span class="line">              - needLoadCountJs = <span class="literal">true</span></span><br><span class="line">              span.<span class="property">article</span>-meta</span><br><span class="line">                span.<span class="property">article</span>-meta-separator</span><br><span class="line">                i.<span class="property">eucalyptus</span>.<span class="property">icon</span>-comments</span><br><span class="line">                <span class="keyword">if</span> block</span><br><span class="line">                  block</span><br><span class="line">                span.<span class="property">article</span>-meta-label= <span class="string">&#x27; &#x27;</span> + <span class="title function_">_p</span>(<span class="string">&#x27;card_post_count&#x27;</span>)</span><br><span class="line">            </span><br><span class="line">            <span class="keyword">if</span> theme.<span class="property">comments</span>.<span class="property">card_post_count</span></span><br><span class="line">              <span class="keyword">case</span> theme.<span class="property">comments</span>.<span class="property">use</span>[<span class="number">0</span>]</span><br><span class="line">                when <span class="string">&#x27;Valine&#x27;</span></span><br><span class="line">                  +countBlockHorizontal</span><br><span class="line">                    <span class="title function_">a</span>(href=<span class="title function_">url_for</span>(link) + <span class="string">&#x27;#post-comment&#x27;</span>)</span><br><span class="line">                      span.<span class="property">valine</span>-comment-<span class="title function_">count</span>(data-xid=<span class="title function_">url_for</span>(link))</span><br><span class="line">                        i.<span class="property">anzhiyufont</span>.<span class="property">anzhiyu</span>-icon-spinner.<span class="property">anzhiyu</span>-spin</span><br><span class="line">                when <span class="string">&#x27;Waline&#x27;</span></span><br><span class="line">                  +countBlockHorizontal</span><br><span class="line">                    <span class="title function_">a</span>(href=<span class="title function_">url_for</span>(link) + <span class="string">&#x27;#post-comment&#x27;</span>)</span><br><span class="line">                      span.<span class="property">waline</span>-comment-<span class="title function_">count</span>(id=<span class="title function_">url_for</span>(link))</span><br><span class="line">                        i.<span class="property">anzhiyufont</span>.<span class="property">anzhiyu</span>-icon-spinner.<span class="property">anzhiyu</span>-spin</span><br><span class="line">                when <span class="string">&#x27;Twikoo&#x27;</span></span><br><span class="line">                  +countBlockHorizontal</span><br><span class="line">                    a.<span class="property">twikoo</span>-<span class="title function_">count</span>(href=<span class="title function_">url_for</span>(link) + <span class="string">&#x27;#post-comment&#x27;</span> tabindex=<span class="string">&quot;-1&quot;</span>)</span><br><span class="line">                      i.<span class="property">anzhiyufont</span>.<span class="property">anzhiyu</span>-icon-spinner.<span class="property">anzhiyu</span>-spin</span><br><span class="line">                when <span class="string">&#x27;Artalk&#x27;</span></span><br><span class="line">                  +countBlockHorizontal</span><br><span class="line">                    <span class="title function_">a</span>(href=<span class="title function_">url_for</span>(link) + <span class="string">&#x27;#post-comment&#x27;</span>)</span><br><span class="line">                      span.<span class="property">artalk</span>-<span class="title function_">count</span>(data-page-key=<span class="title function_">url_for</span>(link))</span><br><span class="line">                        i.<span class="property">anzhiyufont</span>.<span class="property">anzhiyu</span>-icon-spinner.<span class="property">anzhiyu</span>-spin</span><br><span class="line"></span><br><span class="line">          <span class="keyword">case</span> theme.<span class="property">index_post_content</span>.<span class="property">method</span></span><br><span class="line">            when <span class="literal">false</span></span><br><span class="line">              - <span class="keyword">break</span></span><br><span class="line">            when <span class="number">1</span></span><br><span class="line">              .<span class="property">post</span>-desc!= article.<span class="property">description</span></span><br><span class="line">            when <span class="number">2</span></span><br><span class="line">              <span class="keyword">if</span> article.<span class="property">description</span></span><br><span class="line">                .<span class="property">post</span>-desc!= article.<span class="property">description</span></span><br><span class="line">              <span class="keyword">else</span></span><br><span class="line">                - <span class="keyword">const</span> content = <span class="title function_">strip_html</span>(article.<span class="property">content</span>)</span><br><span class="line">                - <span class="keyword">let</span> expert = content.<span class="title function_">substring</span>(<span class="number">0</span>, theme.<span class="property">index_post_content</span>.<span class="property">length</span>) </span><br><span class="line">                - content.<span class="property">length</span> &gt; theme.<span class="property">index_post_content</span>.<span class="property">length</span> ? expert += <span class="string">&#x27; ...&#x27;</span> : <span class="string">&#x27;&#x27;</span></span><br><span class="line">                .<span class="property">post</span>-desc!= expert</span><br><span class="line">            <span class="keyword">default</span></span><br><span class="line">              - <span class="keyword">const</span> content = <span class="title function_">strip_html</span>(article.<span class="property">content</span>)</span><br><span class="line">              - <span class="keyword">let</span> expert = content.<span class="title function_">substring</span>(<span class="number">0</span>, theme.<span class="property">index_post_content</span>.<span class="property">length</span>) </span><br><span class="line">              - content.<span class="property">length</span> &gt; theme.<span class="property">index_post_content</span>.<span class="property">length</span> ? expert += <span class="string">&#x27; ...&#x27;</span> : <span class="string">&#x27;&#x27;</span></span><br><span class="line">              .<span class="property">post</span>-desc!= expert</span><br><span class="line"></span><br><span class="line">        <span class="comment">// 图片在右（偶数卡片）</span></span><br><span class="line">        <span class="keyword">if</span> isLeft &amp;&amp; post_cover &amp;&amp; theme.<span class="property">cover</span>.<span class="property">index_enable</span></span><br><span class="line">          .<span class="property">post</span>-cover</span><br><span class="line">            <span class="title function_">a</span>(href=<span class="title function_">url_for</span>(link) title=title)</span><br><span class="line">              img.<span class="title function_">post_bg</span>(src=<span class="title function_">url_for</span>(post_cover) onerror=<span class="string">`this.onerror=null;this.src=&#x27;`</span>+ <span class="title function_">url_for</span>(theme.<span class="property">error_img</span>.<span class="property">post_page</span>) + <span class="string">`&#x27;`</span> alt=title loading=<span class="string">&quot;lazy&quot;</span>)</span><br><span class="line">        </span><br><span class="line">        <span class="comment">// 无图片占位（偶数卡片）</span></span><br><span class="line">        <span class="keyword">if</span> isLeft &amp;&amp; !(post_cover &amp;&amp; theme.<span class="property">cover</span>.<span class="property">index_enable</span>)</span><br><span class="line">          .<span class="property">post</span>-cover.<span class="property">no</span>-image</span><br><span class="line">            <span class="title function_">a</span>(href=<span class="title function_">url_for</span>(link) title=title)</span><br><span class="line">              .<span class="property">cover</span>-placeholder</span><br><span class="line"></span><br><span class="line">      <span class="keyword">if</span> theme.<span class="property">ad</span> &amp;&amp; theme.<span class="property">ad</span>.<span class="property">index</span></span><br><span class="line">        <span class="keyword">if</span> (index + <span class="number">1</span>) % <span class="number">3</span> == <span class="number">0</span></span><br><span class="line">          .<span class="property">horizontal</span>-card.<span class="property">ads</span>-wrap!=theme.<span class="property">ad</span>.<span class="property">index</span></span><br></pre></td></tr></table></figure><p>修改index.pug</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><span class="line">extends includes/layout.pug</span><br><span class="line"></span><br><span class="line">block content</span><br><span class="line">  include ./includes/mixins/post-ui.pug</span><br><span class="line">  </span><br><span class="line">  // 关键：include 必须在顶层，不能放在 if 里面</span><br><span class="line">  include ./includes/mixins/post-ui-horizontal.pug</span><br><span class="line">  </span><br><span class="line">  #recent-posts.recent-posts</span><br><span class="line">    include includes/categoryGroup.pug</span><br><span class="line">    </span><br><span class="line">    if theme.home_card_style === &#x27;horizontal&#x27;</span><br><span class="line">      +postUIHorizontal()</span><br><span class="line">    else</span><br><span class="line">      +postUI</span><br><span class="line">    </span><br><span class="line">    include includes/pagination.pug</span><br></pre></td></tr></table></figure><p>新建horizontal-card.css</p><figure class="highlight css"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br><span class="line">89</span><br><span class="line">90</span><br><span class="line">91</span><br><span class="line">92</span><br><span class="line">93</span><br><span class="line">94</span><br><span class="line">95</span><br><span class="line">96</span><br><span class="line">97</span><br><span class="line">98</span><br><span class="line">99</span><br><span class="line">100</span><br><span class="line">101</span><br><span class="line">102</span><br><span class="line">103</span><br><span class="line">104</span><br><span class="line">105</span><br><span class="line">106</span><br><span class="line">107</span><br><span class="line">108</span><br><span class="line">109</span><br><span class="line">110</span><br><span class="line">111</span><br><span class="line">112</span><br><span class="line">113</span><br><span class="line">114</span><br><span class="line">115</span><br><span class="line">116</span><br><span class="line">117</span><br><span class="line">118</span><br><span class="line">119</span><br><span class="line">120</span><br><span class="line">121</span><br><span class="line">122</span><br><span class="line">123</span><br><span class="line">124</span><br><span class="line">125</span><br><span class="line">126</span><br><span class="line">127</span><br><span class="line">128</span><br><span class="line">129</span><br><span class="line">130</span><br><span class="line">131</span><br><span class="line">132</span><br><span class="line">133</span><br><span class="line">134</span><br><span class="line">135</span><br><span class="line">136</span><br><span class="line">137</span><br><span class="line">138</span><br><span class="line">139</span><br><span class="line">140</span><br><span class="line">141</span><br><span class="line">142</span><br><span class="line">143</span><br><span class="line">144</span><br><span class="line">145</span><br><span class="line">146</span><br><span class="line">147</span><br><span class="line">148</span><br><span class="line">149</span><br><span class="line">150</span><br><span class="line">151</span><br><span class="line">152</span><br><span class="line">153</span><br><span class="line">154</span><br><span class="line">155</span><br><span class="line">156</span><br><span class="line">157</span><br><span class="line">158</span><br><span class="line">159</span><br><span class="line">160</span><br><span class="line">161</span><br><span class="line">162</span><br><span class="line">163</span><br><span class="line">164</span><br><span class="line">165</span><br><span class="line">166</span><br><span class="line">167</span><br><span class="line">168</span><br><span class="line">169</span><br><span class="line">170</span><br><span class="line">171</span><br><span class="line">172</span><br><span class="line">173</span><br><span class="line">174</span><br><span class="line">175</span><br><span class="line">176</span><br><span class="line">177</span><br><span class="line">178</span><br><span class="line">179</span><br><span class="line">180</span><br><span class="line">181</span><br><span class="line">182</span><br><span class="line">183</span><br><span class="line">184</span><br><span class="line">185</span><br><span class="line">186</span><br><span class="line">187</span><br><span class="line">188</span><br><span class="line">189</span><br><span class="line">190</span><br><span class="line">191</span><br><span class="line">192</span><br><span class="line">193</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">/* ========== 变量定义 ========== */</span></span><br><span class="line"><span class="selector-pseudo">:root</span> &#123;</span><br><span class="line">    <span class="attr">--card-bg</span>: <span class="number">#1a1a2e</span>;</span><br><span class="line">    <span class="attr">--card-bg-alt</span>: <span class="number">#16213e</span>;</span><br><span class="line">    <span class="attr">--theme-color</span>: <span class="number">#e94560</span>;</span><br><span class="line">    <span class="attr">--skew-width</span>: <span class="number">60px</span>;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">/* ========== 基础卡片样式 ========== */</span></span><br><span class="line"><span class="selector-class">.horizontal-card</span> &#123;</span><br><span class="line">    <span class="attribute">display</span>: flex;</span><br><span class="line">    <span class="attribute">flex-direction</span>: row;</span><br><span class="line">    <span class="attribute">width</span>: <span class="number">100%</span>;</span><br><span class="line">    <span class="attribute">height</span>: <span class="number">260px</span>;</span><br><span class="line">    <span class="attribute">border-radius</span>: <span class="number">16px</span>;</span><br><span class="line">    <span class="attribute">overflow</span>: hidden;</span><br><span class="line">    <span class="attribute">background</span>: <span class="built_in">var</span>(--card-bg);</span><br><span class="line">    <span class="attribute">box-shadow</span>: <span class="number">0</span> <span class="number">8px</span> <span class="number">32px</span> <span class="built_in">rgba</span>(<span class="number">0</span>,<span class="number">0</span>,<span class="number">0</span>,<span class="number">0.3</span>);</span><br><span class="line">    <span class="attribute">transition</span>: all <span class="number">0.4s</span> ease;</span><br><span class="line">    <span class="attribute">cursor</span>: pointer;</span><br><span class="line">    <span class="attribute">position</span>: relative;</span><br><span class="line">    <span class="attribute">margin-bottom</span>: <span class="number">32px</span>;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="selector-class">.horizontal-card</span><span class="selector-pseudo">:hover</span> &#123;</span><br><span class="line">    <span class="attribute">transform</span>: <span class="built_in">translateY</span>(-<span class="number">6px</span>);</span><br><span class="line">    <span class="attribute">box-shadow</span>: <span class="number">0</span> <span class="number">16px</span> <span class="number">48px</span> <span class="built_in">rgba</span>(<span class="number">233</span>, <span class="number">69</span>, <span class="number">96</span>, <span class="number">0.15</span>);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">/* ========== 内容区域基础 ========== */</span></span><br><span class="line"><span class="selector-class">.horizontal-card</span> <span class="selector-class">.post-content</span> &#123;</span><br><span class="line">    <span class="attribute">width</span>: <span class="built_in">calc</span>(<span class="number">58%</span> - <span class="number">0px</span>);</span><br><span class="line">    <span class="attribute">flex</span>: <span class="number">0</span> <span class="number">0</span> <span class="built_in">calc</span>(<span class="number">58%</span> - <span class="number">0px</span>);</span><br><span class="line">    <span class="attribute">max-width</span>: <span class="built_in">calc</span>(<span class="number">58%</span> - <span class="number">0px</span>);</span><br><span class="line">    <span class="attribute">padding</span>: <span class="number">32px</span> <span class="number">40px</span>;</span><br><span class="line">    <span class="attribute">display</span>: flex;</span><br><span class="line">    <span class="attribute">flex-direction</span>: column;</span><br><span class="line">    <span class="attribute">justify-content</span>: space-between;</span><br><span class="line">    <span class="attribute">position</span>: relative;</span><br><span class="line">    <span class="attribute">z-index</span>: <span class="number">2</span>;</span><br><span class="line">    <span class="attribute">min-width</span>: <span class="number">0</span>;</span><br><span class="line">    <span class="attribute">overflow</span>: hidden;</span><br><span class="line">    <span class="attribute">box-sizing</span>: border-box;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="selector-class">.horizontal-card</span> <span class="selector-class">.post-title</span> &#123;</span><br><span class="line">    <span class="attribute">font-size</span>: <span class="number">1.6rem</span>;</span><br><span class="line">    <span class="attribute">font-weight</span>: <span class="number">700</span>;</span><br><span class="line">    <span class="attribute">line-height</span>: <span class="number">1.3</span>;</span><br><span class="line">    <span class="attribute">display</span>: -webkit-box;</span><br><span class="line">    -webkit-<span class="selector-tag">line</span>-clamp: <span class="number">2</span>;</span><br><span class="line">    -webkit-<span class="attribute">box-orient</span>: vertical;</span><br><span class="line">    <span class="attribute">overflow</span>: hidden;</span><br><span class="line">    <span class="attribute">margin-bottom</span>: <span class="number">4px</span>;</span><br><span class="line">    <span class="attribute">word-break</span>: break-word;</span><br><span class="line">    <span class="attribute">overflow-wrap</span>: break-word;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="selector-class">.horizontal-card</span> <span class="selector-class">.post-desc</span> &#123;</span><br><span class="line">    <span class="attribute">font-size</span>: <span class="number">1rem</span>;</span><br><span class="line">    <span class="attribute">line-height</span>: <span class="number">1.8</span>;</span><br><span class="line">    <span class="attribute">display</span>: -webkit-box;</span><br><span class="line">    -webkit-<span class="selector-tag">line</span>-clamp: <span class="number">2</span>;</span><br><span class="line">    -webkit-<span class="attribute">box-orient</span>: vertical;</span><br><span class="line">    <span class="attribute">overflow</span>: hidden;</span><br><span class="line">    <span class="attribute">margin-top</span>: <span class="number">12px</span>;</span><br><span class="line">    <span class="attribute">word-break</span>: break-word;</span><br><span class="line">    <span class="attribute">overflow-wrap</span>: break-word;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">/* ========== 图片区域基础 ========== */</span></span><br><span class="line"><span class="selector-class">.horizontal-card</span> <span class="selector-class">.post-cover</span> &#123;</span><br><span class="line">    <span class="attribute">width</span>: <span class="built_in">calc</span>(<span class="number">42%</span> - <span class="number">0px</span>);</span><br><span class="line">    <span class="attribute">flex</span>: <span class="number">0</span> <span class="number">0</span> <span class="built_in">calc</span>(<span class="number">42%</span> - <span class="number">0px</span>);</span><br><span class="line">    <span class="attribute">max-width</span>: <span class="built_in">calc</span>(<span class="number">42%</span> - <span class="number">0px</span>);</span><br><span class="line">    <span class="attribute">min-width</span>: <span class="number">320px</span>;</span><br><span class="line">    <span class="attribute">height</span>: <span class="number">100%</span>;</span><br><span class="line">    <span class="attribute">position</span>: relative;</span><br><span class="line">    <span class="attribute">overflow</span>: hidden;</span><br><span class="line">    <span class="attribute">box-sizing</span>: border-box;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="selector-class">.horizontal-card</span> <span class="selector-class">.post-cover</span> <span class="selector-tag">img</span> &#123;</span><br><span class="line">    <span class="attribute">width</span>: <span class="number">100%</span>;</span><br><span class="line">    <span class="attribute">height</span>: <span class="number">100%</span>;</span><br><span class="line">    <span class="attribute">object-fit</span>: cover;</span><br><span class="line">    <span class="attribute">transition</span>: transform <span class="number">0.6s</span> ease;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="selector-class">.horizontal-card</span><span class="selector-pseudo">:hover</span> <span class="selector-class">.post-cover</span> <span class="selector-tag">img</span> &#123;</span><br><span class="line">    <span class="attribute">transform</span>: <span class="built_in">scale</span>(<span class="number">1.1</span>);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">/* ========== 图片在右边 + 斜切线 ========== */</span></span><br><span class="line"><span class="selector-class">.horizontal-card</span><span class="selector-class">.image-right</span> &#123;</span><br><span class="line">    <span class="attribute">background</span>: <span class="built_in">linear-gradient</span>(<span class="number">135deg</span>, <span class="built_in">var</span>(--card-bg) <span class="number">0%</span>, <span class="built_in">var</span>(--card-bg-alt) <span class="number">100%</span>);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="selector-class">.horizontal-card</span><span class="selector-class">.image-right</span> <span class="selector-class">.post-content</span> &#123;</span><br><span class="line">    <span class="attribute">order</span>: <span class="number">1</span>;</span><br><span class="line">    <span class="attribute">padding-right</span>: <span class="number">80px</span>;</span><br><span class="line">    <span class="attribute">clip-path</span>: <span class="built_in">polygon</span>(</span><br><span class="line">        <span class="number">0</span> <span class="number">0</span>, </span><br><span class="line">        <span class="built_in">calc</span>(<span class="number">100%</span> - <span class="built_in">var</span>(--skew-width)) <span class="number">0</span>, </span><br><span class="line">        <span class="number">100%</span> <span class="number">100%</span>, </span><br><span class="line">        <span class="number">0</span> <span class="number">100%</span></span><br><span class="line">    );</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="selector-class">.horizontal-card</span><span class="selector-class">.image-right</span> <span class="selector-class">.post-cover</span> &#123;</span><br><span class="line">    <span class="attribute">order</span>: <span class="number">2</span>;</span><br><span class="line">    <span class="attribute">clip-path</span>: <span class="built_in">polygon</span>(</span><br><span class="line">        <span class="built_in">var</span>(--skew-width) <span class="number">0</span>, </span><br><span class="line">        <span class="number">100%</span> <span class="number">0</span>, </span><br><span class="line">        <span class="number">100%</span> <span class="number">100%</span>, </span><br><span class="line">        <span class="number">0</span> <span class="number">100%</span></span><br><span class="line">    );</span><br><span class="line">    <span class="attribute">margin-left</span>: <span class="built_in">calc</span>(<span class="built_in">var</span>(--skew-width) * -<span class="number">1</span>);</span><br><span class="line">    <span class="attribute">margin-right</span>: <span class="built_in">calc</span>(<span class="built_in">var</span>(--skew-width) * -<span class="number">1</span>);</span><br><span class="line">    <span class="attribute">width</span>: <span class="built_in">calc</span>(<span class="number">42%</span> + <span class="built_in">var</span>(--skew-width)) <span class="meta">!important</span>;</span><br><span class="line">    <span class="attribute">flex</span>: <span class="number">0</span> <span class="number">0</span> <span class="built_in">calc</span>(<span class="number">42%</span> + <span class="built_in">var</span>(--skew-width)) <span class="meta">!important</span>;</span><br><span class="line">    <span class="attribute">max-width</span>: <span class="built_in">calc</span>(<span class="number">42%</span> + <span class="built_in">var</span>(--skew-width)) <span class="meta">!important</span>;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">/* ========== 图片在左边 + 斜切线 ========== */</span></span><br><span class="line"><span class="selector-class">.horizontal-card</span><span class="selector-class">.image-left</span> &#123;</span><br><span class="line">    <span class="attribute">background</span>: <span class="built_in">linear-gradient</span>(<span class="number">225deg</span>, <span class="built_in">var</span>(--card-bg) <span class="number">0%</span>, <span class="built_in">var</span>(--card-bg-alt) <span class="number">100%</span>);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="selector-class">.horizontal-card</span><span class="selector-class">.image-left</span> <span class="selector-class">.post-content</span> &#123;</span><br><span class="line">    <span class="attribute">order</span>: <span class="number">2</span>;</span><br><span class="line">    <span class="attribute">padding-left</span>: <span class="number">80px</span>;</span><br><span class="line">    <span class="attribute">clip-path</span>: <span class="built_in">polygon</span>(</span><br><span class="line">        <span class="built_in">var</span>(--skew-width) <span class="number">0</span>, </span><br><span class="line">        <span class="number">100%</span> <span class="number">0</span>, </span><br><span class="line">        <span class="number">100%</span> <span class="number">100%</span>, </span><br><span class="line">        <span class="number">0</span> <span class="number">100%</span></span><br><span class="line">    );</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="selector-class">.horizontal-card</span><span class="selector-class">.image-left</span> <span class="selector-class">.post-cover</span> &#123;</span><br><span class="line">    <span class="attribute">order</span>: <span class="number">1</span>;</span><br><span class="line">    <span class="attribute">clip-path</span>: <span class="built_in">polygon</span>(</span><br><span class="line">        <span class="number">0</span> <span class="number">0</span>, </span><br><span class="line">        <span class="built_in">calc</span>(<span class="number">100%</span> - <span class="built_in">var</span>(--skew-width)) <span class="number">0</span>, </span><br><span class="line">        <span class="number">100%</span> <span class="number">100%</span>, </span><br><span class="line">        <span class="number">0</span> <span class="number">100%</span></span><br><span class="line">    );</span><br><span class="line">    <span class="attribute">margin-right</span>: <span class="built_in">calc</span>(<span class="built_in">var</span>(--skew-width) * -<span class="number">1</span>);</span><br><span class="line">    <span class="attribute">margin-left</span>: <span class="built_in">calc</span>(<span class="built_in">var</span>(--skew-width) * -<span class="number">1</span>);</span><br><span class="line">    <span class="attribute">width</span>: <span class="built_in">calc</span>(<span class="number">42%</span> + <span class="built_in">var</span>(--skew-width)) <span class="meta">!important</span>;</span><br><span class="line">    <span class="attribute">flex</span>: <span class="number">0</span> <span class="number">0</span> <span class="built_in">calc</span>(<span class="number">42%</span> + <span class="built_in">var</span>(--skew-width)) <span class="meta">!important</span>;</span><br><span class="line">    <span class="attribute">max-width</span>: <span class="built_in">calc</span>(<span class="number">42%</span> + <span class="built_in">var</span>(--skew-width)) <span class="meta">!important</span>;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">/* 第一张卡片距离顶部增加间距 */</span></span><br><span class="line"><span class="selector-class">.horizontal-card</span><span class="selector-pseudo">:first-child</span> &#123;</span><br><span class="line">    <span class="attribute">margin-top</span>: <span class="number">32px</span>;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">/* ========== 响应式适配 ========== */</span></span><br><span class="line"><span class="keyword">@media</span> screen <span class="keyword">and</span> (<span class="attribute">max-width</span>: <span class="number">768px</span>) &#123;</span><br><span class="line">    <span class="selector-class">.horizontal-card</span>,</span><br><span class="line">    <span class="selector-class">.horizontal-card</span><span class="selector-class">.image-left</span>,</span><br><span class="line">    <span class="selector-class">.horizontal-card</span><span class="selector-class">.image-right</span> &#123;</span><br><span class="line">        <span class="attribute">flex-direction</span>: column;</span><br><span class="line">        <span class="attribute">height</span>: auto;</span><br><span class="line">        <span class="attribute">margin-bottom</span>: <span class="number">28px</span>;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="selector-class">.horizontal-card</span> <span class="selector-class">.post-content</span>,</span><br><span class="line">    <span class="selector-class">.horizontal-card</span><span class="selector-class">.image-left</span> <span class="selector-class">.post-content</span>,</span><br><span class="line">    <span class="selector-class">.horizontal-card</span><span class="selector-class">.image-right</span> <span class="selector-class">.post-content</span> &#123;</span><br><span class="line">        <span class="attribute">width</span>: <span class="number">100%</span>;</span><br><span class="line">        <span class="attribute">flex</span>: none;</span><br><span class="line">        <span class="attribute">max-width</span>: <span class="number">100%</span>;</span><br><span class="line">        <span class="attribute">padding</span>: <span class="number">24px</span>;</span><br><span class="line">        <span class="attribute">order</span>: <span class="number">2</span>;</span><br><span class="line">        <span class="attribute">clip-path</span>: none;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="selector-class">.horizontal-card</span><span class="selector-class">.image-left</span> <span class="selector-class">.post-cover</span>,</span><br><span class="line">    <span class="selector-class">.horizontal-card</span><span class="selector-class">.image-right</span> <span class="selector-class">.post-cover</span> &#123;</span><br><span class="line">        <span class="attribute">width</span>: <span class="number">100%</span> <span class="meta">!important</span>;</span><br><span class="line">        <span class="attribute">flex</span>: none <span class="meta">!important</span>;</span><br><span class="line">        <span class="attribute">max-width</span>: <span class="number">100%</span> <span class="meta">!important</span>;</span><br><span class="line">        <span class="attribute">min-width</span>: auto;</span><br><span class="line">        <span class="attribute">height</span>: <span class="number">220px</span>;</span><br><span class="line">        <span class="attribute">order</span>: <span class="number">1</span>;</span><br><span class="line">        <span class="attribute">margin</span>: <span class="number">0</span> <span class="meta">!important</span>;</span><br><span class="line">        <span class="attribute">clip-path</span>: <span class="built_in">polygon</span>(<span class="number">0</span> <span class="number">0</span>, <span class="number">100%</span> <span class="number">0</span>, <span class="number">100%</span> <span class="number">85%</span>, <span class="number">0</span> <span class="number">100%</span>);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>_config.anzhiyu.yml调用自定义horizontal-card.css</p><figure class="highlight yaml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="attr">inject:</span></span><br><span class="line">  <span class="attr">head:</span></span><br><span class="line">    <span class="comment"># 自定义css</span></span><br><span class="line">    <span class="string">...</span></span><br><span class="line">    <span class="bullet">-</span> <span class="string">&lt;link</span> <span class="string">rel=&quot;stylesheet&quot;</span> <span class="string">href=&quot;/cdn/css/horizontal-card.css&quot;&gt;</span></span><br></pre></td></tr></table></figure><p>_config.anzhiyu.yml中使用新的文章卡片样式，建议添加article_double_row之后</p><figure class="highlight yaml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="string">...</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># 首页双栏显示</span></span><br><span class="line"><span class="attr">article_double_row:</span> <span class="literal">true</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># 文章卡片样式: default | horizontal</span></span><br><span class="line"><span class="attr">home_card_style:</span> <span class="string">horizontal</span></span><br><span class="line"></span><br><span class="line"><span class="string">...</span></span><br></pre></td></tr></table></figure>]]></content:encoded>
      
      
      <category domain="https://blog.eucalyptus.cc/categories/Hexo/">Hexo</category>
      
      
      <category domain="https://blog.eucalyptus.cc/tags/%E6%96%87%E7%AB%A0%E5%8D%A1%E7%89%87%E6%A0%B7%E5%BC%8F/">文章卡片样式</category>
      
      
      <comments>https://blog.eucalyptus.cc/2026/04/13/anzhiyu%E4%B8%BB%E9%A2%98%E6%96%B0%E6%B7%BB%E6%96%87%E7%AB%A0%E5%8D%A1%E7%89%87%E6%A0%B7%E5%BC%8F/#disqus_thread</comments>
      
    </item>
    
    <item>
      <title>anzhiyu主题侧边栏添加访客信息</title>
      <link>https://blog.eucalyptus.cc/2026/04/12/anzhiyu%E4%B8%BB%E9%A2%98%E4%BE%A7%E8%BE%B9%E6%A0%8F%E6%B7%BB%E5%8A%A0%E8%AE%BF%E5%AE%A2%E4%BF%A1%E6%81%AF/</link>
      <guid>https://blog.eucalyptus.cc/2026/04/12/anzhiyu%E4%B8%BB%E9%A2%98%E4%BE%A7%E8%BE%B9%E6%A0%8F%E6%B7%BB%E5%8A%A0%E8%AE%BF%E5%AE%A2%E4%BF%A1%E6%81%AF/</guid>
      <pubDate>Sun, 12 Apr 2026 15:15:45 GMT</pubDate>
      
        
        
          
          
      <description>&lt;h2 id=&quot;前言&quot;&gt;&lt;a href=&quot;#前言&quot; class=&quot;headerlink&quot;</description>
          
        
      
      
      
      <content:encoded><![CDATA[<h2 id="前言"><a href="#前言" class="headerlink" title="前言"></a>前言</h2><p>博客是写在网上的日记，但总有人悄悄路过。</p><p>侧边栏那个小小的卡片，它会尝试获取你的大致位置（不精确到门牌号，只是城市和距离），然后告诉博主：你离我多少公里，此刻是早安、午安还是晚安。</p><p>代码不多，但花了不少心思：多 API 降级、PJAX 兼容、距离计算、时间问候……甚至为了不泄露隐私，全程只使用公开的 IP 地理服务。</p><p>如果你是安知鱼主题的用户，可以直接照搬；如果你用其他主题，改改选择器也能用。</p><h2 id="正文"><a href="#正文" class="headerlink" title="正文"></a>正文</h2><p>文件路径</p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line">blog/</span><br><span class="line">├── themes/</span><br><span class="line">│   └── anzhiyu/                    </span><br><span class="line">│       ├── layout/                 </span><br><span class="line">│       │   ├── includes/           </span><br><span class="line">│       │   │   └── widget/         </span><br><span class="line">│       │   │       └── card_welcome.pug </span><br><span class="line">│       │   │       └── index.pug                      </span><br><span class="line">├── source/                        </span><br><span class="line">│   └── cdn/                        </span><br><span class="line">│       └── css/</span><br><span class="line">│       │   └── welcome.css</span><br><span class="line">│       └── js/</span><br><span class="line">│           └── welcome.js</span><br><span class="line">└── ...                             </span><br></pre></td></tr></table></figure><p><font style="color:#000000;background-color:rgba(255, 255, 255, 0.05);">新建card_welcome.pug</font></p><figure class="highlight css"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="selector-class">.card-widget</span><span class="selector-class">.card-welcome</span></span><br><span class="line">  <span class="selector-class">.card-content</span></span><br><span class="line">    <span class="selector-class">.item-headline</span></span><br><span class="line">      <span class="selector-tag">i</span><span class="selector-class">.fas</span><span class="selector-class">.fa-map-marker-alt</span></span><br><span class="line">    <span class="selector-id">#welcome-info</span>(style=&quot;<span class="attribute">min-height</span>: <span class="number">100px</span>; <span class="attribute">display</span>: flex; <span class="attribute">align-items</span>: center; <span class="attribute">justify-content</span>: center;&quot;)</span><br><span class="line">      <span class="selector-class">.loading-spinner</span></span><br><span class="line">        <span class="selector-tag">i</span><span class="selector-class">.fas</span><span class="selector-class">.fa-spinner</span><span class="selector-class">.fa-spin</span></span><br><span class="line">        |  正在定位...</span><br></pre></td></tr></table></figure><p>修改index.pug</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br></pre></td><td class="code"><pre><span class="line">#aside-content.<span class="property">aside</span>-content</span><br><span class="line">  <span class="comment">//- post</span></span><br><span class="line">  <span class="keyword">if</span> <span class="title function_">is_post</span>()</span><br><span class="line">    - <span class="keyword">const</span> tocStyle = page.<span class="property">toc_style_simple</span></span><br><span class="line">    - <span class="keyword">const</span> tocStyleVal = tocStyle === <span class="literal">true</span> || tocStyle === <span class="literal">false</span> ? tocStyle : theme.<span class="property">toc</span>.<span class="property">style_simple</span></span><br><span class="line">    <span class="keyword">if</span> showToc &amp;&amp; tocStyleVal</span><br><span class="line">      .<span class="property">sticky_layout</span></span><br><span class="line">        include ./card_post_toc.<span class="property">pug</span></span><br><span class="line">    <span class="keyword">else</span></span><br><span class="line">      !=<span class="title function_">partial</span>(<span class="string">&#x27;includes/widget/card_author&#x27;</span>, &#123;&#125;, &#123;<span class="attr">cache</span>: <span class="literal">true</span>&#125;)</span><br><span class="line">      !=<span class="title function_">partial</span>(<span class="string">&#x27;includes/widget/card_announcement&#x27;</span>, &#123;&#125;, &#123;<span class="attr">cache</span>: <span class="literal">true</span>&#125;)</span><br><span class="line">      </span><br><span class="line">      <span class="comment">// 添加访客信息卡片（公告下面）</span></span><br><span class="line">      !=<span class="title function_">partial</span>(<span class="string">&#x27;includes/widget/card_welcome&#x27;</span>, &#123;&#125;, &#123;<span class="attr">cache</span>: <span class="literal">true</span>&#125;)</span><br><span class="line">      </span><br><span class="line">      !=<span class="title function_">partial</span>(<span class="string">&#x27;includes/widget/card_weixin&#x27;</span>, &#123;&#125;, &#123;<span class="attr">cache</span>: <span class="literal">true</span>&#125;)</span><br><span class="line">      !=<span class="title function_">partial</span>(<span class="string">&#x27;includes/widget/card_top_self&#x27;</span>, &#123;&#125;, &#123;<span class="attr">cache</span>: <span class="literal">true</span>&#125;)</span><br><span class="line">      .<span class="property">sticky_layout</span></span><br><span class="line">        <span class="keyword">if</span> showToc</span><br><span class="line">          include ./card_post_toc.<span class="property">pug</span></span><br><span class="line">        !=<span class="title function_">partial</span>(<span class="string">&#x27;includes/widget/card_recent_post&#x27;</span>, &#123;&#125;, &#123;<span class="attr">cache</span>: <span class="literal">true</span>&#125;)</span><br><span class="line">        !=<span class="title function_">partial</span>(<span class="string">&#x27;includes/widget/card_ad&#x27;</span>, &#123;&#125;, &#123;<span class="attr">cache</span>: <span class="literal">true</span>&#125;)</span><br><span class="line">  <span class="keyword">else</span></span><br><span class="line">    <span class="comment">//- page</span></span><br><span class="line">    !=<span class="title function_">partial</span>(<span class="string">&#x27;includes/widget/card_author&#x27;</span>, &#123;&#125;, &#123;<span class="attr">cache</span>: <span class="literal">true</span>&#125;)</span><br><span class="line">    !=<span class="title function_">partial</span>(<span class="string">&#x27;includes/widget/card_announcement&#x27;</span>, &#123;&#125;, &#123;<span class="attr">cache</span>: <span class="literal">true</span>&#125;)</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 添加访客信息卡片（公告下面）</span></span><br><span class="line">    !=<span class="title function_">partial</span>(<span class="string">&#x27;includes/widget/card_welcome&#x27;</span>, &#123;&#125;, &#123;<span class="attr">cache</span>: <span class="literal">true</span>&#125;)</span><br><span class="line">    </span><br><span class="line">    !=<span class="title function_">partial</span>(<span class="string">&#x27;includes/widget/card_weixin&#x27;</span>, &#123;&#125;, &#123;<span class="attr">cache</span>: <span class="literal">true</span>&#125;)</span><br><span class="line">    !=<span class="title function_">partial</span>(<span class="string">&#x27;includes/widget/card_top_self&#x27;</span>, &#123;&#125;, &#123;<span class="attr">cache</span>: <span class="literal">true</span>&#125;)</span><br><span class="line">    !=<span class="title function_">partial</span>(<span class="string">&#x27;includes/widget/card_categories&#x27;</span>, &#123;&#125;, &#123;<span class="attr">cache</span>: <span class="literal">true</span>&#125;)  </span><br><span class="line"></span><br><span class="line">    .<span class="property">sticky_layout</span></span><br><span class="line">      <span class="keyword">if</span> showToc</span><br><span class="line">        include ./card_post_toc.<span class="property">pug</span></span><br><span class="line">      .<span class="property">card</span>-widget</span><br><span class="line">        !=<span class="title function_">partial</span>(<span class="string">&#x27;includes/widget/card_ad&#x27;</span>, &#123;&#125;, &#123;<span class="attr">cache</span>: <span class="literal">true</span>&#125;)</span><br><span class="line">        !=<span class="title function_">partial</span>(<span class="string">&#x27;includes/widget/card_tags&#x27;</span>, &#123;&#125;, &#123;<span class="attr">cache</span>: <span class="literal">true</span>&#125;)</span><br><span class="line">        !=<span class="title function_">partial</span>(<span class="string">&#x27;includes/widget/card_archives&#x27;</span>, &#123;&#125;, &#123;<span class="attr">cache</span>: <span class="literal">true</span>&#125;)</span><br><span class="line">        !=<span class="title function_">partial</span>(<span class="string">&#x27;includes/widget/card_webinfo&#x27;</span>, &#123;&#125;, &#123;<span class="attr">cache</span>: <span class="literal">true</span>&#125;)</span><br><span class="line">      !=<span class="title function_">partial</span>(<span class="string">&#x27;includes/widget/card_bottom_self&#x27;</span>, &#123;&#125;, &#123;<span class="attr">cache</span>: <span class="literal">true</span>&#125;)</span><br></pre></td></tr></table></figure><p>新建welcome.js</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br><span class="line">89</span><br><span class="line">90</span><br><span class="line">91</span><br><span class="line">92</span><br><span class="line">93</span><br><span class="line">94</span><br><span class="line">95</span><br><span class="line">96</span><br><span class="line">97</span><br><span class="line">98</span><br><span class="line">99</span><br><span class="line">100</span><br><span class="line">101</span><br><span class="line">102</span><br><span class="line">103</span><br><span class="line">104</span><br><span class="line">105</span><br><span class="line">106</span><br><span class="line">107</span><br><span class="line">108</span><br><span class="line">109</span><br><span class="line">110</span><br><span class="line">111</span><br><span class="line">112</span><br><span class="line">113</span><br><span class="line">114</span><br><span class="line">115</span><br><span class="line">116</span><br><span class="line">117</span><br><span class="line">118</span><br><span class="line">119</span><br><span class="line">120</span><br><span class="line">121</span><br><span class="line">122</span><br><span class="line">123</span><br><span class="line">124</span><br><span class="line">125</span><br><span class="line">126</span><br><span class="line">127</span><br><span class="line">128</span><br><span class="line">129</span><br><span class="line">130</span><br><span class="line">131</span><br><span class="line">132</span><br><span class="line">133</span><br><span class="line">134</span><br><span class="line">135</span><br><span class="line">136</span><br><span class="line">137</span><br><span class="line">138</span><br><span class="line">139</span><br><span class="line">140</span><br><span class="line">141</span><br><span class="line">142</span><br><span class="line">143</span><br><span class="line">144</span><br><span class="line">145</span><br><span class="line">146</span><br><span class="line">147</span><br><span class="line">148</span><br><span class="line">149</span><br><span class="line">150</span><br><span class="line">151</span><br><span class="line">152</span><br><span class="line">153</span><br><span class="line">154</span><br><span class="line">155</span><br><span class="line">156</span><br><span class="line">157</span><br><span class="line">158</span><br><span class="line">159</span><br><span class="line">160</span><br><span class="line">161</span><br><span class="line">162</span><br><span class="line">163</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 使用 IIFE 避免全局污染</span></span><br><span class="line">(<span class="keyword">function</span>(<span class="params"></span>) &#123;</span><br><span class="line">    <span class="comment">// 标记是否已经获取过 IP 信息（当前会话内只获取一次即可）</span></span><br><span class="line">    <span class="keyword">let</span> ipInfoLoaded = <span class="literal">false</span>;</span><br><span class="line">    <span class="keyword">let</span> cachedHTML = <span class="literal">null</span>;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">const</span> <span class="title function_">fetchIpInfo</span> = <span class="keyword">async</span> (<span class="params"></span>) =&gt; &#123;</span><br><span class="line">        <span class="keyword">const</span> welcomeInfo = <span class="variable language_">document</span>.<span class="title function_">querySelector</span>(<span class="string">&#x27;#welcome-info&#x27;</span>);</span><br><span class="line">        <span class="keyword">if</span> (!welcomeInfo) <span class="keyword">return</span>;</span><br><span class="line">        </span><br><span class="line">        <span class="comment">// 如果已经加载过，直接恢复缓存的内容</span></span><br><span class="line">        <span class="keyword">if</span> (ipInfoLoaded &amp;&amp; cachedHTML) &#123;</span><br><span class="line">            welcomeInfo.<span class="property">innerHTML</span> = cachedHTML;</span><br><span class="line">            <span class="keyword">return</span>;</span><br><span class="line">        &#125;</span><br><span class="line">        </span><br><span class="line">        <span class="comment">// 显示加载状态</span></span><br><span class="line">        welcomeInfo.<span class="property">innerHTML</span> = <span class="string">&#x27;&lt;div class=&quot;loading&quot;&gt;&lt;i class=&quot;fas fa-spinner fa-spin&quot;&gt;&lt;/i&gt; 定位中...&lt;/div&gt;&#x27;</span>;</span><br><span class="line">        </span><br><span class="line">        <span class="comment">// 备用 API 列表（ipinfo.io 在国内可能被拦截，放在后面）</span></span><br><span class="line">        <span class="keyword">const</span> apis = [</span><br><span class="line">            &#123; </span><br><span class="line">                <span class="attr">url</span>: <span class="string">&#x27;https://api.ip.sb/geoip&#x27;</span>, </span><br><span class="line">                <span class="attr">parser</span>: <span class="function">(<span class="params">d</span>) =&gt;</span> (&#123;</span><br><span class="line">                    <span class="attr">country</span>: d.<span class="property">country</span>,</span><br><span class="line">                    <span class="attr">region</span>: d.<span class="property">region</span>,</span><br><span class="line">                    <span class="attr">city</span>: d.<span class="property">city</span>,</span><br><span class="line">                    <span class="attr">loc</span>: d.<span class="property">latitude</span> &amp;&amp; d.<span class="property">longitude</span> ? <span class="string">`<span class="subst">$&#123;d.latitude&#125;</span>,<span class="subst">$&#123;d.longitude&#125;</span>`</span> : <span class="literal">null</span></span><br><span class="line">                &#125;)</span><br><span class="line">            &#125;,</span><br><span class="line">            &#123;</span><br><span class="line">                <span class="attr">url</span>: <span class="string">&#x27;https://ipapi.co/json/&#x27;</span>,</span><br><span class="line">                <span class="attr">parser</span>: <span class="function">(<span class="params">d</span>) =&gt;</span> (&#123;</span><br><span class="line">                    <span class="attr">country</span>: d.<span class="property">country_code</span>,</span><br><span class="line">                    <span class="attr">region</span>: d.<span class="property">region</span>,</span><br><span class="line">                    <span class="attr">city</span>: d.<span class="property">city</span>,</span><br><span class="line">                    <span class="attr">loc</span>: d.<span class="property">latitude</span> &amp;&amp; d.<span class="property">longitude</span> ? <span class="string">`<span class="subst">$&#123;d.latitude&#125;</span>,<span class="subst">$&#123;d.longitude&#125;</span>`</span> : <span class="literal">null</span></span><br><span class="line">                &#125;)</span><br><span class="line">            &#125;,</span><br><span class="line">            &#123;</span><br><span class="line">                <span class="attr">url</span>: <span class="string">&#x27;https://ipinfo.io/json&#x27;</span>,</span><br><span class="line">                <span class="attr">parser</span>: <span class="function">(<span class="params">d</span>) =&gt;</span> (&#123;</span><br><span class="line">                    <span class="attr">country</span>: d.<span class="property">country</span>,</span><br><span class="line">                    <span class="attr">region</span>: d.<span class="property">region</span>,</span><br><span class="line">                    <span class="attr">city</span>: d.<span class="property">city</span>,</span><br><span class="line">                    <span class="attr">loc</span>: d.<span class="property">loc</span></span><br><span class="line">                &#125;)</span><br><span class="line">            &#125;</span><br><span class="line">        ];</span><br><span class="line">        </span><br><span class="line">        <span class="keyword">let</span> data = <span class="literal">null</span>;</span><br><span class="line">        </span><br><span class="line">        <span class="keyword">for</span> (<span class="keyword">const</span> api <span class="keyword">of</span> apis) &#123;</span><br><span class="line">            <span class="keyword">try</span> &#123;</span><br><span class="line">                <span class="keyword">const</span> controller = <span class="keyword">new</span> <span class="title class_">AbortController</span>();</span><br><span class="line">                <span class="keyword">const</span> timeout = <span class="built_in">setTimeout</span>(<span class="function">() =&gt;</span> controller.<span class="title function_">abort</span>(), <span class="number">3000</span>);</span><br><span class="line">                </span><br><span class="line">                <span class="keyword">const</span> res = <span class="keyword">await</span> <span class="title function_">fetch</span>(api.<span class="property">url</span>, &#123; </span><br><span class="line">                    <span class="attr">signal</span>: controller.<span class="property">signal</span>,</span><br><span class="line">                    <span class="attr">headers</span>: &#123; <span class="string">&#x27;Accept&#x27;</span>: <span class="string">&#x27;application/json&#x27;</span> &#125;</span><br><span class="line">                &#125;);</span><br><span class="line">                <span class="built_in">clearTimeout</span>(timeout);</span><br><span class="line">                </span><br><span class="line">                <span class="keyword">if</span> (res.<span class="property">ok</span>) &#123;</span><br><span class="line">                    <span class="keyword">const</span> raw = <span class="keyword">await</span> res.<span class="title function_">json</span>();</span><br><span class="line">                    data = api.<span class="title function_">parser</span>(raw);</span><br><span class="line">                    <span class="keyword">break</span>;</span><br><span class="line">                &#125;</span><br><span class="line">            &#125; <span class="keyword">catch</span> (e) &#123;</span><br><span class="line">                <span class="keyword">continue</span>;</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">        </span><br><span class="line">        <span class="comment">// 渲染内容</span></span><br><span class="line">        <span class="keyword">if</span> (data) &#123;</span><br><span class="line">            <span class="keyword">const</span> &#123; country, region, city, loc &#125; = data;</span><br><span class="line">            <span class="keyword">const</span> [lat, lng] = loc ? loc.<span class="title function_">split</span>(<span class="string">&#x27;,&#x27;</span>).<span class="title function_">map</span>(<span class="title class_">Number</span>) : [];</span><br><span class="line">            </span><br><span class="line">            <span class="keyword">const</span> myLat = <span class="number">31.707754</span>;</span><br><span class="line">            <span class="keyword">const</span> myLng = <span class="number">119.825873</span>;</span><br><span class="line">            <span class="keyword">const</span> dist = (lat &amp;&amp; lng) ? <span class="title function_">calculateDistance</span>(lng, lat, myLng, myLat) : <span class="literal">null</span>;</span><br><span class="line">            </span><br><span class="line">            <span class="keyword">const</span> location = country === <span class="string">&quot;CN&quot;</span> || country === <span class="string">&quot;China&quot;</span> </span><br><span class="line">                ? <span class="string">`<span class="subst">$&#123;region || <span class="string">&#x27;&#x27;</span>&#125;</span> <span class="subst">$&#123;city || <span class="string">&#x27;&#x27;</span>&#125;</span>`</span>.<span class="title function_">trim</span>() </span><br><span class="line">                : (country || <span class="string">&#x27;地球&#x27;</span>);</span><br><span class="line">            </span><br><span class="line">            cachedHTML = <span class="string">`</span></span><br><span class="line"><span class="string">                &lt;div class=&quot;welcome-content&quot;&gt;</span></span><br><span class="line"><span class="string">                    &lt;p&gt;欢迎来自 &lt;span class=&quot;location&quot;&gt;<span class="subst">$&#123;location || <span class="string">&#x27;神秘星球&#x27;</span>&#125;</span>&lt;/span&gt; 的道友 💖&lt;/p&gt;</span></span><br><span class="line"><span class="string">                    <span class="subst">$&#123;dist ? <span class="string">`&lt;p&gt;距博主约 &lt;span class=&quot;distance&quot;&gt;<span class="subst">$&#123;dist&#125;</span>&lt;/span&gt; 公里&lt;/p&gt;`</span> : <span class="string">&#x27;&#x27;</span>&#125;</span></span></span><br><span class="line"><span class="string">                    &lt;p class=&quot;greeting&quot;&gt;<span class="subst">$&#123;getTimeGreeting()&#125;</span>&lt;/p&gt;</span></span><br><span class="line"><span class="string">                &lt;/div&gt;</span></span><br><span class="line"><span class="string">            `</span>;</span><br><span class="line">            ipInfoLoaded = <span class="literal">true</span>;</span><br><span class="line">            welcomeInfo.<span class="property">innerHTML</span> = cachedHTML;</span><br><span class="line">        &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">            cachedHTML = <span class="string">`</span></span><br><span class="line"><span class="string">                &lt;div class=&quot;welcome-content&quot;&gt;</span></span><br><span class="line"><span class="string">                    &lt;p&gt;🎉 欢迎来到我的博客！&lt;/p&gt;</span></span><br><span class="line"><span class="string">                    &lt;p class=&quot;greeting&quot;&gt;<span class="subst">$&#123;getTimeGreeting()&#125;</span>&lt;/p&gt;</span></span><br><span class="line"><span class="string">                &lt;/div&gt;</span></span><br><span class="line"><span class="string">            `</span>;</span><br><span class="line">            ipInfoLoaded = <span class="literal">true</span>;</span><br><span class="line">            welcomeInfo.<span class="property">innerHTML</span> = cachedHTML;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">const</span> <span class="title function_">calculateDistance</span> = (<span class="params">lng1, lat1, lng2, lat2</span>) =&gt; &#123;</span><br><span class="line">        <span class="keyword">if</span> (!lat1 || !lng1 || !lat2 || !lng2) <span class="keyword">return</span> <span class="literal">null</span>;</span><br><span class="line">        <span class="keyword">const</span> R = <span class="number">6371</span>;</span><br><span class="line">        <span class="keyword">const</span> rad = <span class="title class_">Math</span>.<span class="property">PI</span> / <span class="number">180</span>;</span><br><span class="line">        <span class="keyword">const</span> dLat = (lat2 - lat1) * rad;</span><br><span class="line">        <span class="keyword">const</span> dLon = (lng2 - lng1) * rad;</span><br><span class="line">        <span class="keyword">const</span> a = <span class="title class_">Math</span>.<span class="title function_">sin</span>(dLat/<span class="number">2</span>) ** <span class="number">2</span> + <span class="title class_">Math</span>.<span class="title function_">cos</span>(lat1 * rad) * <span class="title class_">Math</span>.<span class="title function_">cos</span>(lat2 * rad) * <span class="title class_">Math</span>.<span class="title function_">sin</span>(dLon/<span class="number">2</span>) ** <span class="number">2</span>;</span><br><span class="line">        <span class="keyword">return</span> <span class="title class_">Math</span>.<span class="title function_">round</span>(R * <span class="number">2</span> * <span class="title class_">Math</span>.<span class="title function_">atan2</span>(<span class="title class_">Math</span>.<span class="title function_">sqrt</span>(a), <span class="title class_">Math</span>.<span class="title function_">sqrt</span>(<span class="number">1</span>-a)));</span><br><span class="line">    &#125;;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">const</span> <span class="title function_">getTimeGreeting</span> = (<span class="params"></span>) =&gt; &#123;</span><br><span class="line">        <span class="keyword">const</span> hour = <span class="keyword">new</span> <span class="title class_">Date</span>().<span class="title function_">getHours</span>();</span><br><span class="line">        <span class="keyword">if</span> (hour &lt; <span class="number">6</span>) <span class="keyword">return</span> <span class="string">&quot;It&#x27;s late, pay attention to rest 🌙&quot;</span>;</span><br><span class="line">        <span class="keyword">if</span> (hour &lt; <span class="number">12</span>) <span class="keyword">return</span> <span class="string">&quot;Good morning, start your day. ☀️&quot;</span>;</span><br><span class="line">        <span class="keyword">if</span> (hour &lt; <span class="number">18</span>) <span class="keyword">return</span> <span class="string">&quot;Good afternoon. Good work. ☕&quot;</span>;</span><br><span class="line">        <span class="keyword">return</span> <span class="string">&quot;Good evening. May you have a good mood. 🌆&quot;</span>;</span><br><span class="line">    &#125;;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// ===== 初始化策略 =====</span></span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 1. 首次加载</span></span><br><span class="line">    <span class="keyword">if</span> (<span class="variable language_">document</span>.<span class="property">readyState</span> === <span class="string">&#x27;loading&#x27;</span>) &#123;</span><br><span class="line">        <span class="variable language_">document</span>.<span class="title function_">addEventListener</span>(<span class="string">&#x27;DOMContentLoaded&#x27;</span>, fetchIpInfo);</span><br><span class="line">    &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">        <span class="title function_">fetchIpInfo</span>();</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 2. 安知鱼主题 PJAX 事件（关键）</span></span><br><span class="line">    <span class="comment">// 安知鱼使用 pjax:complete，但 footer 不刷新，所以需要手动恢复内容</span></span><br><span class="line">    <span class="variable language_">document</span>.<span class="title function_">addEventListener</span>(<span class="string">&#x27;pjax:complete&#x27;</span>, <span class="function">() =&gt;</span> &#123;</span><br><span class="line">        <span class="comment">// PJAX 完成后，检查 welcome-info 是否存在</span></span><br><span class="line">        <span class="built_in">setTimeout</span>(<span class="function">() =&gt;</span> &#123;</span><br><span class="line">            <span class="keyword">const</span> welcomeInfo = <span class="variable language_">document</span>.<span class="title function_">querySelector</span>(<span class="string">&#x27;#welcome-info&#x27;</span>);</span><br><span class="line">            <span class="keyword">if</span> (welcomeInfo) &#123;</span><br><span class="line">                <span class="keyword">if</span> (cachedHTML &amp;&amp; ipInfoLoaded) &#123;</span><br><span class="line">                    <span class="comment">// 已经有缓存，直接恢复</span></span><br><span class="line">                    welcomeInfo.<span class="property">innerHTML</span> = cachedHTML;</span><br><span class="line">                &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">                    <span class="comment">// 首次或缓存丢失，重新获取</span></span><br><span class="line">                    <span class="title function_">fetchIpInfo</span>();</span><br><span class="line">                &#125;</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;, <span class="number">100</span>); <span class="comment">// 延迟确保 DOM 已更新</span></span><br><span class="line">    &#125;);</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 3. 浏览器前进/后退按钮</span></span><br><span class="line">    <span class="variable language_">window</span>.<span class="title function_">addEventListener</span>(<span class="string">&#x27;popstate&#x27;</span>, <span class="function">() =&gt;</span> &#123;</span><br><span class="line">        <span class="built_in">setTimeout</span>(<span class="function">() =&gt;</span> &#123;</span><br><span class="line">            <span class="keyword">const</span> welcomeInfo = <span class="variable language_">document</span>.<span class="title function_">querySelector</span>(<span class="string">&#x27;#welcome-info&#x27;</span>);</span><br><span class="line">            <span class="keyword">if</span> (welcomeInfo &amp;&amp; cachedHTML &amp;&amp; ipInfoLoaded) &#123;</span><br><span class="line">                welcomeInfo.<span class="property">innerHTML</span> = cachedHTML;</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;, <span class="number">100</span>);</span><br><span class="line">    &#125;);</span><br><span class="line"></span><br><span class="line">&#125;)();</span><br></pre></td></tr></table></figure><p>新建welcome.css</p><figure class="highlight css"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br></pre></td><td class="code"><pre><span class="line"><span class="selector-class">.card-welcome</span> <span class="selector-class">.item-headline</span> <span class="selector-tag">i</span> &#123;</span><br><span class="line">    <span class="attribute">color</span>: <span class="number">#4dabf7</span>;</span><br><span class="line">  &#125;</span><br><span class="line">  </span><br><span class="line">  <span class="selector-class">.card-welcome</span> <span class="selector-id">#welcome-info</span> &#123;</span><br><span class="line">    <span class="attribute">color</span>: <span class="built_in">var</span>(--anzhiyu-fontcolor);</span><br><span class="line">    <span class="attribute">text-align</span>: center;</span><br><span class="line">    <span class="attribute">padding</span>: <span class="number">15px</span>;</span><br><span class="line">  &#125;</span><br><span class="line">  </span><br><span class="line">  <span class="selector-class">.card-welcome</span> <span class="selector-id">#welcome-info</span> <span class="selector-class">.loading</span> &#123;</span><br><span class="line">    <span class="attribute">font-size</span>: <span class="number">14px</span>;</span><br><span class="line">  &#125;</span><br><span class="line">  </span><br><span class="line">  <span class="selector-class">.card-welcome</span> <span class="selector-id">#welcome-info</span> <span class="selector-class">.loading</span> <span class="selector-tag">i</span> &#123;</span><br><span class="line">    <span class="attribute">margin-right</span>: <span class="number">8px</span>;</span><br><span class="line">  &#125;</span><br><span class="line">  </span><br><span class="line">  <span class="selector-class">.card-welcome</span> <span class="selector-class">.welcome-content</span> <span class="selector-class">.location</span> &#123;</span><br><span class="line">    <span class="attribute">color</span>: <span class="number">#ffd700</span>;</span><br><span class="line">    <span class="attribute">font-weight</span>: bold;</span><br><span class="line">    <span class="attribute">font-size</span>: <span class="number">16px</span>;</span><br><span class="line">  &#125;</span><br><span class="line">  </span><br><span class="line">  <span class="selector-class">.card-welcome</span> <span class="selector-class">.welcome-content</span> <span class="selector-class">.distance</span> &#123;</span><br><span class="line">    <span class="attribute">color</span>: <span class="number">#4dabf7</span>;</span><br><span class="line">    <span class="attribute">font-weight</span>: bold;</span><br><span class="line">  &#125;</span><br><span class="line">  </span><br><span class="line">  <span class="selector-class">.card-welcome</span> <span class="selector-class">.welcome-content</span> <span class="selector-class">.ip-address</span> &#123;</span><br><span class="line">    <span class="attribute">font-size</span>: <span class="number">12px</span>;</span><br><span class="line">    <span class="attribute">opacity</span>: <span class="number">0.7</span>;</span><br><span class="line">    <span class="attribute">margin-top</span>: <span class="number">5px</span>;</span><br><span class="line">  &#125;</span><br><span class="line">  </span><br><span class="line">  <span class="selector-class">.card-welcome</span> <span class="selector-class">.welcome-content</span> <span class="selector-class">.greeting</span> &#123;</span><br><span class="line">    <span class="attribute">font-size</span>: <span class="number">14px</span>;</span><br><span class="line">    <span class="attribute">line-height</span>: <span class="number">1.5</span>;</span><br><span class="line">    <span class="attribute">opacity</span>: <span class="number">0.95</span>;</span><br><span class="line">    <span class="attribute">letter-spacing</span>: <span class="number">0.3px</span>;</span><br><span class="line">  &#125;</span><br></pre></td></tr></table></figure><p><font style="color:rgb(76, 73, 72);background-color:rgba(255, 255, 255, 0.9);">_config.anzhiyu.yml 调用自定义 welcome.css和welcome.js</font></p><figure class="highlight yaml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="attr">inject:</span></span><br><span class="line">  <span class="attr">head:</span></span><br><span class="line">    <span class="comment"># 自定义css</span></span><br><span class="line">    <span class="string">...</span></span><br><span class="line">    <span class="bullet">-</span> <span class="string">&lt;link</span> <span class="string">rel=&quot;stylesheet&quot;</span> <span class="string">href=&quot;/cdn/css/welcome.css&quot;&gt;</span></span><br><span class="line">    <span class="string">...</span></span><br><span class="line"> </span><br><span class="line">  <span class="attr">bottom:</span></span><br><span class="line">    <span class="comment"># 自定义js</span></span><br><span class="line">    <span class="string">...</span></span><br><span class="line"><span class="string">```</span>    <span class="bullet">-</span> <span class="string">&lt;script</span> <span class="string">defer</span> <span class="string">src=&quot;/cdn/js/welcome.js&quot;&gt;&lt;/script&gt;</span></span><br><span class="line">    <span class="string">...</span></span><br></pre></td></tr></table></figure>]]></content:encoded>
      
      
      <category domain="https://blog.eucalyptus.cc/categories/Hexo/">Hexo</category>
      
      
      <category domain="https://blog.eucalyptus.cc/tags/%E4%BE%A7%E8%BE%B9%E6%A0%8F/">侧边栏</category>
      
      
      <comments>https://blog.eucalyptus.cc/2026/04/12/anzhiyu%E4%B8%BB%E9%A2%98%E4%BE%A7%E8%BE%B9%E6%A0%8F%E6%B7%BB%E5%8A%A0%E8%AE%BF%E5%AE%A2%E4%BF%A1%E6%81%AF/#disqus_thread</comments>
      
    </item>
    
    <item>
      <title>Hexo网站地图生成与百度主动推送</title>
      <link>https://blog.eucalyptus.cc/2026/03/01/sitemap/</link>
      <guid>https://blog.eucalyptus.cc/2026/03/01/sitemap/</guid>
      <pubDate>Sun, 01 Mar 2026 10:43:45 GMT</pubDate>
      
        
        
          
          
      <description>&lt;h2 id=&quot;网站地图生成&quot;&gt;&lt;a href=&quot;#网站地图生成&quot; class=&quot;headerlink&quot; title=&quot;网站地图生成&quot;&gt;&lt;/a&gt;网站地图生成&lt;/h2&gt;&lt;h3 id=&quot;安装插件&quot;&gt;&lt;a href=&quot;#安装插件&quot; class=&quot;headerlink&quot;</description>
          
        
      
      
      
      <content:encoded><![CDATA[<h2 id="网站地图生成"><a href="#网站地图生成" class="headerlink" title="网站地图生成"></a>网站地图生成</h2><h3 id="安装插件"><a href="#安装插件" class="headerlink" title="安装插件"></a>安装插件</h3><p>在 Hexo 站点根目录执行：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">npm install hexo-generator-sitemap --save</span><br></pre></td></tr></table></figure><h3 id="配置-config-yml"><a href="#配置-config-yml" class="headerlink" title="配置 _config.yml"></a>配置 _config.yml</h3><p>编辑 Hexo 根目录的 <code>_config.yml</code> 文件，添加以下配置：</p><figure class="highlight yaml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># 站点地图配置</span></span><br><span class="line"><span class="attr">sitemap:</span></span><br><span class="line">  <span class="attr">path:</span> <span class="string">sitemap.xml</span></span><br><span class="line">  <span class="comment"># 可选参数</span></span><br><span class="line">  <span class="attr">priority:</span> <span class="number">0.5</span>        <span class="comment"># 页面优先级，范围 0.0-1.0</span></span><br><span class="line">  <span class="attr">changefreq:</span> <span class="string">daily</span>    <span class="comment"># 更新频率：always/hourly/daily/weekly/monthly/yearly/never</span></span><br><span class="line">  <span class="attr">lastmod:</span> <span class="literal">true</span>        <span class="comment"># 是否包含最后修改时间</span></span><br><span class="line">  <span class="attr">tags:</span> <span class="literal">true</span>           <span class="comment"># 是否包含标签页</span></span><br><span class="line">  <span class="attr">categories:</span> <span class="literal">true</span>     <span class="comment"># 是否包含分类页</span></span><br></pre></td></tr></table></figure><p><strong>注意</strong>：确保 <code>_config.yml</code> 中的 <code>url</code> 配置正确，站点地图会基于这个 URL 生成完整链接 ：</p><figure class="highlight yaml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># URL 配置（必须正确设置）</span></span><br><span class="line"><span class="attr">url:</span> <span class="string">https://img.eucalyptus.cc</span></span><br><span class="line"><span class="attr">root:</span> <span class="string">/</span></span><br><span class="line"><span class="attr">permalink:</span> <span class="string">:year/:month/:day/:title/</span></span><br></pre></td></tr></table></figure><h3 id="生成站点地图"><a href="#生成站点地图" class="headerlink" title="生成站点地图"></a>生成站点地图</h3><p>执行生成命令：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">hexo clean &amp;&amp; hexo generate</span><br></pre></td></tr></table></figure><p>生成后会在 <code>public/</code> 目录下创建文件：<code>sitemap.xml</code> </p><h2 id="百度主动推送"><a href="#百度主动推送" class="headerlink" title="百度主动推送"></a>百度主动推送</h2><h3 id="1-安装插件"><a href="#1-安装插件" class="headerlink" title="1. 安装插件"></a>1. 安装插件</h3><p>在 Hexo 站点根目录下执行：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">npm install hexo-baidu-url-submit --save</span><br></pre></td></tr></table></figure><h3 id="2-获取百度-Token"><a href="#2-获取百度-Token" class="headerlink" title="2. 获取百度 Token"></a>2. 获取百度 Token</h3><ol><li>访问 <div class='anzhiyu-tag-link'><a class="tag-Link" target="_blank" rel="noopener external nofollow noreferrer" href="https://ziyuan.baidu.com/site">    <div class="tag-link-tips">引用站外地址</div>    <div class="tag-link-bottom">        <div class="tag-link-left" data-icon="https://ziyuan.baidu.com/favicon.ico" style="width:60px;height:60px;min-width:60px;min-height:60px;position:relative;border-radius:var(--anzhiyu-border-radius);overflow:hidden;">          <img class="tag-link-favicon" src="https://ziyuan.baidu.com/favicon.ico" onerror="this.style.display='none';this.nextElementSibling.style.display='flex';" style="width:60px;height:60px;object-fit:cover;border-radius:var(--anzhiyu-border-radius);position:absolute;top:0;left:0;" alt="favicon"/>          <i class="fa-solid fa-link" style="display:flex;align-items:center;justify-content:center;width:100%;height:100%;"></i>        </div>        <div class="tag-link-right">            <div class="tag-link-title">百度搜索资源平台</div>            <div class="tag-link-sitename">百度SEO</div>        </div>        <i class="eucalyptus icon-angle-right"></i>    </div>    </a></div></li><li>注册&#x2F;登录并添加你的网站</li><li>进入 <strong>站点管理 → 资源提交 → 链接提交 → 主动推送(实时)</strong></li><li>找到推送接口地址，复制 <code>token=</code> 后面的字符串</li></ol><!-- 这是一张图片，ocr 内容为： --><p><img src="https://img.eucalyptus.cc/images/20260301/1.avif"></p><h3 id="3-修改站点配置"><a href="#3-修改站点配置" class="headerlink" title="3. 修改站点配置"></a>3. 修改站点配置</h3><p>编辑 Hexo 根目录的 <code>_config.yml</code>，添加以下配置：</p><figure class="highlight yaml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># 百度主动推送配置</span></span><br><span class="line"><span class="attr">baidu_url_submit:</span></span><br><span class="line">  <span class="attr">count:</span> <span class="number">10</span>              <span class="comment"># 提交最新的多少个链接（默认200）</span></span><br><span class="line">  <span class="attr">host:</span> <span class="string">img.eucalyptus.cc</span>  <span class="comment"># 在百度站长平台注册的域名（不含 http/https）</span></span><br><span class="line">  <span class="attr">token:</span> <span class="string">your_token_here</span>  <span class="comment"># 你的百度推送密钥（不要泄露！）</span></span><br><span class="line">  <span class="attr">path:</span> <span class="string">baidu_urls.txt</span>    <span class="comment"># 生成的链接文件路径（默认即可）</span></span><br></pre></td></tr></table></figure><p><strong>注意</strong>：<code>host</code> 字段不要带 <code>http://</code> 或 <code>https://</code>，只需要纯域名 。</p><h3 id="4-添加部署配置"><a href="#4-添加部署配置" class="headerlink" title="4. 添加部署配置"></a>4. 添加部署配置</h3><p>在 <code>_config.yml</code> 的 <code>deploy</code> 部分添加推送类型：</p><figure class="highlight yaml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="attr">deploy:</span></span><br><span class="line">  <span class="bullet">-</span> <span class="attr">type:</span> <span class="string">baidu_url_submitter</span>    <span class="comment"># 添加这一行</span></span><br></pre></td></tr></table></figure><p>如果有多个部署目标，确保每个 <code>type</code> 前都有 <code>- </code> 。</p><h3 id="5-验证配置"><a href="#5-验证配置" class="headerlink" title="5. 验证配置"></a>5. 验证配置</h3><p>执行部署命令：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">hexo clean &amp;&amp; hexo generate &amp;&amp; hexo deploy</span><br></pre></td></tr></table></figure><p>推送成功后会显示下述信息：</p><!-- 这是一张图片，ocr 内容为： --><p><img src="https://img.eucalyptus.cc/images/20260301/2.avif"></p><h2 id="手动提交"><a href="#手动提交" class="headerlink" title="手动提交"></a>手动提交</h2><p>手动提交可以参考之前写的这篇文章</p><div class='anzhiyu-tag-link'><a class="tag-Link"  href="/2023/05/08/%E7%BD%91%E7%AB%99%E6%8F%90%E4%BA%A4%E6%90%9C%E7%B4%A2%E5%BC%95%E6%93%8E/">    <div class="tag-link-tips">站内地址</div>    <div class="tag-link-bottom">        <div class="tag-link-left" data-icon="https://img.eucalyptus.cc/images/touxiang.avif" style="width:60px;height:60px;min-width:60px;min-height:60px;position:relative;border-radius:var(--anzhiyu-border-radius);overflow:hidden;">          <img class="tag-link-favicon" src="https://img.eucalyptus.cc/images/touxiang.avif" onerror="this.style.display='none';this.nextElementSibling.style.display='flex';" style="width:60px;height:60px;object-fit:cover;border-radius:var(--anzhiyu-border-radius);position:absolute;top:0;left:0;" alt="favicon"/>          <i class="fa-solid fa-link" style="display:flex;align-items:center;justify-content:center;width:100%;height:100%;"></i>        </div>        <div class="tag-link-right">            <div class="tag-link-title">网站提交搜索引擎</div>            <div class="tag-link-sitename">本站文章</div>        </div>        <i class="eucalyptus icon-angle-right"></i>    </div>    </a></div>]]></content:encoded>
      
      
      <category domain="https://blog.eucalyptus.cc/categories/Hexo/">Hexo</category>
      
      
      <category domain="https://blog.eucalyptus.cc/tags/SEO/">SEO</category>
      
      
      <comments>https://blog.eucalyptus.cc/2026/03/01/sitemap/#disqus_thread</comments>
      
    </item>
    
    <item>
      <title>Hexo中字体的引入与压缩</title>
      <link>https://blog.eucalyptus.cc/2026/02/13/%E5%AD%97%E4%BD%93%E7%9A%84%E5%BC%95%E5%85%A5%E4%B8%8E%E5%8E%8B%E7%BC%A9/</link>
      <guid>https://blog.eucalyptus.cc/2026/02/13/%E5%AD%97%E4%BD%93%E7%9A%84%E5%BC%95%E5%85%A5%E4%B8%8E%E5%8E%8B%E7%BC%A9/</guid>
      <pubDate>Fri, 13 Feb 2026 07:58:51 GMT</pubDate>
      
        
        
          
          
      <description>&lt;h2 id=&quot;引入tff字体&quot;&gt;&lt;a href=&quot;#引入tff字体&quot; class=&quot;headerlink&quot;</description>
          
        
      
      
      
      <content:encoded><![CDATA[<h2 id="引入tff字体"><a href="#引入tff字体" class="headerlink" title="引入tff字体"></a>引入tff字体</h2><blockquote><p>本文中涉及的文件没有的话再对应的目录下创建即可</p></blockquote><p>首先，找一个你喜欢的字体。你可以在一些网站上免费下载。下载完之后，把那个.ttf格式的文件放到你网站根目录下的&#x2F;source&#x2F;fonts文件夹里。</p><div class='anzhiyu-tag-link'><a class="tag-Link" target="_blank" rel="noopener external nofollow noreferrer" href="https://www.maoken.com/all-fonts">    <div class="tag-link-tips">引用站外地址</div>    <div class="tag-link-bottom">        <div class="tag-link-left" data-icon="https://www.maoken.com/favicon.ico" style="width:60px;height:60px;min-width:60px;min-height:60px;position:relative;border-radius:var(--anzhiyu-border-radius);overflow:hidden;">          <img class="tag-link-favicon" src="https://www.maoken.com/favicon.ico" onerror="this.style.display='none';this.nextElementSibling.style.display='flex';" style="width:60px;height:60px;object-fit:cover;border-radius:var(--anzhiyu-border-radius);position:absolute;top:0;left:0;" alt="favicon"/>          <i class="fa-solid fa-link" style="display:flex;align-items:center;justify-content:center;width:100%;height:100%;"></i>        </div>        <div class="tag-link-right">            <div class="tag-link-title">猫啃网 - 免费字体下载</div>            <div class="tag-link-sitename">可免费商用中文字体</div>        </div>        <i class="eucalyptus icon-angle-right"></i>    </div>    </a></div><p>接下来，你需要把下面的代码加到&#x2F;source&#x2F;css&#x2F;custom.css文件里，这样就可以引入字体了。</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">@font-face &#123;</span><br><span class="line">  font-family: <span class="string">&#x27;BailuFeiYun&#x27;</span>;</span><br><span class="line">  src: url(<span class="string">&#x27;/fonts/BailuFeiYu.ttf&#x27;</span>) format(<span class="string">&#x27;truetype&#x27;</span>); </span><br><span class="line">  font-weight: normal;</span><br><span class="line">  font-style: normal;</span><br><span class="line">  font-display: swap;  /* 关键：防止闪烁，先显示后备字体 */</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>记得在_config.anzhiyu.yml文件里加上custom.css，并且设置一下“# Global font settings”那部分。</p><figure class="highlight html"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">link</span> <span class="attr">rel</span>=<span class="string">&quot;stylesheet&quot;</span> <span class="attr">href</span>=<span class="string">&quot;/css/custom.css&quot;</span>&gt;</span></span><br></pre></td></tr></table></figure><p><img src="https://img.eucalyptus.cc/images/20260213/1.avif"></p><p><img src="https://img.eucalyptus.cc/images/20260213/2.avif"></p><p>最后一步就是清理缓存，然后重新生成就行了。</p><figure class="highlight html"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">hexo clean</span><br><span class="line">hexo generate</span><br></pre></td></tr></table></figure><h2 id="tff转换woff2"><a href="#tff转换woff2" class="headerlink" title="tff转换woff2"></a>tff转换woff2</h2><p>.ttf字体文件有时候会很大，这样会让网站加载变慢。为了解决这个问题，你可以把.ttf文件转换成.woff2格式，这种格式的文件更小，加载更快。你可以在一些免费的在线网站上完成这个转换。</p><div class='anzhiyu-tag-link'><a class="tag-Link" target="_blank" rel="noopener external nofollow noreferrer" href="https://cloudconvert.com/ttf-to-woff2">    <div class="tag-link-tips">引用站外地址</div>    <div class="tag-link-bottom">        <div class="tag-link-left" data-icon="https://cloudconvert.com/favicon.ico" style="width:60px;height:60px;min-width:60px;min-height:60px;position:relative;border-radius:var(--anzhiyu-border-radius);overflow:hidden;">          <img class="tag-link-favicon" src="https://cloudconvert.com/favicon.ico" onerror="this.style.display='none';this.nextElementSibling.style.display='flex';" style="width:60px;height:60px;object-fit:cover;border-radius:var(--anzhiyu-border-radius);position:absolute;top:0;left:0;" alt="favicon"/>          <i class="fa-solid fa-link" style="display:flex;align-items:center;justify-content:center;width:100%;height:100%;"></i>        </div>        <div class="tag-link-right">            <div class="tag-link-title">TTF to WOFF2 | CloudConvert</div>            <div class="tag-link-sitename">在线转换工具</div>        </div>        <i class="eucalyptus icon-angle-right"></i>    </div>    </a></div><p>记得改一下这里的代码。</p><figure class="highlight html"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line">@font-face &#123;</span><br><span class="line">  font-family: &#x27;BailuFeiYun&#x27;;</span><br><span class="line">  src: url(&#x27;/fonts/BailuFeiYu.woff2&#x27;) format(&#x27;woff2&#x27;),</span><br><span class="line">       url(&#x27;/fonts/BailuFeiYun.ttf&#x27;) format(&#x27;truetype&#x27;); </span><br><span class="line">  font-weight: normal;</span><br><span class="line">  font-style: normal;</span><br><span class="line">  font-display: swap;  /* 关键：防止闪烁，先显示后备字体 */</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="字体压缩"><a href="#字体压缩" class="headerlink" title="字体压缩"></a>字体压缩</h2><p>如果你引用了*.woff2格式的字体文件，但网站加载还是很慢，那可能就得考虑压缩字体了——可以用字蛛（font-spider）这个工具。</p><p>1、安装字蛛</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># 全局安装</span></span><br><span class="line">npm install font-spider -g</span><br><span class="line"></span><br><span class="line"><span class="comment"># 验证安装</span></span><br><span class="line">font-spider --version</span><br></pre></td></tr></table></figure><p>2、准备工作目录</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># 创建工作目录</span></span><br><span class="line"><span class="built_in">mkdir</span> font-spider-work</span><br><span class="line"><span class="built_in">cd</span> font-spider-work</span><br><span class="line"></span><br><span class="line"><span class="comment"># 创建必要文件</span></span><br><span class="line"><span class="built_in">touch</span> index.html</span><br><span class="line"><span class="built_in">mkdir</span> -p <span class="built_in">source</span>/fonts</span><br><span class="line"></span><br><span class="line"><span class="comment"># 放入原字体</span></span><br><span class="line"><span class="built_in">cp</span> /source/fonts/BailuFeiYun.ttf <span class="built_in">source</span>/fonts/</span><br></pre></td></tr></table></figure><p>3、建一个index.html文件，在里面放上你博客要用的所有字体。</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br></pre></td><td class="code"><pre><span class="line">&lt;!DOCTYPE html&gt;</span><br><span class="line">&lt;html&gt;</span><br><span class="line"></span><br><span class="line">&lt;<span class="built_in">head</span>&gt;</span><br><span class="line">    &lt;meta charset=<span class="string">&quot;UTF-8&quot;</span>&gt;</span><br><span class="line">    &lt;title&gt;Font Spider&lt;/title&gt;</span><br><span class="line">    &lt;style&gt;</span><br><span class="line">        @font-face &#123;</span><br><span class="line">            font-family: <span class="string">&#x27;BailuFeiYun&#x27;</span>;</span><br><span class="line">            src: url(<span class="string">&#x27;../source/fonts/BailuFeiYun.ttf&#x27;</span>) format(<span class="string">&#x27;truetype&#x27;</span>);</span><br><span class="line">            font-weight: normal;</span><br><span class="line">            font-style: normal;</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        .font-test &#123;</span><br><span class="line">            font-family: <span class="string">&#x27;BailuFeiYun&#x27;</span>;</span><br><span class="line">            font-size: 16px;</span><br><span class="line">            line-height: 1.8;</span><br><span class="line">        &#125;</span><br><span class="line">    &lt;/style&gt;</span><br><span class="line">&lt;/head&gt;</span><br><span class="line"></span><br><span class="line">&lt;body&gt;</span><br><span class="line">    &lt;div class=<span class="string">&quot;font-test&quot;</span>&gt;</span><br><span class="line">        &lt;!-- 把你博客的所有文字放这里 --&gt;</span><br><span class="line">        首页文章归档分类标签友链关于</span><br><span class="line">        技术分享HexoWordPress建站心路</span><br><span class="line">        上一篇下一篇打赏作者复制链接</span><br><span class="line">        猫笔千锤岁月长啃文万遍见真功</span><br><span class="line">        &lt;!-- 继续添加更多文字... --&gt;</span><br><span class="line"></span><br><span class="line">        &lt;!-- 常用标点符号 --&gt;</span><br><span class="line">        ，。！？、；：<span class="string">&quot; &quot;</span> <span class="string">&#x27; &#x27;</span> （）《》【】… — ～ · ｜</span><br><span class="line"></span><br><span class="line">        &lt;!-- 常用英文和数字 --&gt;</span><br><span class="line">        abcdefghijklmnopqrstuvwxyz ABCDEFGHIJKLMNOPQRSTUVWXYZ 0123456789</span><br><span class="line"></span><br><span class="line">        &lt;!-- 你的博客标题/昵称（如果有特殊字） --&gt;</span><br><span class="line">        安知鱼 白路飞云 手写体 博客 笔记 技术 生活 随笔</span><br><span class="line"></span><br><span class="line">        &lt;!-- 从你的文章提取的高频文字（复制几篇文章的标题和摘要） --&gt;</span><br><span class="line">        &lt;!-- 这里粘贴你几篇文章的标题和内容中的汉字 --&gt;</span><br><span class="line">    &lt;/div&gt;</span><br><span class="line">&lt;/body&gt;</span><br><span class="line"></span><br><span class="line">&lt;/html&gt;</span><br></pre></td></tr></table></figure><p>4、执行压缩</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">font-spider index.html</span><br></pre></td></tr></table></figure><p><img src="https://img.eucalyptus.cc/images/20260213/3.avif"></p><p>最后，只需要在custom.css文件里加入压缩后的字体就行了。</p><h2 id="压缩脚本"><a href="#压缩脚本" class="headerlink" title="压缩脚本"></a>压缩脚本</h2><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br><span class="line">89</span><br><span class="line">90</span><br><span class="line">91</span><br><span class="line">92</span><br><span class="line">93</span><br><span class="line">94</span><br><span class="line">95</span><br><span class="line">96</span><br><span class="line">97</span><br><span class="line">98</span><br><span class="line">99</span><br><span class="line">100</span><br><span class="line">101</span><br><span class="line">102</span><br><span class="line">103</span><br><span class="line">104</span><br><span class="line">105</span><br><span class="line">106</span><br><span class="line">107</span><br><span class="line">108</span><br><span class="line">109</span><br><span class="line">110</span><br><span class="line">111</span><br><span class="line">112</span><br><span class="line">113</span><br><span class="line">114</span><br><span class="line">115</span><br><span class="line">116</span><br><span class="line">117</span><br><span class="line">118</span><br><span class="line">119</span><br><span class="line">120</span><br><span class="line">121</span><br><span class="line">122</span><br><span class="line">123</span><br><span class="line">124</span><br><span class="line">125</span><br><span class="line">126</span><br><span class="line">127</span><br><span class="line">128</span><br><span class="line">129</span><br><span class="line">130</span><br><span class="line">131</span><br><span class="line">132</span><br><span class="line">133</span><br><span class="line">134</span><br><span class="line">135</span><br><span class="line">136</span><br><span class="line">137</span><br><span class="line">138</span><br><span class="line">139</span><br><span class="line">140</span><br><span class="line">141</span><br><span class="line">142</span><br><span class="line">143</span><br><span class="line">144</span><br><span class="line">145</span><br><span class="line">146</span><br><span class="line">147</span><br><span class="line">148</span><br><span class="line">149</span><br><span class="line">150</span><br><span class="line">151</span><br><span class="line">152</span><br><span class="line">153</span><br><span class="line">154</span><br><span class="line">155</span><br><span class="line">156</span><br><span class="line">157</span><br><span class="line">158</span><br><span class="line">159</span><br><span class="line">160</span><br><span class="line">161</span><br><span class="line">162</span><br><span class="line">163</span><br><span class="line">164</span><br><span class="line">165</span><br><span class="line">166</span><br><span class="line">167</span><br><span class="line">168</span><br><span class="line">169</span><br><span class="line">170</span><br><span class="line">171</span><br><span class="line">172</span><br><span class="line">173</span><br><span class="line">174</span><br><span class="line">175</span><br><span class="line">176</span><br><span class="line">177</span><br><span class="line">178</span><br><span class="line">179</span><br><span class="line">180</span><br><span class="line">181</span><br><span class="line">182</span><br><span class="line">183</span><br><span class="line">184</span><br><span class="line">185</span><br><span class="line">186</span><br><span class="line">187</span><br><span class="line">188</span><br><span class="line">189</span><br><span class="line">190</span><br><span class="line">191</span><br><span class="line">192</span><br><span class="line">193</span><br><span class="line">194</span><br><span class="line">195</span><br><span class="line">196</span><br><span class="line">197</span><br><span class="line">198</span><br><span class="line">199</span><br><span class="line">200</span><br><span class="line">201</span><br><span class="line">202</span><br><span class="line">203</span><br><span class="line">204</span><br><span class="line">205</span><br><span class="line">206</span><br><span class="line">207</span><br><span class="line">208</span><br><span class="line">209</span><br><span class="line">210</span><br><span class="line">211</span><br><span class="line">212</span><br><span class="line">213</span><br><span class="line">214</span><br><span class="line">215</span><br><span class="line">216</span><br><span class="line">217</span><br><span class="line">218</span><br><span class="line">219</span><br><span class="line">220</span><br><span class="line">221</span><br><span class="line">222</span><br><span class="line">223</span><br><span class="line">224</span><br><span class="line">225</span><br><span class="line">226</span><br><span class="line">227</span><br><span class="line">228</span><br><span class="line">229</span><br><span class="line">230</span><br><span class="line">231</span><br><span class="line">232</span><br><span class="line">233</span><br><span class="line">234</span><br><span class="line">235</span><br><span class="line">236</span><br><span class="line">237</span><br><span class="line">238</span><br><span class="line">239</span><br><span class="line">240</span><br><span class="line">241</span><br><span class="line">242</span><br><span class="line">243</span><br><span class="line">244</span><br><span class="line">245</span><br><span class="line">246</span><br><span class="line">247</span><br><span class="line">248</span><br><span class="line">249</span><br><span class="line">250</span><br><span class="line">251</span><br><span class="line">252</span><br><span class="line">253</span><br><span class="line">254</span><br><span class="line">255</span><br><span class="line">256</span><br><span class="line">257</span><br><span class="line">258</span><br><span class="line">259</span><br><span class="line">260</span><br><span class="line">261</span><br><span class="line">262</span><br><span class="line">263</span><br><span class="line">264</span><br><span class="line">265</span><br><span class="line">266</span><br><span class="line">267</span><br><span class="line">268</span><br><span class="line">269</span><br><span class="line">270</span><br><span class="line">271</span><br><span class="line">272</span><br><span class="line">273</span><br><span class="line">274</span><br><span class="line">275</span><br><span class="line">276</span><br><span class="line">277</span><br><span class="line">278</span><br><span class="line">279</span><br><span class="line">280</span><br><span class="line">281</span><br><span class="line">282</span><br><span class="line">283</span><br><span class="line">284</span><br><span class="line">285</span><br><span class="line">286</span><br><span class="line">287</span><br><span class="line">288</span><br><span class="line">289</span><br><span class="line">290</span><br><span class="line">291</span><br><span class="line">292</span><br><span class="line">293</span><br><span class="line">294</span><br><span class="line">295</span><br><span class="line">296</span><br><span class="line">297</span><br><span class="line">298</span><br><span class="line">299</span><br><span class="line">300</span><br><span class="line">301</span><br><span class="line">302</span><br><span class="line">303</span><br><span class="line">304</span><br><span class="line">305</span><br><span class="line">306</span><br><span class="line">307</span><br><span class="line">308</span><br><span class="line">309</span><br><span class="line">310</span><br><span class="line">311</span><br><span class="line">312</span><br><span class="line">313</span><br><span class="line">314</span><br><span class="line">315</span><br><span class="line">316</span><br><span class="line">317</span><br><span class="line">318</span><br><span class="line">319</span><br><span class="line">320</span><br><span class="line">321</span><br><span class="line">322</span><br><span class="line">323</span><br><span class="line">324</span><br><span class="line">325</span><br><span class="line">326</span><br><span class="line">327</span><br><span class="line">328</span><br><span class="line">329</span><br><span class="line">330</span><br><span class="line">331</span><br><span class="line">332</span><br><span class="line">333</span><br><span class="line">334</span><br><span class="line">335</span><br><span class="line">336</span><br><span class="line">337</span><br><span class="line">338</span><br><span class="line">339</span><br><span class="line">340</span><br><span class="line">341</span><br><span class="line">342</span><br><span class="line">343</span><br><span class="line">344</span><br><span class="line">345</span><br><span class="line">346</span><br><span class="line">347</span><br><span class="line">348</span><br><span class="line">349</span><br><span class="line">350</span><br><span class="line">351</span><br><span class="line">352</span><br><span class="line">353</span><br><span class="line">354</span><br><span class="line">355</span><br><span class="line">356</span><br><span class="line">357</span><br><span class="line">358</span><br><span class="line">359</span><br><span class="line">360</span><br><span class="line">361</span><br><span class="line">362</span><br><span class="line">363</span><br><span class="line">364</span><br><span class="line">365</span><br><span class="line">366</span><br><span class="line">367</span><br><span class="line">368</span><br><span class="line">369</span><br><span class="line">370</span><br><span class="line">371</span><br><span class="line">372</span><br><span class="line">373</span><br><span class="line">374</span><br><span class="line">375</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#!/usr/bin/env python3</span></span><br><span class="line"><span class="comment"># -*- coding: utf-8 -*-</span></span><br><span class="line"><span class="string">&quot;&quot;</span><span class="string">&quot;</span></span><br><span class="line"><span class="string">Hexo 博客字体压缩脚本 - 动态完整版</span></span><br><span class="line"><span class="string">扫描汉字、英文、数字、标点</span></span><br><span class="line"><span class="string">&quot;</span><span class="string">&quot;&quot;</span></span><br><span class="line"></span><br><span class="line">import os</span><br><span class="line">import re</span><br><span class="line">import sys</span><br><span class="line">import yaml</span><br><span class="line">import json</span><br><span class="line"></span><br><span class="line"></span><br><span class="line">def extract_chars_from_file(filepath, skip_comments=False):</span><br><span class="line">    <span class="string">&quot;&quot;</span><span class="string">&quot;从文件提取所有字符（汉字、英文、数字、标点）&quot;</span><span class="string">&quot;&quot;</span></span><br><span class="line">    try:</span><br><span class="line">        with open(filepath, <span class="string">&#x27;r&#x27;</span>, encoding=<span class="string">&#x27;utf-8&#x27;</span>, errors=<span class="string">&#x27;ignore&#x27;</span>) as f:</span><br><span class="line">            content = f.read()</span><br><span class="line">            </span><br><span class="line">            <span class="comment"># 如果需要过滤注释（YAML 文件）</span></span><br><span class="line">            <span class="keyword">if</span> skip_comments:</span><br><span class="line">                lines = content.split(<span class="string">&#x27;\n&#x27;</span>)</span><br><span class="line">                content = <span class="string">&#x27;\n&#x27;</span>.<span class="built_in">join</span>([line <span class="keyword">for</span> line <span class="keyword">in</span> lines <span class="keyword">if</span> not line.strip().startswith(<span class="string">&#x27;#&#x27;</span>)])</span><br><span class="line">            </span><br><span class="line">            chars = <span class="built_in">set</span>()</span><br><span class="line">            </span><br><span class="line">            <span class="comment"># 1. 汉字</span></span><br><span class="line">            chars.update(re.findall(r<span class="string">&#x27;[\u4e00-\u9fa5]&#x27;</span>, content))</span><br><span class="line">            </span><br><span class="line">            <span class="comment"># 2. 英文字母（大小写）</span></span><br><span class="line">            chars.update(re.findall(r<span class="string">&#x27;[a-zA-Z]&#x27;</span>, content))</span><br><span class="line">            </span><br><span class="line">            <span class="comment"># 3. 数字</span></span><br><span class="line">            chars.update(re.findall(r<span class="string">&#x27;[0-9]&#x27;</span>, content))</span><br><span class="line">            </span><br><span class="line">            <span class="comment"># 4. 常用标点符号</span></span><br><span class="line">            <span class="comment"># 中文标点</span></span><br><span class="line">            chars.update(re.findall(r<span class="string">&#x27;[，。！？、；：&quot;&quot;&#x27;</span><span class="string">&#x27;（）《》【】…—～·｜「」『』【】]&#x27;</span>, content))</span><br><span class="line">            <span class="comment"># 英文标点</span></span><br><span class="line">            chars.update(re.findall(r<span class="string">&#x27;[.,!?;:\&#x27;</span>\&quot;()\[\]&#123;&#125;@#$%^&amp;*+=_\-\\/&lt;&gt;~`]<span class="string">&#x27;, content))</span></span><br><span class="line"><span class="string">            </span></span><br><span class="line"><span class="string">            return chars</span></span><br><span class="line"><span class="string">            </span></span><br><span class="line"><span class="string">    except Exception as e:</span></span><br><span class="line"><span class="string">        print(f&quot;读取文件失败 &#123;filepath&#125;: &#123;e&#125;&quot;)</span></span><br><span class="line"><span class="string">        return set()</span></span><br><span class="line"><span class="string"></span></span><br><span class="line"><span class="string"></span></span><br><span class="line"><span class="string">def extract_chars_from_json_yaml(filepath):</span></span><br><span class="line"><span class="string">    &quot;&quot;&quot;从 JSON/YAML 文件递归提取所有字符串中的字符&quot;&quot;&quot;</span></span><br><span class="line"><span class="string">    chars = set()</span></span><br><span class="line"><span class="string">    try:</span></span><br><span class="line"><span class="string">        with open(filepath, &#x27;</span>r<span class="string">&#x27;, encoding=&#x27;</span>utf-8<span class="string">&#x27;, errors=&#x27;</span>ignore<span class="string">&#x27;) as f:</span></span><br><span class="line"><span class="string">            content = f.read()</span></span><br><span class="line"><span class="string">            </span></span><br><span class="line"><span class="string">            # 尝试解析为 JSON 或 YAML</span></span><br><span class="line"><span class="string">            data = None</span></span><br><span class="line"><span class="string">            try:</span></span><br><span class="line"><span class="string">                data = json.loads(content)</span></span><br><span class="line"><span class="string">            except:</span></span><br><span class="line"><span class="string">                try:</span></span><br><span class="line"><span class="string">                    data = yaml.safe_load(content)</span></span><br><span class="line"><span class="string">                except:</span></span><br><span class="line"><span class="string">                    pass</span></span><br><span class="line"><span class="string">            </span></span><br><span class="line"><span class="string">            def extract_strings(obj):</span></span><br><span class="line"><span class="string">                if isinstance(obj, str):</span></span><br><span class="line"><span class="string">                    result = set()</span></span><br><span class="line"><span class="string">                    # 汉字</span></span><br><span class="line"><span class="string">                    result.update(re.findall(r&#x27;</span>[\u4e00-\u9fa5]<span class="string">&#x27;, obj))</span></span><br><span class="line"><span class="string">                    # 英文</span></span><br><span class="line"><span class="string">                    result.update(re.findall(r&#x27;</span>[a-zA-Z]<span class="string">&#x27;, obj))</span></span><br><span class="line"><span class="string">                    # 数字</span></span><br><span class="line"><span class="string">                    result.update(re.findall(r&#x27;</span>[0-9]<span class="string">&#x27;, obj))</span></span><br><span class="line"><span class="string">                    # 标点</span></span><br><span class="line"><span class="string">                    result.update(re.findall(r&#x27;</span>[，。！？、；：<span class="string">&quot;&quot;</span><span class="string">&#x27;&#x27;</span>（）《》【】…—～·｜「」『』【】.,!?;:\&#x27;\&quot;()\[\]&#123;&#125;@#$%^&amp;*+=_\-\\/&lt;&gt;~`]<span class="string">&#x27;, obj))</span></span><br><span class="line"><span class="string">                    return result</span></span><br><span class="line"><span class="string">                elif isinstance(obj, list):</span></span><br><span class="line"><span class="string">                    result = set()</span></span><br><span class="line"><span class="string">                    for item in obj:</span></span><br><span class="line"><span class="string">                        result.update(extract_strings(item))</span></span><br><span class="line"><span class="string">                    return result</span></span><br><span class="line"><span class="string">                elif isinstance(obj, dict):</span></span><br><span class="line"><span class="string">                    result = set()</span></span><br><span class="line"><span class="string">                    for key, value in obj.items():</span></span><br><span class="line"><span class="string">                        result.update(extract_strings(key))</span></span><br><span class="line"><span class="string">                        result.update(extract_strings(value))</span></span><br><span class="line"><span class="string">                    return result</span></span><br><span class="line"><span class="string">                return set()</span></span><br><span class="line"><span class="string">            </span></span><br><span class="line"><span class="string">            if data:</span></span><br><span class="line"><span class="string">                chars.update(extract_strings(data))</span></span><br><span class="line"><span class="string">            else:</span></span><br><span class="line"><span class="string">                # 解析失败，直接正则提取</span></span><br><span class="line"><span class="string">                chars.update(extract_chars_from_file(filepath))</span></span><br><span class="line"><span class="string">                </span></span><br><span class="line"><span class="string">    except Exception as e:</span></span><br><span class="line"><span class="string">        print(f&quot;读取失败 &#123;filepath&#125;: &#123;e&#125;&quot;)</span></span><br><span class="line"><span class="string">    </span></span><br><span class="line"><span class="string">    return chars</span></span><br><span class="line"><span class="string"></span></span><br><span class="line"><span class="string"></span></span><br><span class="line"><span class="string">def extract_title_from_md(filepath):</span></span><br><span class="line"><span class="string">    &quot;&quot;&quot;只提取 Markdown 文件的 Front-matter title&quot;&quot;&quot;</span></span><br><span class="line"><span class="string">    try:</span></span><br><span class="line"><span class="string">        with open(filepath, &#x27;</span>r<span class="string">&#x27;, encoding=&#x27;</span>utf-8<span class="string">&#x27;, errors=&#x27;</span>ignore<span class="string">&#x27;) as f:</span></span><br><span class="line"><span class="string">            content = f.read()</span></span><br><span class="line"><span class="string">            </span></span><br><span class="line"><span class="string">            # 匹配 Front-matter</span></span><br><span class="line"><span class="string">            fm_match = re.match(r&#x27;</span>^---\s*\n(.*?)\n---<span class="string">&#x27;, content, re.DOTALL)</span></span><br><span class="line"><span class="string">            title = &#x27;</span><span class="string">&#x27;</span></span><br><span class="line"><span class="string">            if fm_match:</span></span><br><span class="line"><span class="string">                try:</span></span><br><span class="line"><span class="string">                    fm = yaml.safe_load(fm_match.group(1))</span></span><br><span class="line"><span class="string">                    title = str(fm.get(&#x27;</span>title<span class="string">&#x27;, &#x27;</span><span class="string">&#x27;))</span></span><br><span class="line"><span class="string">                except:</span></span><br><span class="line"><span class="string">                    pass</span></span><br><span class="line"><span class="string">            </span></span><br><span class="line"><span class="string">            # 备用：正则提取 title: xxx</span></span><br><span class="line"><span class="string">            if not title:</span></span><br><span class="line"><span class="string">                title_match = re.search(r&#x27;</span>^title:\s*(.+)$<span class="string">&#x27;, content, re.MULTILINE)</span></span><br><span class="line"><span class="string">                if title_match:</span></span><br><span class="line"><span class="string">                    title = title_match.group(1).strip().strip(&#x27;</span><span class="string">&quot;\&#x27;&#x27;)</span></span><br><span class="line"><span class="string">            </span></span><br><span class="line"><span class="string">            # 提取所有字符</span></span><br><span class="line"><span class="string">            chars = set()</span></span><br><span class="line"><span class="string">            chars.update(re.findall(r&#x27;[\u4e00-\u9fa5]&#x27;, title))</span></span><br><span class="line"><span class="string">            chars.update(re.findall(r&#x27;[a-zA-Z]&#x27;, title))</span></span><br><span class="line"><span class="string">            chars.update(re.findall(r&#x27;[0-9]&#x27;, title))</span></span><br><span class="line"><span class="string">            chars.update(re.findall(r&#x27;[，。！？、；：&quot;</span><span class="string">&quot;&#x27;&#x27;（）《》【】…—～·｜「」『』【】.,!?;:\&#x27;\&quot;()\[\]&#123;&#125;@#$%^&amp;*+=_\-\\/&lt;&gt;~`]&#x27;, title))</span></span><br><span class="line"><span class="string">            </span></span><br><span class="line"><span class="string">            return chars</span></span><br><span class="line"><span class="string">            </span></span><br><span class="line"><span class="string">    except Exception as e:</span></span><br><span class="line"><span class="string">        print(f&quot;</span>读取失败 &#123;filepath&#125;: &#123;e&#125;<span class="string">&quot;)</span></span><br><span class="line"><span class="string">        return set()</span></span><br><span class="line"><span class="string"></span></span><br><span class="line"><span class="string"></span></span><br><span class="line"><span class="string">def get_dynamic_fixed_chars(hexo_root):</span></span><br><span class="line"><span class="string">    &quot;</span><span class="string">&quot;&quot;</span>从配置文件动态读取固定字符<span class="string">&quot;&quot;</span><span class="string">&quot;</span></span><br><span class="line"><span class="string">    chars = set()</span></span><br><span class="line"><span class="string">    </span></span><br><span class="line"><span class="string">    # 1. 从站点配置 _config.yml 读取</span></span><br><span class="line"><span class="string">    site_config = os.path.join(hexo_root, &#x27;_config.yml&#x27;)</span></span><br><span class="line"><span class="string">    if os.path.exists(site_config):</span></span><br><span class="line"><span class="string">        print(f&quot;</span>  读取站点配置: _config.yml<span class="string">&quot;)</span></span><br><span class="line"><span class="string">        config_chars = extract_chars_from_file(site_config, skip_comments=True)</span></span><br><span class="line"><span class="string">        chars.update(config_chars)</span></span><br><span class="line"><span class="string">        print(f&quot;</span>    -&gt; &#123;len(config_chars)&#125; 字符<span class="string">&quot;)</span></span><br><span class="line"><span class="string">    </span></span><br><span class="line"><span class="string">    # 2. 从主题配置 _config.anzhiyu.yml 读取（过滤注释）</span></span><br><span class="line"><span class="string">    theme_config = os.path.join(hexo_root, &#x27;_config.anzhiyu.yml&#x27;)</span></span><br><span class="line"><span class="string">    if os.path.exists(theme_config):</span></span><br><span class="line"><span class="string">        print(f&quot;</span>  读取主题配置: _config.anzhiyu.yml<span class="string">&quot;)</span></span><br><span class="line"><span class="string">        config_chars = extract_chars_from_file(theme_config, skip_comments=True)</span></span><br><span class="line"><span class="string">        chars.update(config_chars)</span></span><br><span class="line"><span class="string">        print(f&quot;</span>    -&gt; &#123;len(config_chars)&#125; 字符<span class="string">&quot;)</span></span><br><span class="line"><span class="string">    </span></span><br><span class="line"><span class="string">    # 3. 从主题语言文件读取</span></span><br><span class="line"><span class="string">    theme_lang_dir = os.path.join(hexo_root, &#x27;themes&#x27;, &#x27;anzhiyu&#x27;, &#x27;languages&#x27;)</span></span><br><span class="line"><span class="string">    if os.path.exists(theme_lang_dir):</span></span><br><span class="line"><span class="string">        print(f&quot;</span>  读取主题语言文件:<span class="string">&quot;)</span></span><br><span class="line"><span class="string">        for lang_file in os.listdir(theme_lang_dir):</span></span><br><span class="line"><span class="string">            if lang_file.endswith((&#x27;.yml&#x27;, &#x27;.yaml&#x27;)):</span></span><br><span class="line"><span class="string">                filepath = os.path.join(theme_lang_dir, lang_file)</span></span><br><span class="line"><span class="string">                lang_chars = extract_chars_from_file(filepath, skip_comments=True)</span></span><br><span class="line"><span class="string">                if lang_chars:</span></span><br><span class="line"><span class="string">                    chars.update(lang_chars)</span></span><br><span class="line"><span class="string">                    print(f&quot;</span>    &#123;lang_file&#125; -&gt; &#123;len(lang_chars)&#125; 字符<span class="string">&quot;)</span></span><br><span class="line"><span class="string">    </span></span><br><span class="line"><span class="string">    return chars</span></span><br><span class="line"><span class="string"></span></span><br><span class="line"><span class="string"></span></span><br><span class="line"><span class="string">def scan_directory_full(directory, chars_set, skip_dirs=None, depth=0):</span></span><br><span class="line"><span class="string">    &quot;</span><span class="string">&quot;&quot;</span>递归扫描目录下所有文件<span class="string">&quot;&quot;</span><span class="string">&quot;</span></span><br><span class="line"><span class="string">    if skip_dirs is None:</span></span><br><span class="line"><span class="string">        skip_dirs = [&#x27;node_modules&#x27;, &#x27;.git&#x27;, &#x27;images&#x27;, &#x27;img&#x27;, &#x27;fonts&#x27;, &#x27;css&#x27;, &#x27;js&#x27;]</span></span><br><span class="line"><span class="string">    </span></span><br><span class="line"><span class="string">    if not os.path.exists(directory):</span></span><br><span class="line"><span class="string">        return 0, 0</span></span><br><span class="line"><span class="string">    </span></span><br><span class="line"><span class="string">    file_count = 0</span></span><br><span class="line"><span class="string">    total_chars = 0</span></span><br><span class="line"><span class="string">    </span></span><br><span class="line"><span class="string">    prefix = &quot;</span>  <span class="string">&quot; * depth</span></span><br><span class="line"><span class="string">    </span></span><br><span class="line"><span class="string">    for root, dirs, files in os.walk(directory):</span></span><br><span class="line"><span class="string">        dirs[:] = [d for d in dirs if d not in skip_dirs and not d.startswith(&#x27;.&#x27;)]</span></span><br><span class="line"><span class="string">        </span></span><br><span class="line"><span class="string">        for file in files:</span></span><br><span class="line"><span class="string">            filepath = os.path.join(root, file)</span></span><br><span class="line"><span class="string">            rel_path = os.path.relpath(filepath, directory)</span></span><br><span class="line"><span class="string">            </span></span><br><span class="line"><span class="string">            file_chars = set()</span></span><br><span class="line"><span class="string">            </span></span><br><span class="line"><span class="string">            if file.endswith(&#x27;.md&#x27;):</span></span><br><span class="line"><span class="string">                file_chars = extract_chars_from_file(filepath)</span></span><br><span class="line"><span class="string">            elif file.endswith((&#x27;.json&#x27;, &#x27;.yml&#x27;, &#x27;.yaml&#x27;)):</span></span><br><span class="line"><span class="string">                file_chars = extract_chars_from_json_yaml(filepath)</span></span><br><span class="line"><span class="string">            elif file.endswith((&#x27;.html&#x27;, &#x27;.htm&#x27;, &#x27;.txt&#x27;)):</span></span><br><span class="line"><span class="string">                file_chars = extract_chars_from_file(filepath)</span></span><br><span class="line"><span class="string">            </span></span><br><span class="line"><span class="string">            if file_chars:</span></span><br><span class="line"><span class="string">                chars_set.update(file_chars)</span></span><br><span class="line"><span class="string">                file_count += 1</span></span><br><span class="line"><span class="string">                total_chars += len(file_chars)</span></span><br><span class="line"><span class="string">                print(f&quot;</span>&#123;prefix&#125;  &#123;rel_path&#125; -&gt; &#123;len(file_chars)&#125; 字符<span class="string">&quot;)</span></span><br><span class="line"><span class="string">    </span></span><br><span class="line"><span class="string">    return file_count, total_chars</span></span><br><span class="line"><span class="string"></span></span><br><span class="line"><span class="string"></span></span><br><span class="line"><span class="string">def generate_char_file(output_path, hexo_root):</span></span><br><span class="line"><span class="string">    &quot;</span><span class="string">&quot;&quot;</span>生成字符文件<span class="string">&quot;&quot;</span><span class="string">&quot;</span></span><br><span class="line"><span class="string">    print(&quot;</span>=<span class="string">&quot; * 60)</span></span><br><span class="line"><span class="string">    print(&quot;</span>Hexo 博客字体压缩工具 - 动态完整版<span class="string">&quot;)</span></span><br><span class="line"><span class="string">    print(&quot;</span>=<span class="string">&quot; * 60)</span></span><br><span class="line"><span class="string">    </span></span><br><span class="line"><span class="string">    chars = set()</span></span><br><span class="line"><span class="string">    </span></span><br><span class="line"><span class="string">    # 1. 文章目录：只扫描标题</span></span><br><span class="line"><span class="string">    posts_dir = os.path.join(hexo_root, &#x27;source&#x27;, &#x27;_posts&#x27;)</span></span><br><span class="line"><span class="string">    if os.path.exists(posts_dir):</span></span><br><span class="line"><span class="string">        print(f&quot;</span>\n📁 扫描文章标题(仅title): &#123;posts_dir&#125;<span class="string">&quot;)</span></span><br><span class="line"><span class="string">        count = 0</span></span><br><span class="line"><span class="string">        for root, dirs, files in os.walk(posts_dir):</span></span><br><span class="line"><span class="string">            dirs[:] = [d for d in dirs if d not in [&#x27;node_modules&#x27;, &#x27;.git&#x27;]]</span></span><br><span class="line"><span class="string">            for file in files:</span></span><br><span class="line"><span class="string">                if file.endswith(&#x27;.md&#x27;):</span></span><br><span class="line"><span class="string">                    filepath = os.path.join(root, file)</span></span><br><span class="line"><span class="string">                    title_chars = extract_title_from_md(filepath)</span></span><br><span class="line"><span class="string">                    chars.update(title_chars)</span></span><br><span class="line"><span class="string">                    count += 1</span></span><br><span class="line"><span class="string">        print(f&quot;</span>  扫描了 &#123;count&#125; 篇文章，提取 &#123;len(chars)&#125; 个独特字符<span class="string">&quot;)</span></span><br><span class="line"><span class="string">    </span></span><br><span class="line"><span class="string">    # 2. 其他页面目录：全内容扫描</span></span><br><span class="line"><span class="string">    source_dir = os.path.join(hexo_root, &#x27;source&#x27;)</span></span><br><span class="line"><span class="string">    if os.path.exists(source_dir):</span></span><br><span class="line"><span class="string">        print(f&quot;</span>\n📁 扫描页面目录(全内容递归):<span class="string">&quot;)</span></span><br><span class="line"><span class="string">        </span></span><br><span class="line"><span class="string">        for item in os.listdir(source_dir):</span></span><br><span class="line"><span class="string">            if item in [&#x27;_posts&#x27;, &#x27;images&#x27;, &#x27;img&#x27;, &#x27;fonts&#x27;, &#x27;css&#x27;, &#x27;js&#x27;]:</span></span><br><span class="line"><span class="string">                continue</span></span><br><span class="line"><span class="string">            </span></span><br><span class="line"><span class="string">            item_path = os.path.join(source_dir, item)</span></span><br><span class="line"><span class="string">            </span></span><br><span class="line"><span class="string">            if os.path.isdir(item_path):</span></span><br><span class="line"><span class="string">                print(f&quot;</span>\n  📂 &#123;item&#125;/<span class="string">&quot;)</span></span><br><span class="line"><span class="string">                file_count, total_chars = scan_directory_full(item_path, chars, depth=1)</span></span><br><span class="line"><span class="string">                print(f&quot;</span>    合计: &#123;file_count&#125; 个文件，&#123;total_chars&#125; 字符<span class="string">&quot;)</span></span><br><span class="line"><span class="string">    </span></span><br><span class="line"><span class="string">    # 3. 扫描 _data 目录</span></span><br><span class="line"><span class="string">    data_dir = os.path.join(hexo_root, &#x27;source&#x27;, &#x27;_data&#x27;)</span></span><br><span class="line"><span class="string">    if os.path.exists(data_dir):</span></span><br><span class="line"><span class="string">        print(f&quot;</span>\n📁 扫描数据目录(_data):<span class="string">&quot;)</span></span><br><span class="line"><span class="string">        file_count, total_chars = scan_directory_full(data_dir, chars, depth=1)</span></span><br><span class="line"><span class="string">        print(f&quot;</span>    合计: &#123;file_count&#125; 个文件，&#123;total_chars&#125; 字符<span class="string">&quot;)</span></span><br><span class="line"><span class="string">    </span></span><br><span class="line"><span class="string">    # 4. 动态读取配置文件</span></span><br><span class="line"><span class="string">    print(f&quot;</span>\n📁 动态读取配置文件:<span class="string">&quot;)</span></span><br><span class="line"><span class="string">    fixed_chars = get_dynamic_fixed_chars(hexo_root)</span></span><br><span class="line"><span class="string">    chars.update(fixed_chars)</span></span><br><span class="line"><span class="string">    print(f&quot;</span>  配置文件共提取: &#123;len(fixed_chars)&#125; 字符<span class="string">&quot;)</span></span><br><span class="line"><span class="string">    </span></span><br><span class="line"><span class="string">    # 5. 排序并保存</span></span><br><span class="line"><span class="string">    # 按字符类型排序：汉字 -&gt; 英文 -&gt; 数字 -&gt; 标点</span></span><br><span class="line"><span class="string">    hanzi = sorted([c for c in chars if &#x27;\u4e00&#x27; &lt;= c &lt;= &#x27;\u9fa5&#x27;])</span></span><br><span class="line"><span class="string">    english = sorted([c for c in chars if c.isalpha()])</span></span><br><span class="line"><span class="string">    digits = sorted([c for c in chars if c.isdigit()])</span></span><br><span class="line"><span class="string">    others = sorted([c for c in chars if not (&#x27;\u4e00&#x27; &lt;= c &lt;= &#x27;\u9fa5&#x27; or c.isalnum())])</span></span><br><span class="line"><span class="string">    </span></span><br><span class="line"><span class="string">    sorted_chars = &#x27;&#x27;.join(hanzi + english + digits + others)</span></span><br><span class="line"><span class="string">    </span></span><br><span class="line"><span class="string">    with open(output_path, &#x27;w&#x27;, encoding=&#x27;utf-8&#x27;) as f:</span></span><br><span class="line"><span class="string">        f.write(sorted_chars)</span></span><br><span class="line"><span class="string">    </span></span><br><span class="line"><span class="string">    print(f&quot;</span>\n<span class="string">&quot; + &quot;</span>=<span class="string">&quot; * 60)</span></span><br><span class="line"><span class="string">    print(f&quot;</span>✅ 共收集 &#123;len(chars)&#125; 个独特字符<span class="string">&quot;)</span></span><br><span class="line"><span class="string">    print(f&quot;</span>   - 汉字: &#123;len(hanzi)&#125; 个<span class="string">&quot;)</span></span><br><span class="line"><span class="string">    print(f&quot;</span>   - 英文: &#123;len(english)&#125; 个<span class="string">&quot;)</span></span><br><span class="line"><span class="string">    print(f&quot;</span>   - 数字: &#123;len(digits)&#125; 个<span class="string">&quot;)</span></span><br><span class="line"><span class="string">    print(f&quot;</span>   - 标点/其他: &#123;len(others)&#125; 个<span class="string">&quot;)</span></span><br><span class="line"><span class="string">    print(f&quot;</span>💾 已保存到: &#123;output_path&#125;<span class="string">&quot;)</span></span><br><span class="line"><span class="string">    </span></span><br><span class="line"><span class="string">    preview = sorted_chars[:100]</span></span><br><span class="line"><span class="string">    print(f&quot;</span>\n📋 字符预览: &#123;preview&#125;...<span class="string">&quot;)</span></span><br><span class="line"><span class="string">    </span></span><br><span class="line"><span class="string">    return sorted_chars</span></span><br><span class="line"><span class="string"></span></span><br><span class="line"><span class="string"></span></span><br><span class="line"><span class="string">def subset_font(input_font, output_font, chars_file):</span></span><br><span class="line"><span class="string">    &quot;</span><span class="string">&quot;&quot;</span>使用 fonttools 子集化字体<span class="string">&quot;&quot;</span><span class="string">&quot;</span></span><br><span class="line"><span class="string">    try:</span></span><br><span class="line"><span class="string">        from fontTools.subset import main as subset_main</span></span><br><span class="line"><span class="string">    except ImportError:</span></span><br><span class="line"><span class="string">        print(&quot;</span>\n❌ 缺少 fonttools，正在安装...<span class="string">&quot;)</span></span><br><span class="line"><span class="string">        os.system(f&quot;</span>&#123;sys.executable&#125; -m pip install fonttools brotli zopfli<span class="string">&quot;)</span></span><br><span class="line"><span class="string">        from fontTools.subset import main as subset_main</span></span><br><span class="line"><span class="string">    </span></span><br><span class="line"><span class="string">    print(f&quot;</span>\n🔧 开始压缩字体...<span class="string">&quot;)</span></span><br><span class="line"><span class="string">    print(f&quot;</span>   输入: &#123;input_font&#125;<span class="string">&quot;)</span></span><br><span class="line"><span class="string">    print(f&quot;</span>   输出: &#123;output_font&#125;<span class="string">&quot;)</span></span><br><span class="line"><span class="string">    </span></span><br><span class="line"><span class="string">    args = [</span></span><br><span class="line"><span class="string">        input_font,</span></span><br><span class="line"><span class="string">        f&quot;</span>--text-file=&#123;chars_file&#125;<span class="string">&quot;,</span></span><br><span class="line"><span class="string">        f&quot;</span>--output-file=&#123;output_font&#125;<span class="string">&quot;,</span></span><br><span class="line"><span class="string">        &quot;</span>--flavor=woff2<span class="string">&quot;,</span></span><br><span class="line"><span class="string">        &quot;</span>--desubroutinize<span class="string">&quot;,</span></span><br><span class="line"><span class="string">        &quot;</span>--recalc-bounds<span class="string">&quot;,</span></span><br><span class="line"><span class="string">        &quot;</span>--canonical-order<span class="string">&quot;,</span></span><br><span class="line"><span class="string">        &quot;</span>--layout-features=*<span class="string">&quot;,</span></span><br><span class="line"><span class="string">    ]</span></span><br><span class="line"><span class="string">    </span></span><br><span class="line"><span class="string">    ttf_output = output_font.replace(&#x27;.woff2&#x27;, &#x27;.ttf&#x27;)</span></span><br><span class="line"><span class="string">    args_ttf = [</span></span><br><span class="line"><span class="string">        input_font,</span></span><br><span class="line"><span class="string">        f&quot;</span>--text-file=&#123;chars_file&#125;<span class="string">&quot;,</span></span><br><span class="line"><span class="string">        f&quot;</span>--output-file=&#123;ttf_output&#125;<span class="string">&quot;,</span></span><br><span class="line"><span class="string">        &quot;</span>--desubroutinize<span class="string">&quot;,</span></span><br><span class="line"><span class="string">        &quot;</span>--recalc-bounds<span class="string">&quot;,</span></span><br><span class="line"><span class="string">    ]</span></span><br><span class="line"><span class="string">    </span></span><br><span class="line"><span class="string">    try:</span></span><br><span class="line"><span class="string">        subset_main(args)</span></span><br><span class="line"><span class="string">        subset_main(args_ttf)</span></span><br><span class="line"><span class="string">        </span></span><br><span class="line"><span class="string">        original_size = os.path.getsize(input_font)</span></span><br><span class="line"><span class="string">        woff2_size = os.path.getsize(output_font)</span></span><br><span class="line"><span class="string">        ttf_size = os.path.getsize(ttf_output)</span></span><br><span class="line"><span class="string">        </span></span><br><span class="line"><span class="string">        print(f&quot;</span>\n✅ 压缩完成!<span class="string">&quot;)</span></span><br><span class="line"><span class="string">        print(f&quot;</span>   原始大小: &#123;original_size/1024:.2f&#125; KB<span class="string">&quot;)</span></span><br><span class="line"><span class="string">        print(f&quot;</span>   WOFF2 大小: &#123;woff2_size/1024:.2f&#125; KB (压缩率: &#123;woff2_size/original_size*100:.1f&#125;%)<span class="string">&quot;)</span></span><br><span class="line"><span class="string">        print(f&quot;</span>   TTF 大小: &#123;ttf_size/1024:.2f&#125; KB (压缩率: &#123;ttf_size/original_size*100:.1f&#125;%)<span class="string">&quot;)</span></span><br><span class="line"><span class="string">        </span></span><br><span class="line"><span class="string">        return True</span></span><br><span class="line"><span class="string">        </span></span><br><span class="line"><span class="string">    except Exception as e:</span></span><br><span class="line"><span class="string">        print(f&quot;</span>\n❌ 压缩失败: &#123;e&#125;<span class="string">&quot;)</span></span><br><span class="line"><span class="string">        return False</span></span><br><span class="line"><span class="string"></span></span><br><span class="line"><span class="string"></span></span><br><span class="line"><span class="string">def main():</span></span><br><span class="line"><span class="string">    &quot;</span><span class="string">&quot;&quot;</span>主函数<span class="string">&quot;&quot;</span><span class="string">&quot;</span></span><br><span class="line"><span class="string">    HEXO_ROOT = &quot;</span>/www/wwwroot/myblog<span class="string">&quot;</span></span><br><span class="line"><span class="string">    WORK_DIR = &quot;</span>/www/wwwroot/myblog/font-spider-work<span class="string">&quot;</span></span><br><span class="line"><span class="string">    INPUT_FONT = os.path.join(WORK_DIR, &quot;</span>fonts<span class="string">&quot;, &quot;</span>BailuFeiYun.ttf<span class="string">&quot;)</span></span><br><span class="line"><span class="string">    </span></span><br><span class="line"><span class="string">    CHARS_FILE = os.path.join(WORK_DIR, &quot;</span>chars-full.txt<span class="string">&quot;)</span></span><br><span class="line"><span class="string">    OUTPUT_WOFF2 = os.path.join(HEXO_ROOT, &quot;</span><span class="built_in">source</span><span class="string">&quot;, &quot;</span>fonts<span class="string">&quot;, &quot;</span>BailuFeiYun.woff2<span class="string">&quot;)</span></span><br><span class="line"><span class="string">    </span></span><br><span class="line"><span class="string">    if not os.path.exists(INPUT_FONT):</span></span><br><span class="line"><span class="string">        print(f&quot;</span>❌ 未找到原始字体: &#123;INPUT_FONT&#125;<span class="string">&quot;)</span></span><br><span class="line"><span class="string">        sys.exit(1)</span></span><br><span class="line"><span class="string">    </span></span><br><span class="line"><span class="string">    os.makedirs(os.path.dirname(OUTPUT_WOFF2), exist_ok=True)</span></span><br><span class="line"><span class="string">    </span></span><br><span class="line"><span class="string">    chars = generate_char_file(CHARS_FILE, HEXO_ROOT)</span></span><br><span class="line"><span class="string">    </span></span><br><span class="line"><span class="string">    if len(chars) &lt; 50:</span></span><br><span class="line"><span class="string">        print(&quot;</span>⚠️  提取字符过少，请检查路径<span class="string">&quot;)</span></span><br><span class="line"><span class="string">        sys.exit(1)</span></span><br><span class="line"><span class="string">    </span></span><br><span class="line"><span class="string">    success = subset_font(INPUT_FONT, OUTPUT_WOFF2, CHARS_FILE)</span></span><br><span class="line"><span class="string">    </span></span><br><span class="line"><span class="string">    if success:</span></span><br><span class="line"><span class="string">        print(f&quot;</span>\n🎉 全部完成!<span class="string">&quot;)</span></span><br><span class="line"><span class="string">        print(f&quot;</span>   压缩后的字体已输出到: &#123;os.path.dirname(OUTPUT_WOFF2)&#125;<span class="string">&quot;)</span></span><br><span class="line"><span class="string">    else:</span></span><br><span class="line"><span class="string">        sys.exit(1)</span></span><br><span class="line"><span class="string"></span></span><br><span class="line"><span class="string"></span></span><br><span class="line"><span class="string">if __name__ == &quot;</span>__main__<span class="string">&quot;:</span></span><br><span class="line"><span class="string">    main()</span></span><br></pre></td></tr></table></figure><h3 id="工作流程"><a href="#工作流程" class="headerlink" title="工作流程"></a>工作流程</h3><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line">1. 扫描文章标题 ──┐</span><br><span class="line">2. 扫描页面内容 ──┤</span><br><span class="line">3. 扫描 _data 目录 ─┼──&gt; 收集所有字符 ──&gt; 去重排序 ──&gt; 生成字符文件</span><br><span class="line">4. 扫描配置文件 ──┤</span><br><span class="line">5. 扫描语言文件 ──┘</span><br><span class="line"></span><br><span class="line">6. 使用 fonttools 压缩字体</span><br><span class="line">   ├── 输入：原始 TTF 字体</span><br><span class="line">   ├── 根据字符文件裁剪</span><br><span class="line">   └── 输出：WOFF2 + TTF（子集化后）</span><br></pre></td></tr></table></figure><h3 id="输出示例"><a href="#输出示例" class="headerlink" title="输出示例"></a>输出示例</h3><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br><span class="line">89</span><br><span class="line">90</span><br></pre></td><td class="code"><pre><span class="line">============================================================</span><br><span class="line">Hexo 博客字体压缩工具 - 动态完整版</span><br><span class="line">============================================================</span><br><span class="line"></span><br><span class="line">📁 扫描文章标题(仅title): /www/wwwroot/myblog/source/_posts</span><br><span class="line">  扫描了 17 篇文章，提取 143 个独特字符</span><br><span class="line"></span><br><span class="line">📁 扫描页面目录(全内容递归):</span><br><span class="line"></span><br><span class="line">  📂 charts/</span><br><span class="line">    index.md -&gt; 67 字符</span><br><span class="line">    合计: 1 个文件，67 字符</span><br><span class="line"></span><br><span class="line">  📂 agreement/</span><br><span class="line">    index.md -&gt; 433 字符</span><br><span class="line">    合计: 1 个文件，433 字符</span><br><span class="line"></span><br><span class="line">  📂 link/</span><br><span class="line">    index.md -&gt; 126 字符</span><br><span class="line">    合计: 1 个文件，126 字符</span><br><span class="line"></span><br><span class="line">  📂 essay/</span><br><span class="line">    index.md -&gt; 29 字符</span><br><span class="line">    合计: 1 个文件，29 字符</span><br><span class="line"></span><br><span class="line">  📂 categories/</span><br><span class="line">    index.md -&gt; 28 字符</span><br><span class="line">    合计: 1 个文件，28 字符</span><br><span class="line"></span><br><span class="line">  📂 _data/</span><br><span class="line">    link.yml -&gt; 201 字符</span><br><span class="line">    bangumis.json -&gt; 231 字符</span><br><span class="line">    creativity.yml -&gt; 75 字符</span><br><span class="line">    essay.yml -&gt; 73 字符</span><br><span class="line">    about.yml -&gt; 232 字符</span><br><span class="line">    合计: 5 个文件，812 字符</span><br><span class="line"></span><br><span class="line">  📂 cdn/</span><br><span class="line">    合计: 0 个文件，0 字符</span><br><span class="line"></span><br><span class="line">  📂 about/</span><br><span class="line">    index.md -&gt; 34 字符</span><br><span class="line">    合计: 1 个文件，34 字符</span><br><span class="line"></span><br><span class="line">  📂 sitemap/</span><br><span class="line">    合计: 0 个文件，0 字符</span><br><span class="line"></span><br><span class="line">  📂 tags/</span><br><span class="line">    index.md -&gt; 28 字符</span><br><span class="line">    合计: 1 个文件，28 字符</span><br><span class="line"></span><br><span class="line">  📂 privacy/</span><br><span class="line">    index.md -&gt; 379 字符</span><br><span class="line">    合计: 1 个文件，379 字符</span><br><span class="line"></span><br><span class="line">📁 扫描数据目录(_data):</span><br><span class="line">    link.yml -&gt; 201 字符</span><br><span class="line">    bangumis.json -&gt; 231 字符</span><br><span class="line">    creativity.yml -&gt; 75 字符</span><br><span class="line">    essay.yml -&gt; 73 字符</span><br><span class="line">    about.yml -&gt; 232 字符</span><br><span class="line">    合计: 5 个文件，812 字符</span><br><span class="line"></span><br><span class="line">📁 动态读取配置文件:</span><br><span class="line">  读取站点配置: _config.yml</span><br><span class="line">    -&gt; 360 字符</span><br><span class="line">  读取主题配置: _config.anzhiyu.yml</span><br><span class="line">    -&gt; 526 字符</span><br><span class="line">  读取主题语言文件:</span><br><span class="line">    zh-TW.yml -&gt; 341 字符</span><br><span class="line">    en.yml -&gt; 67 字符</span><br><span class="line">    zh-CN.yml -&gt; 346 字符</span><br><span class="line">    default.yml -&gt; 251 字符</span><br><span class="line">  配置文件共提取: 843 字符</span><br><span class="line"></span><br><span class="line">============================================================</span><br><span class="line">✅ 共收集 1180 个独特字符</span><br><span class="line">   - 汉字: 1073 个</span><br><span class="line">   - 英文: 1125 个</span><br><span class="line">   - 数字: 10 个</span><br><span class="line">   - 标点/其他: 45 个</span><br><span class="line">💾 已保存到: /www/wwwroot/myblog/font-spider-work/chars-full.txt</span><br><span class="line"></span><br><span class="line">📋 字符预览: 一七万三上下不与专且世业东丢个中丰为主举久么义之乌乎乐也习书乾了予二于云互些交亦产享亮人亿什仅仇今介仍从他付仙代以们件价任份优伙会传伦伴伸但位低住体何作你使來例供依侧侵便保信修個們倒值假偏做停健偿備储...</span><br><span class="line"></span><br><span class="line">🔧 开始压缩字体...</span><br><span class="line">   输入: /www/wwwroot/myblog/font-spider-work/fonts/BailuFeiYun.ttf</span><br><span class="line">   输出: /www/wwwroot/myblog/source/fonts/BailuFeiYun.woff2</span><br><span class="line"></span><br><span class="line">✅ 压缩完成!</span><br></pre></td></tr></table></figure><h3 id="文件位置"><a href="#文件位置" class="headerlink" title="文件位置"></a>文件位置</h3><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line">/www/wwwroot/myblog/</span><br><span class="line">├── source/fonts/           # 输出压缩后的字体</span><br><span class="line">│   ├── BailuFeiYun.woff2</span><br><span class="line">│   └── BailuFeiYun.ttf</span><br><span class="line">├── font-spider-work/       # 工作目录</span><br><span class="line">│   ├── fonts/</span><br><span class="line">│   │   └── BailuFeiYun.ttf  # 原始字体</span><br><span class="line">│   └── chars-full.txt       # 提取的字符列表</span><br><span class="line">└── compress_font.py         # 本脚本</span><br></pre></td></tr></table></figure>]]></content:encoded>
      
      
      <category domain="https://blog.eucalyptus.cc/categories/Hexo/">Hexo</category>
      
      
      <category domain="https://blog.eucalyptus.cc/tags/%E5%AD%97%E4%BD%93/">字体</category>
      
      
      <comments>https://blog.eucalyptus.cc/2026/02/13/%E5%AD%97%E4%BD%93%E7%9A%84%E5%BC%95%E5%85%A5%E4%B8%8E%E5%8E%8B%E7%BC%A9/#disqus_thread</comments>
      
    </item>
    
    <item>
      <title>Twikoo私有化部署</title>
      <link>https://blog.eucalyptus.cc/2026/02/08/Twikoo%E7%A7%81%E6%9C%89%E5%8C%96%E9%83%A8%E7%BD%B2/</link>
      <guid>https://blog.eucalyptus.cc/2026/02/08/Twikoo%E7%A7%81%E6%9C%89%E5%8C%96%E9%83%A8%E7%BD%B2/</guid>
      <pubDate>Sun, 08 Feb 2026 10:25:41 GMT</pubDate>
      
        
        
          
          
      <description>&lt;h2 id=&quot;前言&quot;&gt;&lt;a href=&quot;#前言&quot; class=&quot;headerlink&quot; title=&quot;前言&quot;&gt;&lt;/a&gt;前言&lt;/h2&gt;&lt;p&gt;对于使用 Hexo 搭建的静态博客而言，一个稳定、轻量的评论系统是连接读者的重要桥梁。Twikoo</description>
          
        
      
      
      
      <content:encoded><![CDATA[<h2 id="前言"><a href="#前言" class="headerlink" title="前言"></a>前言</h2><p>对于使用 Hexo 搭建的静态博客而言，一个稳定、轻量的评论系统是连接读者的重要桥梁。Twikoo 作为目前最受欢迎的开源评论方案之一，除了官方提供的云开发版本外，私有化部署能让你获得完全的数据自主权——不再受限于免费额度，无需担心云厂商政策变动，还能通过自定义二级域名和 SSL 证书实现更专业、更稳定的访问体验。<br>本文将提供一份从零开始的实操指南，手把手教你通过 Docker 完成 Twikoo 的私有化部署。无论你是习惯命令行的开发者，还是偏好图形界面的宝塔用户，都能找到适合自己的部署方式。内容涵盖域名解析、容器配置、反向代理到证书申请的全流程，特别针对 AnZhiYu 等热门 Hexo 主题给出了详细的对接配置。跟随本文操作，你将拥有一个完全属于自己、独立运行的评论服务。</p><h2 id="准备：解析二级域名"><a href="#准备：解析二级域名" class="headerlink" title="准备：解析二级域名"></a>准备：解析二级域名</h2><p>建议为 Twikoo 单独分配一个二级域名（如 <code>twikoo.yourdomain.com</code>），便于后期维护和管理。</p><ul><li><strong>记录类型</strong>：A 记录</li><li><strong>主机记录</strong>：<code>twikoo</code>（或其他你喜欢的二级域名前缀）</li><li><strong>记录值</strong>：你的服务器 IP 地址</li></ul><blockquote><p><strong>提示</strong>：解析生效需要几分钟到几小时不等，可通过 <code>ping twikoo.yourdomain.com</code> 验证是否生效。</p></blockquote><p><img src="https://img.eucalyptus.cc/images/20260208/image.avif"></p><h2 id="拉起Twikoo的容器"><a href="#拉起Twikoo的容器" class="headerlink" title="拉起Twikoo的容器"></a>拉起Twikoo的容器</h2><h3 id="方案一：Docker-命令行部署（推荐）"><a href="#方案一：Docker-命令行部署（推荐）" class="headerlink" title="方案一：Docker 命令行部署（推荐）"></a>方案一：Docker 命令行部署（推荐）</h3><p>适合熟悉 Linux 命令的开发者，轻量且可控。</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">docker run --name twikoo -e TWIKOO_THROTTLE=1000 -p 8181:8080 -v <span class="variable">$&#123;PWD&#125;</span>/data:/app/data -d imaegoo/twikoo</span><br></pre></td></tr></table></figure><p><img src="https://img.eucalyptus.cc/images/20260208/image1.avif"></p><h3 id="方案二：宝塔面板部署（可视化）"><a href="#方案二：宝塔面板部署（可视化）" class="headerlink" title="方案二：宝塔面板部署（可视化）"></a>方案二：宝塔面板部署（可视化）</h3><p>适合不熟悉命令行或已安装宝塔面板的用户。</p><p><img src="https://img.eucalyptus.cc/images/20260208/image2.avif"></p><h2 id="配置反向代理"><a href="#配置反向代理" class="headerlink" title="配置反向代理"></a>配置反向代理</h2><p>直接使用 IP:端口 访问既不优雅也不安全。通过 Nginx 反向代理，可以实现：</p><ul><li>使用域名访问（如 <code>twikoo.yourdomain.com</code>）</li><li>后续支持 HTTPS（SSL 证书）</li><li>隐藏真实端口，提高安全性</li></ul><p>Nginx 配置示例,在宝塔 “网站” → “反向代理” 中添加：</p><ul><li>目标 URL：<code>http://127.0.0.1:8181</code>（即你刚才映射的本地端口）</li><li>发送域名：$host</li></ul><blockquote><p>原理：所有访问 <code>twikoo.yourdomain.com</code> 的请求，都会被 Nginx 转发到本地的 8181 端口，由 Twikoo 容器处理。</p></blockquote><p><img src="https://img.eucalyptus.cc/images/20260208/image3.avif"></p><h2 id="部署ssl证书"><a href="#部署ssl证书" class="headerlink" title="部署ssl证书"></a>部署ssl证书</h2><p>为保证数据传输安全，强烈建议配置 HTTPS。宝塔中操作如下：</p><ul><li>在刚才的反向代理站点中，点击 “SSL” 选项卡</li><li>选择 “Let’s Encrypt” 免费证书，或上传自己的证书</li><li>开启 “强制 HTTPS”</li></ul><p><img src="https://img.eucalyptus.cc/images/20260208/imag4.avif"></p><p>最后再浏览器访问你的Twikoo的域名，以下输出代表Twikoo部署已经完成</p><p><img src="https://img.eucalyptus.cc/images/20260208/image5.avif"></p><h2 id="开启评论功能"><a href="#开启评论功能" class="headerlink" title="开启评论功能"></a>开启评论功能</h2><p>下面可以参考安知鱼主题官方文档开启评论功能</p><div class='anzhiyu-tag-link'><a class="tag-Link" target="_blank" rel="noopener external nofollow noreferrer" href="https://docs.anheyu.com/advanced/#%E8%AF%84%E8%AE%BA">    <div class="tag-link-tips">引用站外地址</div>    <div class="tag-link-bottom">        <div class="tag-link-left" data-icon="https://docs.anheyu.com/favicon.ico" style="width:60px;height:60px;min-width:60px;min-height:60px;position:relative;border-radius:var(--anzhiyu-border-radius);overflow:hidden;">          <img class="tag-link-favicon" src="https://docs.anheyu.com/favicon.ico" onerror="this.style.display='none';this.nextElementSibling.style.display='flex';" style="width:60px;height:60px;object-fit:cover;border-radius:var(--anzhiyu-border-radius);position:absolute;top:0;left:0;" alt="favicon"/>          <i class="fa-solid fa-link" style="display:flex;align-items:center;justify-content:center;width:100%;height:100%;"></i>        </div>        <div class="tag-link-right">            <div class="tag-link-title">安知鱼主题 - 评论</div>            <div class="tag-link-sitename">安知鱼主题官方文档</div>        </div>        <i class="eucalyptus icon-angle-right"></i>    </div>    </a></div><p><img src="https://img.eucalyptus.cc/images/20260208/image6.avif"></p><p><img src="https://img.eucalyptus.cc/images/20260208/image7.avif"></p><p>到这里Twikoo就部署完成了,最后记得清除缓存和重新生成静态网站</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">hexo clean</span><br><span class="line">hexo generate</span><br></pre></td></tr></table></figure><h2 id="常见问题"><a href="#常见问题" class="headerlink" title="常见问题"></a>常见问题</h2><p>Q：为什么选择私有化部署而非云开发？<br>A：云开发有免费额度限制（如腾讯云每日读取次数限制），私有化部署只要服务器不宕机就永久可用，数据完全自主可控。<br>Q：容器重启后数据会丢失吗？<br>A：不会。-v ${PWD}&#x2F;data:&#x2F;app&#x2F;data 已将数据持久化到宿主机，只要 data 文件夹在，重新运行容器数据依然保留。<br>Q：评论通知如何配置？<br>A：访问 Twikoo 管理后台（在评论区点击登录，输入管理员密码），在”设置”中配置 SMTP 邮箱或 webhook 推送。</p><p>至此，你拥有了一个完全独立的私有化评论系统！</p>]]></content:encoded>
      
      
      <category domain="https://blog.eucalyptus.cc/categories/Hexo/">Hexo</category>
      
      
      <category domain="https://blog.eucalyptus.cc/tags/Twikoo/">Twikoo</category>
      
      
      <comments>https://blog.eucalyptus.cc/2026/02/08/Twikoo%E7%A7%81%E6%9C%89%E5%8C%96%E9%83%A8%E7%BD%B2/#disqus_thread</comments>
      
    </item>
    
    <item>
      <title>从Miniflux 到 NextFlux：一步升级，拥抱现代化阅读体验</title>
      <link>https://blog.eucalyptus.cc/2025/12/11/MiniFlux%E5%8D%87%E7%BA%A7NextFlux/</link>
      <guid>https://blog.eucalyptus.cc/2025/12/11/MiniFlux%E5%8D%87%E7%BA%A7NextFlux/</guid>
      <pubDate>Thu, 11 Dec 2025 03:51:56 GMT</pubDate>
      
        
        
          
          
      <description>&lt;p&gt;前段时间我部署了Miniflux，这是一款功能强大的极简主义RSS订阅器，它支持多平台同步、文章过滤和离线阅读等实用功能，完全满足了我的信息聚合需求；不过它的界面设计确实过于朴素，采用了最基础的列表布局和单调的配色方案，在这个视觉至上的时代里，这种毫不修饰的极简风格可能会让</description>
          
        
      
      
      
      <content:encoded><![CDATA[<p>前段时间我部署了Miniflux，这是一款功能强大的极简主义RSS订阅器，它支持多平台同步、文章过滤和离线阅读等实用功能，完全满足了我的信息聚合需求；不过它的界面设计确实过于朴素，采用了最基础的列表布局和单调的配色方案，在这个视觉至上的时代里，这种毫不修饰的极简风格可能会让许多用户在初次接触时就失去耐心，随手划走转而选择那些界面更炫目、交互更花哨的阅读应用。</p><p>因此，为了弥补 Miniflux 在视觉和交互体验上的不足，我这次专门挑选了一款高颜值的第三方客户端 Nextflux。它不仅继承了 Miniflux 强大的 RSS 订阅和内容聚合功能，更通过现代化的界面设计、流畅的动画效果和直观的操作逻辑，大幅提升了用户的使用愉悦感。例如，Nextflux 支持暗黑模式与自定义主题，卡片式布局让信息浏览更加清晰高效，同时保留了 Miniflux 原有的离线阅读与全文抓取等核心能力，真正实现了功能与美感的平衡。</p><p>Nextflux 是一个基于现代化技术栈精心打造的 RSS 阅读器客户端，它专门为开源的Miniflux后端服务设计，完美融合了便捷性和功能性。该客户端采用前沿的React框架进行用户界面开发，并结合Vite这一高效构建工具，不仅确保了极速的热重载和流畅的开发体验，还优化了生产环境的打包性能，为用户提供轻量、响应迅速且高度可定制的阅读界面。无论是订阅源的智能管理、文章的分类筛选，还是跨设备的同步阅读，Nextflux都致力于提升信息获取的效率和舒适度，让用户能够专注于内容本身，而无需为技术细节分心。</p><p><img src="https://img.eucalyptus.cc/images/20251211/1.avif"></p><p>Nextflux与Miniflux类似，同样采用轻量级的容器化部署方式，能够通过Docker或Podman等工具快速搭建和运行，极大简化了安装与维护流程；这种部署模式不仅确保了环境的一致性，还支持灵活扩展与版本管理，特别适合个人或小团队在云服务器、本地主机乃至树莓派等设备上高效部署，同时保持了低资源占用和高稳定性的特点。</p><p>由于之前部署了Miniflux下面就直接部署Nextflux了，当然再结尾也会附上Miniflux+Nextflux+DB的完整yml文件</p><p>使用下述命令直接拉起nextflux的容器</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">docker run -itd  --restart unless-stopped  --name nextflux  -p 8121:3000    electh/nextflux:latest</span><br></pre></td></tr></table></figure><p>这里需要注意下Nextflux容器的端口</p><p><img src="https://img.eucalyptus.cc/images/20251211/2.avif"></p><p>拉起容器后，直接通过宿主机的IP地址加上映射的8121端口（如<code>http://192.168.1.100:8121</code>）在浏览器中访问即可；若页面加载失败，通常是由于端口映射错误导致的，此时需要检查容器日志确认实际监听的端口号，既可以通过命令行方式查看（如执行<code>docker logs 容器ID</code>命令获取详细日志），也可以在宝塔面板的”容器管理”界面中直观地查看运行日志，此外还需确认服务器防火墙是否放行了该端口，这些都是导致访问失败的常见原因。</p><p><img src="https://img.eucalyptus.cc/images/20251211/3.avif"></p><p>到这里基本上就可以看到Nextflux的登录界面了，我们可以看到这里除了用户名和密码外，还有个服务地址，这个服务地址就是Miniflux的访问地址，但是有几点需要注意</p><ul><li>如果你浏览器访问Nextflux的地址使用的是ip，那么服务地址这里就需要使用 <code>http://ip:port</code>，</li><li>如果你浏览器访问Nextflux的地址使用的域名，那么服务地址这里必须使用 <code>https://域名</code></li></ul><p><img src="https://img.eucalyptus.cc/images/20251211/4.avif"></p><p>下面，我们来详细分析使用IP地址访问时出现”Failed to fetch”错误的问题：当按照本文的部署方式部署Nextflux和Miniflux后，即使使用同一IP地址访问也会遇到跨域问题，这是因为浏览器严格执行同源策略，会对比前端页面来源和API请求地址的协议、域名和端口是否完全一致。具体来说，假设你通过<code>http://140.143.94.243:8121</code>访问Nextflux前端界面，而该前端尝试向<code>http://140.143.94.243:80</code>的Miniflux后端发起API请求时，虽然IP地址相同，但由于端口号不同（8121 vs 80），浏览器会判定为跨域请求，此时浏览器会先发送一个OPTIONS预检请求来检查服务器是否允许跨域访问；如果Miniflux后端没有正确配置CORS头部信息，特别是缺少关键的Access-Control-Allow-Origin响应头，或者该头部的值不包含前端来源地址，浏览器就会直接拦截后续的实际请求，导致前端控制台报出”Failed to fetch”错误。要解决这个问题，最直接的方法是在启动Miniflux容器时添加两个必要的环境变量，这些配置会让Miniflux正确处理跨域请求并返回适当的CORS头部，修改后需要重启Miniflux容器使配置生效。</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">environment:</span><br><span class="line">  - CORS_ORIGINS=http://140.143.94.243:8121</span><br><span class="line">  - CORS_ALLOW_HEADERS=*</span><br></pre></td></tr></table></figure><p>这里我建议直接使用宝塔面板的可视化操作界面进行操作，这样不仅能显著提高操作容错率，还能避免因手动输入命令导致的潜在错误；关于yml配置文件，我们可以直接复用之前部署Miniflux时使用的模板文件，只需在数据库配置部分额外添加这两个关键环境变量即可，完成这些修改后，直接保存配置文件即可生效，此时我们再次通过IP地址访问服务并进行登录操作，整个过程就会变得非常顺畅，不会再出现之前遇到的连接或认证问题，这种方案既保留了原有配置的稳定性，又通过最小改动实现了新功能的支持。</p><p><img src="https://img.eucalyptus.cc/images/20251211/5.avif"></p><p><img src="https://img.eucalyptus.cc/images/20251211/6.avif"></p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br></pre></td><td class="code"><pre><span class="line">services:</span><br><span class="line">  miniflux:</span><br><span class="line">    image: miniflux/miniflux:latest</span><br><span class="line">    ports:</span><br><span class="line">      - <span class="string">&quot;8383:8080&quot;</span></span><br><span class="line">    depends_on:</span><br><span class="line">      db:</span><br><span class="line">        condition: service_healthy</span><br><span class="line">    environment:</span><br><span class="line">      - DATABASE_URL=postgres://miniflux:admin@123@db/miniflux?sslmode=<span class="built_in">disable</span></span><br><span class="line">      - RUN_MIGRATIONS=1</span><br><span class="line">      - CREATE_ADMIN=1</span><br><span class="line">      - ADMIN_USERNAME=admin</span><br><span class="line">      - ADMIN_PASSWORD=admin@123</span><br><span class="line">  - CORS_ORIGINS=http://140.143.94.243:8121   <span class="comment">#填写你的实际ip和port</span></span><br><span class="line">      - CORS_ALLOW_HEADERS=*</span><br><span class="line">    healthcheck:</span><br><span class="line">      <span class="built_in">test</span>: [<span class="string">&quot;CMD&quot;</span>, <span class="string">&quot;/usr/bin/miniflux&quot;</span>, <span class="string">&quot;-healthcheck&quot;</span>, <span class="string">&quot;auto&quot;</span>]</span><br><span class="line"></span><br><span class="line">  db:</span><br><span class="line">    image: postgres:17-alpine</span><br><span class="line">    environment:</span><br><span class="line">      - POSTGRES_USER=miniflux</span><br><span class="line">      - POSTGRES_PASSWORD=admin@123</span><br><span class="line">      - POSTGRES_DB=miniflux</span><br><span class="line">    volumes:</span><br><span class="line">      - miniflux-db:/var/lib/postgresql/data</span><br><span class="line">    healthcheck:</span><br><span class="line">      <span class="built_in">test</span>: [<span class="string">&quot;CMD&quot;</span>, <span class="string">&quot;pg_isready&quot;</span>, <span class="string">&quot;-U&quot;</span>, <span class="string">&quot;miniflux&quot;</span>]</span><br><span class="line">      interval: 10s</span><br><span class="line">      start_period: 30s</span><br><span class="line"></span><br><span class="line">volumes:</span><br><span class="line">  miniflux-db:</span><br></pre></td></tr></table></figure><p>最后，我们重点讲解使用域名访问和登录时需要注意的技术细节，特别是关于HTTPS协议和URL格式规范的问题。当使用HTTPS协议访问Miniflux服务时，必须确保后端服务地址同样采用HTTPS协议，否则浏览器会因混合内容安全策略而阻止请求。这里特别要强调的是URL尾斜杠的处理问题：如果你在服务地址栏输入的是类似”<code>https://miniflux.mingliang.net.cn/</code>这样带有尾斜杠的地址，而前端JavaScript代码在拼接API路径时又没有正确处理这个斜杠，就会产生<code>https://miniflux.mingliang.net.cn**//**api/me</code>这样的双斜杠路径。这种URL格式错误虽然看起来很小，但会导致严重的后果：大多数现代Web服务器（如Nginx、Apache）会将其视为无效路径，返回404 Not Found错误；而浏览器端的fetch API则会抛出”Failed to fetch”的通用错误，给调试带来困难。这个问题在单页应用(SPA)开发中尤为常见，正确的做法应该是在配置文件中统一规范基础URL格式，或者在JavaScript代码中加入路径标准化处理逻辑，确保API请求的URL始终保持正确的单斜杠格式。</p><p>如果你之前没有部署过Miniflux这个轻量级RSS阅读器的话，使用docker-compose进行一体化安装是最便捷的方式，首先你需要创建一个名为docker-compose.yml的配置文件，将以下内容完整复制进去，其中包括了Miniflux服务及其必需的PostgreSQL数据库配置，确保网络设置和端口映射（通常Miniflux默认使用8080端口）正确无误后，在当前目录下执行docker-compose up -d命令即可启动容器编排，系统会自动拉取最新镜像并完成所有依赖服务的部署，整个过程无需手动干预，非常适合快速搭建个人阅读服务。</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br></pre></td><td class="code"><pre><span class="line">version: <span class="string">&#x27;3&#x27;</span></span><br><span class="line"></span><br><span class="line">services:</span><br><span class="line">  miniflux:</span><br><span class="line">    image: miniflux/miniflux:latest</span><br><span class="line">    container_name: miniflux-server</span><br><span class="line">    restart: unless-stopped</span><br><span class="line">    ports:</span><br><span class="line">      - <span class="string">&quot;8120:8080&quot;</span></span><br><span class="line">    depends_on:</span><br><span class="line">      db:</span><br><span class="line">        condition: service_healthy</span><br><span class="line">    environment:</span><br><span class="line">      - DATABASE_URL=postgres://miniflux:secret@db/miniflux?sslmode=<span class="built_in">disable</span></span><br><span class="line">      - RUN_MIGRATIONS=1</span><br><span class="line">      - CREATE_ADMIN=1</span><br><span class="line">      - ADMIN_USERNAME=admin</span><br><span class="line">      - ADMIN_PASSWORD=test123</span><br><span class="line">  </span><br><span class="line">  db:</span><br><span class="line">    image: postgres:17-alpine</span><br><span class="line">    container_name: miniflux-db</span><br><span class="line">    restart: unless-stopped</span><br><span class="line">    environment:</span><br><span class="line">      - POSTGRES_USER=miniflux</span><br><span class="line">      - POSTGRES_PASSWORD=secret</span><br><span class="line">      - POSTGRES_DB=miniflux</span><br><span class="line">    volumes:</span><br><span class="line">      - ./data:/var/lib/postgresql/data</span><br><span class="line">    healthcheck:</span><br><span class="line">      <span class="built_in">test</span>: [<span class="string">&quot;CMD&quot;</span>, <span class="string">&quot;pg_isready&quot;</span>, <span class="string">&quot;-U&quot;</span>, <span class="string">&quot;miniflux&quot;</span>]</span><br><span class="line">      interval: 10s</span><br><span class="line">      start_period: 30s</span><br><span class="line"></span><br><span class="line">  nextflux:</span><br><span class="line">    image: electh/nextflux:latest</span><br><span class="line">    container_name: miniflux-nextflux</span><br><span class="line">    restart: unless-stopped</span><br><span class="line">    ports:</span><br><span class="line">      - <span class="string">&quot;8121:80&quot;</span></span><br></pre></td></tr></table></figure><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">docker-compose up -d</span><br></pre></td></tr></table></figure><p>这种联合部署的方式Eucalyptus之前确实没有尝试过，主要参考了AWS和Azure的混合云架构设计思路，并结合了《云计算架构设计模式》一书中提到的服务网格集成方案，如果有任何配置问题或性能优化建议，欢迎各位技术同行在评论区留言讨论。另外Eucalyptus部署的Nextflux测试环境已经稳定运行两周，访问地址是<div class='anzhiyu-tag-link'><a class="tag-Link" target="_blank" rel="noopener external nofollow noreferrer" href="https://nextflux.mingliang.net.cn">    <div class="tag-link-tips">引用站外地址</div>    <div class="tag-link-bottom">        <div class="tag-link-left" data-icon="https://nextflux.mingliang.net.cn/favicon.ico" style="width:60px;height:60px;min-width:60px;min-height:60px;position:relative;border-radius:var(--anzhiyu-border-radius);overflow:hidden;">          <img class="tag-link-favicon" src="https://nextflux.mingliang.net.cn/favicon.ico" onerror="this.style.display='none';this.nextElementSibling.style.display='flex';" style="width:60px;height:60px;object-fit:cover;border-radius:var(--anzhiyu-border-radius);position:absolute;top:0;left:0;" alt="favicon"/>          <i class="fa-solid fa-link" style="display:flex;align-items:center;justify-content:center;width:100%;height:100%;"></i>        </div>        <div class="tag-link-right">            <div class="tag-link-title">Nextflux 测试环境</div>            <div class="tag-link-sitename">RSS聚合平台</div>        </div>        <i class="eucalyptus icon-angle-right"></i>    </div>    </a></div>，该平台集成了实时数据流处理和可视化分析功能，各位朋友如果需要测试体验的话可以留言说明具体，Eucalyptus会根据使用场景为大家提供免费的测试账号。</p>]]></content:encoded>
      
      
      <category domain="https://blog.eucalyptus.cc/categories/WordPress/">WordPress</category>
      
      <category domain="https://blog.eucalyptus.cc/categories/WordPress/RSS/">RSS</category>
      
      
      <category domain="https://blog.eucalyptus.cc/tags/Miniflux/">Miniflux</category>
      
      
      <comments>https://blog.eucalyptus.cc/2025/12/11/MiniFlux%E5%8D%87%E7%BA%A7NextFlux/#disqus_thread</comments>
      
    </item>
    
    <item>
      <title>MiniFlux安全升级</title>
      <link>https://blog.eucalyptus.cc/2025/11/08/MiniFlux%E5%AE%89%E5%85%A8%E5%8D%87%E7%BA%A7/</link>
      <guid>https://blog.eucalyptus.cc/2025/11/08/MiniFlux%E5%AE%89%E5%85%A8%E5%8D%87%E7%BA%A7/</guid>
      <pubDate>Sat, 08 Nov 2025 08:39:45 GMT</pubDate>
      
        
        
          
          
      <description>&lt;p&gt;在信息过载的时代，RSS 依旧是高效获取优质内容的“净土”。Miniflux 以轻量、简洁、开源的姿态，成为许多技术人自托管 RSS 的首选。然而，只在内网 IP 下阅读未免辜负了它的优雅；给 Miniflux 配一个专属的二级域名，再披上 HTTPS</description>
          
        
      
      
      
      <content:encoded><![CDATA[<p>在信息过载的时代，RSS 依旧是高效获取优质内容的“净土”。Miniflux 以轻量、简洁、开源的姿态，成为许多技术人自托管 RSS 的首选。然而，只在内网 IP 下阅读未免辜负了它的优雅；给 Miniflux 配一个专属的二级域名，再披上 HTTPS 的“安全外衣”，不仅能让阅读随时随地、安全无虞，更是对自己数字领地的一次仪式感升级。接下来的篇幅，我们一起把<code>localhost:8383</code>变成<code>https://miniflux.mingliang.net.cn/</code>，让每一次订阅与更新都拥有值得收藏的新地址。</p><p>首先本篇文章中使用的是二级域名，大伙都知道域名是需要进行备案的，“而二级域名需不需要备案”取决于它的一级域名是否已经取得备案号，以及二者是否使用同一家云接入商，大致分为以下几种情况。</p><ul><li>一级域名已备案，且二级域名与一级域名在同一接入商 → 二级域名无需单独备案。</li><li>一级域名已备案，但二级域名要换到另一家国内接入商 → 先做一次“新增接入备案”，等通过后才能解析到新 IP。</li><li>一级域名没有任何备案号 → 必须先把一级域名提交首次备案，二级域名才能在中国大陆正常访问。</li><li>政府&#x2F;事业单位的<code>.gov.cn</code>等后缀二级域名 → 无论一级域名是否已备案，都要单独备案。</li></ul><p>只要网站服务器放在中国大陆，访问者能直接通过二级域名打开网页，就适用上述规则；如果服务器在境外，理论上不受中国备案系统约束，但国内 DNS&#x2F;CDN 厂商通常仍会提示“一级域名需已备案”才能开启加速或防护功能。</p><p>备案就不过多赘述了，如果不清楚的，可以参考下这篇文章：<div class='anzhiyu-tag-link'><a class="tag-Link" target="_blank" rel="noopener external nofollow noreferrer" href="https://blog.csdn.net/weixin_72610956/article/details/135545236">    <div class="tag-link-tips">引用站外地址</div>    <div class="tag-link-bottom">        <div class="tag-link-left" data-icon="https://blog.csdn.net/favicon.ico" style="width:60px;height:60px;min-width:60px;min-height:60px;position:relative;border-radius:var(--anzhiyu-border-radius);overflow:hidden;">          <img class="tag-link-favicon" src="https://blog.csdn.net/favicon.ico" onerror="this.style.display='none';this.nextElementSibling.style.display='flex';" style="width:60px;height:60px;object-fit:cover;border-radius:var(--anzhiyu-border-radius);position:absolute;top:0;left:0;" alt="favicon"/>          <i class="fa-solid fa-link" style="display:flex;align-items:center;justify-content:center;width:100%;height:100%;"></i>        </div>        <div class="tag-link-right">            <div class="tag-link-title">网站ICP备案和公安备案教程</div>            <div class="tag-link-sitename">CSDN文章</div>        </div>        <i class="eucalyptus icon-angle-right"></i>    </div>    </a></div>，下面直接进行二级域名解析教程</p><p>这里以阿里云为例，在一级域名中添加一条解析记录时，首先需要登录阿里云控制台并进入域名解析管理页面，在DNS解析设置中选择”添加记录”选项，这里的记录类型可以根据实际需求选择A类型（用于将域名指向IPv4地址），主机记录则填写你的二级域名前缀（如”www”或”blog”等），记录值需要填写对应服务器的公网IP地址（对于A记录），TTL（生存时间）通常保持默认值即可，但也可以根据业务需求进行调整，设置完成后点击确认即可完成解析记录的添加，一般情况下解析会在10分钟内生效，但受各地DNS缓存影响，全球完全生效可能需要24-48小时。</p><p><img src="https://img.eucalyptus.cc/images/20251108/1.avif"></p><p>接下来进行Nginx反向代理的配置，全程使用宝塔面板的可视化界面操作，首先登录宝塔面板后，在左侧导航栏找到”网站”模块并点击进入，在菜单中选择”反向代理”选项，这时会显示当前站点的反向代理配置页面，然后点击左上角的”添加反向代理”按钮</p><p><img src="https://img.eucalyptus.cc/images/20251108/2.avif"></p><p>在域名配置栏中，需要填写之前在域名控制台已解析完成的二级域名（如”test.example.com”），目标URL地址则应填写本地服务器地址<code>http://127.0.0.1</code>并加上对应的服务端口号（例如<code>:8383</code>表示该服务运行在8383端口），发送域名host保持默认设置即可（通常为自动填充的当前域名），备注栏可根据实际需求填写该条记录的相关说明（如”测试环境API服务”或”开发调试用”等描述性内容），确认所有信息无误后点击保存按钮完成配置。</p><p><img src="https://img.eucalyptus.cc/images/20251108/3.avif"></p><p>最后为刚才配置的反向代理服务开启HTTPS加密连接，首先在控制面板中找到并点击刚才添加的反向代理条目右侧的设置按钮，在弹出的菜单中选择”SSL”选项，然后选择<code>Let’sL Encrypt</code>免费证书服务，系统会自动验证域名所有权并生成证书请求，验证通过后点击”申请证书”按钮，等待证书签发完成后，务必记得点击”部署”按钮将证书应用到当前反向代理配置中，这样就能为网站流量提供端到端的加密保护，有效防止数据在传输过程中被窃听或篡改，同时还能提升网站在搜索引擎中的排名。</p><p><img src="https://img.eucalyptus.cc/images/20251108/4.avif"></p><p>到这里Miniflux 安全升级的全部部署工作已经圆满完成，我们不仅为Eucalyptus的个人RSS阅读器Miniflux绑定了专属域名miniflux.mingliang.net.cn，还成功配置了HTTPS加密协议，确保数据传输的安全性，现在您可以通过<div class='anzhiyu-tag-link'><a class="tag-Link" target="_blank" rel="noopener external nofollow noreferrer" href="https://miniflux.mingliang.net.cn/">    <div class="tag-link-tips">引用站外地址</div>    <div class="tag-link-bottom">        <div class="tag-link-left" data-icon="https://miniflux.mingliang.net.cn/favicon.ico" style="width:60px;height:60px;min-width:60px;min-height:60px;position:relative;border-radius:var(--anzhiyu-border-radius);overflow:hidden;">          <img class="tag-link-favicon" src="https://miniflux.mingliang.net.cn/favicon.ico" onerror="this.style.display='none';this.nextElementSibling.style.display='flex';" style="width:60px;height:60px;object-fit:cover;border-radius:var(--anzhiyu-border-radius);position:absolute;top:0;left:0;" alt="favicon"/>          <i class="fa-solid fa-link" style="display:flex;align-items:center;justify-content:center;width:100%;height:100%;"></i>        </div>        <div class="tag-link-right">            <div class="tag-link-title">Miniflux 安全访问地址</div>            <div class="tag-link-sitename">RSS阅读器</div>        </div>        <i class="eucalyptus icon-angle-right"></i>    </div>    </a></div>这个安全链接访问这个清爽简洁的RSS聚合平台，该平台采用Go语言开发，具有极低的资源占用和快速的响应速度，特别适合追求高效阅读体验的用户，如果您对这款轻量级RSS阅读器感兴趣，想要亲自体验它的强大功能，比如文章分类管理、快捷键操作、多设备同步等特性，欢迎在评论区留言，Eucalyptus会热情地为每位感兴趣的朋友创建专属测试账号，让您零门槛感受Miniflux的独特魅力。</p>]]></content:encoded>
      
      
      <category domain="https://blog.eucalyptus.cc/categories/WordPress/">WordPress</category>
      
      <category domain="https://blog.eucalyptus.cc/categories/WordPress/RSS/">RSS</category>
      
      
      <category domain="https://blog.eucalyptus.cc/tags/Miniflux/">Miniflux</category>
      
      
      <comments>https://blog.eucalyptus.cc/2025/11/08/MiniFlux%E5%AE%89%E5%85%A8%E5%8D%87%E7%BA%A7/#disqus_thread</comments>
      
    </item>
    
    <item>
      <title>Miniflux – RSS 订阅</title>
      <link>https://blog.eucalyptus.cc/2025/10/07/Miniflux-%E2%80%93-RSS-%E8%AE%A2%E9%98%85/</link>
      <guid>https://blog.eucalyptus.cc/2025/10/07/Miniflux-%E2%80%93-RSS-%E8%AE%A2%E9%98%85/</guid>
      <pubDate>Tue, 07 Oct 2025 12:12:24 GMT</pubDate>
      
        
        
          
          
      <description>&lt;h2 id=&quot;什么是Miniflux&quot;&gt;&lt;a href=&quot;#什么是Miniflux&quot; class=&quot;headerlink&quot; title=&quot;什么是Miniflux&quot;&gt;&lt;/a&gt;什么是Miniflux&lt;/h2&gt;&lt;p&gt;Miniflux 是一个用 Go 写的极简开源 RSS</description>
          
        
      
      
      
      <content:encoded><![CDATA[<h2 id="什么是Miniflux"><a href="#什么是Miniflux" class="headerlink" title="什么是Miniflux"></a>什么是Miniflux</h2><p>Miniflux 是一个用 Go 写的极简开源 RSS 阅读器，单二进制 +内建 Web UI，五分钟就能容器化跑起来；它默认 Fever API、内存占用极低，界面干净无广告，适合只想安静看文章、不想折腾样式插件的极简党。</p><h3 id="核心功能与特点"><a href="#核心功能与特点" class="headerlink" title="核心功能与特点"></a>核心功能与特点</h3><ul><li>极简设计 ：功能精简，专注于核心阅读体验。</li><li>高性能 ：基于 Go 语言开发，单二进制文件，无依赖。</li><li>轻量且快速 ：资源占用低，响应迅速。</li><li>现代技术栈 ：使用现代 JavaScript（ES6 和 Fetch API），无复杂框架。</li><li>单一数据库支持 ：仅支持 PostgreSQL，确保数据一致性和性能。</li><li>易于安装 ：提供 Docker 镜像，一键部署。</li></ul><h3 id="适用场景"><a href="#适用场景" class="headerlink" title="适用场景"></a>适用场景</h3><ul><li>极简主义爱好者：追求简洁、高效的新闻阅读体验。</li><li>开发者：通过 API 集成到其他应用中。</li><li>低资源环境：在树莓派等低性能设备上运行。</li></ul><h3 id="官方地址"><a href="#官方地址" class="headerlink" title="官方地址"></a>官方地址</h3><div class='anzhiyu-tag-link'><a class="tag-Link" target="_blank" rel="noopener external nofollow noreferrer" href="https://miniflux.app">    <div class="tag-link-tips">引用站外地址</div>    <div class="tag-link-bottom">        <div class="tag-link-left" data-icon="https://miniflux.app/favicon.ico" style="width:60px;height:60px;min-width:60px;min-height:60px;position:relative;border-radius:var(--anzhiyu-border-radius);overflow:hidden;">          <img class="tag-link-favicon" src="https://miniflux.app/favicon.ico" onerror="this.style.display='none';this.nextElementSibling.style.display='flex';" style="width:60px;height:60px;object-fit:cover;border-radius:var(--anzhiyu-border-radius);position:absolute;top:0;left:0;" alt="favicon"/>          <i class="fa-solid fa-link" style="display:flex;align-items:center;justify-content:center;width:100%;height:100%;"></i>        </div>        <div class="tag-link-right">            <div class="tag-link-title">Miniflux 官方网站</div>            <div class="tag-link-sitename">官方网站</div>        </div>        <i class="eucalyptus icon-angle-right"></i>    </div>    </a></div><ul><li>GitHub 仓库：<div class='anzhiyu-tag-link'><a class="tag-Link" target="_blank" rel="noopener external nofollow noreferrer" href="https://github.com/miniflux/v2">    <div class="tag-link-tips">引用站外地址</div>    <div class="tag-link-bottom">        <div class="tag-link-left" data-icon="https://github.com/favicon.ico" style="width:60px;height:60px;min-width:60px;min-height:60px;position:relative;border-radius:var(--anzhiyu-border-radius);overflow:hidden;">          <img class="tag-link-favicon" src="https://github.com/favicon.ico" onerror="this.style.display='none';this.nextElementSibling.style.display='flex';" style="width:60px;height:60px;object-fit:cover;border-radius:var(--anzhiyu-border-radius);position:absolute;top:0;left:0;" alt="favicon"/>          <i class="fa-solid fa-link" style="display:flex;align-items:center;justify-content:center;width:100%;height:100%;"></i>        </div>        <div class="tag-link-right">            <div class="tag-link-title">Miniflux GitHub</div>            <div class="tag-link-sitename">GitHub仓库</div>        </div>        <i class="eucalyptus icon-angle-right"></i>    </div>    </a></div></li></ul><h2 id="前提条件"><a href="#前提条件" class="headerlink" title="前提条件"></a>前提条件</h2><p>在开始部署之前，请确保你的系统已经安装了 Docker 和 Docker Compose。如果尚未安装，请参考Eucalyptus写的<div class='anzhiyu-tag-link'><a class="tag-Link" target="_blank" rel="noopener external nofollow noreferrer" href="https://blog.csdn.net/weixin_72610956/article/details/131507884">    <div class="tag-link-tips">引用站外地址</div>    <div class="tag-link-bottom">        <div class="tag-link-left" data-icon="https://blog.csdn.net/favicon.ico" style="width:60px;height:60px;min-width:60px;min-height:60px;position:relative;border-radius:var(--anzhiyu-border-radius);overflow:hidden;">          <img class="tag-link-favicon" src="https://blog.csdn.net/favicon.ico" onerror="this.style.display='none';this.nextElementSibling.style.display='flex';" style="width:60px;height:60px;object-fit:cover;border-radius:var(--anzhiyu-border-radius);position:absolute;top:0;left:0;" alt="favicon"/>          <i class="fa-solid fa-link" style="display:flex;align-items:center;justify-content:center;width:100%;height:100%;"></i>        </div>        <div class="tag-link-right">            <div class="tag-link-title">Centos7下安装Docker</div>            <div class="tag-link-sitename">CSDN博客</div>        </div>        <i class="eucalyptus icon-angle-right"></i>    </div>    </a></div> ，</p><h2 id="创建-Docker-Compose-配置文件"><a href="#创建-Docker-Compose-配置文件" class="headerlink" title="创建 Docker Compose 配置文件"></a>创建 Docker Compose 配置文件</h2><h3 id="命令行部署"><a href="#命令行部署" class="headerlink" title="命令行部署"></a>命令行部署</h3><p>首先登录你部署Miniflux的服务器，然后创建个docker-compose.yml文件</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br></pre></td><td class="code"><pre><span class="line">services:</span><br><span class="line">  miniflux:</span><br><span class="line">    image: miniflux/miniflux:latest</span><br><span class="line">    ports:</span><br><span class="line">      - <span class="string">&quot;80:8080&quot;</span></span><br><span class="line">    depends_on:</span><br><span class="line">      db:</span><br><span class="line">        condition: service_healthy</span><br><span class="line">    environment:</span><br><span class="line">      - DATABASE_URL=postgres://miniflux:admin@123@db/miniflux?sslmode=<span class="built_in">disable</span></span><br><span class="line">      - RUN_MIGRATIONS=1</span><br><span class="line">      - CREATE_ADMIN=1</span><br><span class="line">      - ADMIN_USERNAME=admin</span><br><span class="line">      - ADMIN_PASSWORD=admin@123</span><br><span class="line">    healthcheck:</span><br><span class="line">      <span class="built_in">test</span>: [<span class="string">&quot;CMD&quot;</span>, <span class="string">&quot;/usr/bin/miniflux&quot;</span>, <span class="string">&quot;-healthcheck&quot;</span>, <span class="string">&quot;auto&quot;</span>]</span><br><span class="line"></span><br><span class="line">  db:</span><br><span class="line">    image: postgres:17-alpine</span><br><span class="line">    environment:</span><br><span class="line">      - POSTGRES_USER=miniflux          <span class="comment"># 与 DATABASE_URL 用户一致</span></span><br><span class="line">      - POSTGRES_PASSWORD=admin@123</span><br><span class="line">      - POSTGRES_DB=miniflux</span><br><span class="line">    volumes:</span><br><span class="line">      - miniflux-db:/var/lib/postgresql/data   <span class="comment"># 官方默认路径</span></span><br><span class="line">    healthcheck:</span><br><span class="line">      <span class="built_in">test</span>: [<span class="string">&quot;CMD&quot;</span>, <span class="string">&quot;pg_isready&quot;</span>, <span class="string">&quot;-U&quot;</span>, <span class="string">&quot;miniflux&quot;</span>]  <span class="comment"># 用户一致</span></span><br><span class="line">      interval: 10s</span><br><span class="line">      start_period: 30s</span><br><span class="line"></span><br><span class="line">volumes:</span><br><span class="line">  miniflux-db:</span><br></pre></td></tr></table></figure><p>接下来再当前目录下直接执行下述命令即可</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">docker compose up -d</span><br></pre></td></tr></table></figure><p><img src="https://img.eucalyptus.cc/images/20251007/1.avif"></p><h3 id="宝塔可视化部署"><a href="#宝塔可视化部署" class="headerlink" title="宝塔可视化部署"></a>宝塔可视化部署</h3><p>当然也可完全使用宝塔可视化部署，在宝塔中的docker容器编排这里部署</p><p><img src="https://img.eucalyptus.cc/images/20251007/2.avif"></p><p><img src="https://img.eucalyptus.cc/images/20251007/3.avif"></p><p>compose内容</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br></pre></td><td class="code"><pre><span class="line">version: <span class="string">&quot;3.9&quot;</span></span><br><span class="line"></span><br><span class="line">services:</span><br><span class="line">  miniflux:</span><br><span class="line">    image: miniflux/miniflux:latest</span><br><span class="line">    ports:</span><br><span class="line">      - <span class="string">&quot;<span class="variable">$&#123;MINIFLUX_PORT&#125;</span>:8080&quot;</span>          <span class="comment"># ← 变量</span></span><br><span class="line">    depends_on:</span><br><span class="line">      db:</span><br><span class="line">        condition: service_healthy</span><br><span class="line">    environment:</span><br><span class="line">      - DATABASE_URL=postgres://<span class="variable">$&#123;DB_USER&#125;</span>:<span class="variable">$&#123;DB_PASS&#125;</span>@db/<span class="variable">$&#123;DB_NAME&#125;</span>?sslmode=<span class="built_in">disable</span></span><br><span class="line">      - RUN_MIGRATIONS=1</span><br><span class="line">      - CREATE_ADMIN=1</span><br><span class="line">      - ADMIN_USERNAME=<span class="variable">$&#123;ADMIN_USER&#125;</span></span><br><span class="line">      - ADMIN_PASSWORD=<span class="variable">$&#123;ADMIN_PASS&#125;</span></span><br><span class="line">    healthcheck:</span><br><span class="line">      <span class="built_in">test</span>: [<span class="string">&quot;CMD&quot;</span>, <span class="string">&quot;/usr/bin/miniflux&quot;</span>, <span class="string">&quot;-healthcheck&quot;</span>, <span class="string">&quot;auto&quot;</span>]</span><br><span class="line"></span><br><span class="line">  db:</span><br><span class="line">    image: postgres:17-alpine</span><br><span class="line">    environment:</span><br><span class="line">      - POSTGRES_USER=<span class="variable">$&#123;DB_USER&#125;</span></span><br><span class="line">      - POSTGRES_PASSWORD=<span class="variable">$&#123;DB_PASS&#125;</span></span><br><span class="line">      - POSTGRES_DB=<span class="variable">$&#123;DB_NAME&#125;</span></span><br><span class="line">    volumes:</span><br><span class="line">      - miniflux-db:/var/lib/postgresql/data</span><br><span class="line">    healthcheck:</span><br><span class="line">      <span class="built_in">test</span>: [<span class="string">&quot;CMD&quot;</span>, <span class="string">&quot;pg_isready&quot;</span>, <span class="string">&quot;-U&quot;</span>, <span class="string">&quot;<span class="variable">$&#123;DB_USER&#125;</span>&quot;</span>]</span><br><span class="line">      interval: 10s</span><br><span class="line">      start_period: 30s</span><br><span class="line"></span><br><span class="line">volumes:</span><br><span class="line">  miniflux-db:</span><br></pre></td></tr></table></figure><p>.env内容</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># Miniflux 外部端口</span></span><br><span class="line">MINIFLUX_PORT=80</span><br><span class="line"></span><br><span class="line"><span class="comment"># 数据库三要素</span></span><br><span class="line">DB_USER=miniflux</span><br><span class="line">DB_PASS=admin@123</span><br><span class="line">DB_NAME=miniflux</span><br><span class="line"></span><br><span class="line"><span class="comment"># 管理员账号</span></span><br><span class="line">ADMIN_USER=admin</span><br><span class="line">ADMIN_PASS=admin@123</span><br></pre></td></tr></table></figure><h3 id="Miniflux订阅RSS"><a href="#Miniflux订阅RSS" class="headerlink" title="Miniflux订阅RSS"></a>Miniflux订阅RSS</h3><p>到这里Miniflux已经部署完成了，接下来在浏览器访问<code>http://&lt;IP&gt;:80</code>即可，ip使用你部署Miniflux服务器的公网ip，Miniflux的用户是admin，密码是admin@123，pg的用户名是miniflux，密码是admin@123</p><p>输入Miniflux密码进行登录</p><p><img src="https://img.eucalyptus.cc/images/20251007/4.avif"></p><p>刚进去的是英文界面，可以在set设置简体中文</p><p><img src="https://img.eucalyptus.cc/images/20251007/5.avif"></p><p>然后添加订阅源，输入你要订阅BLOG的RSS地址例如：<code>https://mingliang.net.cn/feed/</code></p><p><img src="https://img.eucalyptus.cc/images/20251007/6.avif"></p><p>然后就可以看到BLOG的文章内容了</p><p><img src="https://img.eucalyptus.cc/images/20251007/7.avif"></p><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>到这里Miniflux就完成了部署并且订阅了Blog的RSS，整个过程仅需几分钟就能搭建起一个轻量高效的RSS阅读平台，你可以通过网页端随时随地获取最新内容更新，无需担心广告干扰或算法推荐；如果你不方便自行部署的话，可以_<strong>留言申请Eucalyptus</strong>_部署的Miniflux服务的用户名进行体验，我们提供免费的测试账号供你试用一周，期间你可以完整体验Miniflux的所有功能，包括多设备同步、快捷键操作、文章收藏等特色功能，感受这款开源RSS阅读器的简洁与高效。</p>]]></content:encoded>
      
      
      <category domain="https://blog.eucalyptus.cc/categories/WordPress/">WordPress</category>
      
      <category domain="https://blog.eucalyptus.cc/categories/WordPress/RSS/">RSS</category>
      
      
      <category domain="https://blog.eucalyptus.cc/tags/Miniflux/">Miniflux</category>
      
      
      <comments>https://blog.eucalyptus.cc/2025/10/07/Miniflux-%E2%80%93-RSS-%E8%AE%A2%E9%98%85/#disqus_thread</comments>
      
    </item>
    
    <item>
      <title>Wordpress登录页面DIY</title>
      <link>https://blog.eucalyptus.cc/2025/09/01/%E7%99%BB%E5%BD%95%E9%A1%B5%E9%9D%A2DIY/</link>
      <guid>https://blog.eucalyptus.cc/2025/09/01/%E7%99%BB%E5%BD%95%E9%A1%B5%E9%9D%A2DIY/</guid>
      <pubDate>Mon, 01 Sep 2025 12:37:45 GMT</pubDate>
      
        
        
          
          
      <description>&lt;p&gt;由于部分WordPress主题在默认配置下没有对后台登录界面进行定制化开发，用户只能使用系统自带的原生登录页面，这种页面往往设计简陋、缺乏品牌特色，与网站整体风格格格不入，影响了用户体验和品牌形象；为此，我通过深入研究WordPress的login_head和login_f</description>
          
        
      
      
      
      <content:encoded><![CDATA[<p>由于部分WordPress主题在默认配置下没有对后台登录界面进行定制化开发，用户只能使用系统自带的原生登录页面，这种页面往往设计简陋、缺乏品牌特色，与网站整体风格格格不入，影响了用户体验和品牌形象；为此，我通过深入研究WordPress的login_head和login_footer钩子，对原生登录页面进行了全面的二次开发，不仅替换了默认的WordPress logo为网站品牌标识，还重新设计了登录表单的布局和配色方案，增加了背景图片和CSS3动画效果，同时优化了移动端的响应式布局，使登录页面与网站主题风格完美统一，大大提升了后台管理的专业性和视觉体验。</p><div class='anzhiyu-tag-link'><a class="tag-Link" target="_blank" rel="noopener external nofollow noreferrer" href="https://gitcode.com/weixin_72610956/Blog/tree/login">    <div class="tag-link-tips">引用站外地址</div>    <div class="tag-link-bottom">        <div class="tag-link-left" data-icon="https://gitcode.com/favicon.ico" style="width:60px;height:60px;min-width:60px;min-height:60px;position:relative;border-radius:var(--anzhiyu-border-radius);overflow:hidden;">          <img class="tag-link-favicon" src="https://gitcode.com/favicon.ico" onerror="this.style.display='none';this.nextElementSibling.style.display='flex';" style="width:60px;height:60px;object-fit:cover;border-radius:var(--anzhiyu-border-radius);position:absolute;top:0;left:0;" alt="favicon"/>          <i class="fa-solid fa-link" style="display:flex;align-items:center;justify-content:center;width:100%;height:100%;"></i>        </div>        <div class="tag-link-right">            <div class="tag-link-title">登录页面DIY 源码下载</div>            <div class="tag-link-sitename">GitCode</div>        </div>        <i class="eucalyptus icon-angle-right"></i>    </div>    </a></div><p><img src="https://img.eucalyptus.cc/images/20250901/1.avif"></p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">#下述了两个代码文件均放置到主题目录下的自建self-innovate文件中</span></span><br><span class="line">login-style.php</span><br><span class="line">login-style.css</span><br></pre></td></tr></table></figure><p>核心代码</p><figure class="highlight php"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">&lt;?php</span></span><br><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * 登录页美化（Logo 完全禁用点击）</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="title function_ invoke__">add_action</span>( <span class="string">&#x27;login_enqueue_scripts&#x27;</span>, function () &#123;</span><br><span class="line">    <span class="title function_ invoke__">wp_enqueue_style</span>( <span class="string">&#x27;my-login&#x27;</span>, <span class="title function_ invoke__">get_theme_file_uri</span>( <span class="string">&#x27;/self-innovate/login-style.css&#x27;</span> ), [], <span class="string">&#x27;1.0.0&#x27;</span> );</span><br><span class="line">&#125; );</span><br><span class="line"></span><br><span class="line"><span class="comment">/* 完全移除 &lt;a&gt; 的 href，避免刷新 */</span></span><br><span class="line"><span class="title function_ invoke__">add_filter</span>( <span class="string">&#x27;login_headerurl&#x27;</span>, function () &#123;</span><br><span class="line">    <span class="keyword">return</span> <span class="string">&#x27;javascript:void(0);&#x27;</span>;   <span class="comment">// 真正无动作</span></span><br><span class="line">&#125; );</span><br><span class="line"></span><br><span class="line"><span class="comment">/* 悬停文字留空，防止提示 */</span></span><br><span class="line"><span class="title function_ invoke__">add_filter</span>( <span class="string">&#x27;login_headertext&#x27;</span>, <span class="string">&#x27;__return_empty_string&#x27;</span> );</span><br></pre></td></tr></table></figure><p>下述是css代码，需替换css中body.login和login::before的background:url(”)路径</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br><span class="line">89</span><br></pre></td><td class="code"><pre><span class="line">/* 1. 背景与斜遮罩 */</span><br><span class="line">body.login&#123;</span><br><span class="line">  height:100vh;</span><br><span class="line">  margin:0;</span><br><span class="line">  display:flex;</span><br><span class="line">  align-items:center;</span><br><span class="line">  justify-content:center;</span><br><span class="line">  background:url(<span class="string">&#x27;https://mingliang.net.cn/wp-content/uploads/2025/05/16-scaled.jpg&#x27;</span>) center/cover no-repeat;</span><br><span class="line">  position:relative;</span><br><span class="line">&#125;</span><br><span class="line">body.login::before&#123;</span><br><span class="line">  content:<span class="string">&#x27;&#x27;</span>;</span><br><span class="line">  position:absolute;</span><br><span class="line">  inset:0;</span><br><span class="line">  background:linear-gradient(-75deg, transparent 0% 50%, rgba(255,255,255,.6) 50% 100%);</span><br><span class="line">  z-index:1;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">/* 2. 登录框整体定位与 Logo */</span><br><span class="line"><span class="comment">#login&#123;</span></span><br><span class="line">  position:relative;</span><br><span class="line">  z-index:2;</span><br><span class="line">  width:340px;</span><br><span class="line">  padding:40px 40px 40px; /* 40px+140px空间已含在顶部padding */</span><br><span class="line">  padding-top:140px;      /* 给Logo留空间 */</span><br><span class="line">  margin-left:150px;</span><br><span class="line">&#125;</span><br><span class="line"><span class="comment">#login::before&#123;           /* Logo伪元素 */</span></span><br><span class="line">  content:<span class="string">&#x27;&#x27;</span>;</span><br><span class="line">  position:absolute;</span><br><span class="line">  top:20px;</span><br><span class="line">  left:50%;</span><br><span class="line">  transform:translateX(-50%);</span><br><span class="line">  width:160px;</span><br><span class="line">  aspect-ratio:1;</span><br><span class="line">  background:url(<span class="string">&#x27;https://img.eucalyptus.cc/logo/logo1.avif?imageslim&#x27;</span>) center/contain no-repeat;</span><br><span class="line">  pointer-events:none;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">/* 3. 表单 */</span><br><span class="line">body.login form#loginform&#123;</span><br><span class="line">  margin:24px 0 !important;</span><br><span class="line">  padding:26px 24px !important;</span><br><span class="line">  box-shadow:0 1px 3px rgba(0,0,0,.04) !important;</span><br><span class="line">  background:transparent !important;</span><br><span class="line">  border:none !important;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">/* 4. 文字Logo */</span><br><span class="line">.login h1 a&#123;</span><br><span class="line">  background:none !important;</span><br><span class="line">  text-indent:0;</span><br><span class="line">  width:auto;</span><br><span class="line">  height:auto;</span><br><span class="line">  font:700 24px/1.2 sans-serif;</span><br><span class="line">  color:#333;</span><br><span class="line">  margin-bottom:24px;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">/* 5. 输入框 */</span><br><span class="line">.login form .input&#123;</span><br><span class="line">  border:1px solid <span class="comment">#e1e5e9;</span></span><br><span class="line">  border-radius:8px;</span><br><span class="line">  padding:12px 16px;</span><br><span class="line">  font-size:15px;</span><br><span class="line">  transition:border-color .3s, box-shadow .3s;</span><br><span class="line">&#125;</span><br><span class="line">.login form .input:focus&#123;</span><br><span class="line">  border-color:#667eea;</span><br><span class="line">  box-shadow:0 0 0 2px rgba(102,126,234,.25);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">/* 6. 按钮 */</span><br><span class="line">.wp-core-ui .button-primary&#123;</span><br><span class="line">  width:100%;</span><br><span class="line">  border:none;</span><br><span class="line">  border-radius:30px;</span><br><span class="line">  padding:12px 0;</span><br><span class="line">  font:600 16px/1 sans-serif;</span><br><span class="line">  background:linear-gradient(135deg,#667eea 0%,#764ba2 100%);</span><br><span class="line">  transition:transform .3s;</span><br><span class="line">&#125;</span><br><span class="line">.wp-core-ui .button-primary:hover&#123;</span><br><span class="line">  transform:translateY(-2px);</span><br><span class="line">  box-shadow:0 6px 20px rgba(102,126,234,.45);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">/* 7. 隐藏语言切换 */</span><br><span class="line"><span class="comment">#language-switcher&#123;display:none !important;&#125;</span></span><br></pre></td></tr></table></figure><p>最后需再functions.php中调用该模块</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">require_once get_theme_file_path( <span class="string">&#x27;/self-innovate/login-style.php&#x27;</span> );</span><br></pre></td></tr></table></figure>]]></content:encoded>
      
      
      <category domain="https://blog.eucalyptus.cc/categories/WordPress/">WordPress</category>
      
      
      <category domain="https://blog.eucalyptus.cc/tags/%E6%8F%92%E4%BB%B6/">插件</category>
      
      
      <comments>https://blog.eucalyptus.cc/2025/09/01/%E7%99%BB%E5%BD%95%E9%A1%B5%E9%9D%A2DIY/#disqus_thread</comments>
      
    </item>
    
    <item>
      <title>Meteor主题友链页面自研</title>
      <link>https://blog.eucalyptus.cc/2025/08/31/Meteor%E4%B8%BB%E9%A2%98%E5%8F%8B%E9%93%BE%E9%A1%B5%E9%9D%A2%E8%87%AA%E7%A0%94/</link>
      <guid>https://blog.eucalyptus.cc/2025/08/31/Meteor%E4%B8%BB%E9%A2%98%E5%8F%8B%E9%93%BE%E9%A1%B5%E9%9D%A2%E8%87%AA%E7%A0%94/</guid>
      <pubDate>Sun, 31 Aug 2025 13:47:25 GMT</pubDate>
      
        
        
          
          
      <description>&lt;p&gt;Meteor主题虽然设计简约现代，但由于缺乏原生的友情链接管理功能，许多博主只能将友情链接勉强添加在网站底部，这不仅影响页面美观，也不便于访客查找和互动；为了解决这一痛点，本博主对主题进行了深度二次开发，专门打造了一个独立的友情链接页面，该页面不仅实现了友链分类展示、图文混</description>
          
        
      
      
      
      <content:encoded><![CDATA[<p>Meteor主题虽然设计简约现代，但由于缺乏原生的友情链接管理功能，许多博主只能将友情链接勉强添加在网站底部，这不仅影响页面美观，也不便于访客查找和互动；为了解决这一痛点，本博主对主题进行了深度二次开发，专门打造了一个独立的友情链接页面，该页面不仅实现了友链分类展示、图文混排等基本功能，更创新性地开发了前端提交表单，允许其他站长直接在线提交申请，同时配套开发了完善的后台审核系统，管理员可以便捷地查看申请信息、审核状态，并一键通过或拒绝，整个流程实现了自动化闭环管理，大大提升了友链交换的效率和用户体验。</p><div class='anzhiyu-tag-link'><a class="tag-Link" target="_blank" rel="noopener external nofollow noreferrer" href="https://gitcode.com/weixin_72610956/Blog/tree/link">    <div class="tag-link-tips">引用站外地址</div>    <div class="tag-link-bottom">        <div class="tag-link-left" data-icon="https://gitcode.com/favicon.ico" style="width:60px;height:60px;min-width:60px;min-height:60px;position:relative;border-radius:var(--anzhiyu-border-radius);overflow:hidden;">          <img class="tag-link-favicon" src="https://gitcode.com/favicon.ico" onerror="this.style.display='none';this.nextElementSibling.style.display='flex';" style="width:60px;height:60px;object-fit:cover;border-radius:var(--anzhiyu-border-radius);position:absolute;top:0;left:0;" alt="favicon"/>          <i class="fa-solid fa-link" style="display:flex;align-items:center;justify-content:center;width:100%;height:100%;"></i>        </div>        <div class="tag-link-right">            <div class="tag-link-title">Meteor主题友链页面 源码下载</div>            <div class="tag-link-sitename">GitCode</div>        </div>        <i class="eucalyptus icon-angle-right"></i>    </div>    </a></div><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">default-avatar.jpg</span><br><span class="line">page-links.css</span><br><span class="line">page-links.js</span><br><span class="line">pending-links.php</span><br></pre></td></tr></table></figure><p>以下是核心代码部分，这段代码实现了系统的关键功能，</p><p>并且这里对友链头像有三个判断的逻辑</p><ul><li>有头像 → 正常显示；</li><li>无头像 → 立即显示默认头像；</li><li>有头像但 3 秒仍未加载完成 → 自动替换成默认头像。</li></ul><figure class="highlight php"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br><span class="line">89</span><br><span class="line">90</span><br><span class="line">91</span><br><span class="line">92</span><br><span class="line">93</span><br><span class="line">94</span><br><span class="line">95</span><br><span class="line">96</span><br><span class="line">97</span><br><span class="line">98</span><br><span class="line">99</span><br><span class="line">100</span><br><span class="line">101</span><br><span class="line">102</span><br><span class="line">103</span><br><span class="line">104</span><br><span class="line">105</span><br><span class="line">106</span><br><span class="line">107</span><br><span class="line">108</span><br><span class="line">109</span><br><span class="line">110</span><br><span class="line">111</span><br><span class="line">112</span><br><span class="line">113</span><br><span class="line">114</span><br><span class="line">115</span><br><span class="line">116</span><br><span class="line">117</span><br><span class="line">118</span><br><span class="line">119</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">&lt;?php</span></span><br><span class="line"><span class="comment">/*</span></span><br><span class="line"><span class="comment"> * Template Name: 友情链接</span></span><br><span class="line"><span class="comment"> * Description:  极简友链模板（样式/脚本已分离，含评论）</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"></span><br><span class="line"><span class="title function_ invoke__">get_header</span>(); </span><br><span class="line"><span class="meta">?&gt;</span></span><br><span class="line"></span><br><span class="line">&lt;div <span class="class"><span class="keyword">class</span>=&quot;<span class="title">hero</span>-<span class="title">title</span>&quot;&gt;</span></span><br><span class="line"><span class="class">  &lt;?<span class="title">php</span> <span class="title">the_title</span>( &#x27;&lt;<span class="title">h1</span>&gt;&#x27;, &#x27;&lt;/<span class="title">h1</span>&gt;&#x27; ); ?&gt;</span></span><br><span class="line"><span class="class">  &lt;<span class="title">p</span> <span class="title">class</span>=&quot;<span class="title">hero</span>-<span class="title">sub</span>&quot;&gt;欢迎交换友链 · 携手点亮彼此的星空&lt;/<span class="title">p</span>&gt;</span></span><br><span class="line"><span class="class">&lt;/<span class="title">div</span>&gt;</span></span><br><span class="line"><span class="class"></span></span><br><span class="line"><span class="class"></span></span><br><span class="line"><span class="class">&lt;!-- 载入独立样式 --&gt;</span></span><br><span class="line"><span class="class">&lt;<span class="title">link</span> <span class="title">rel</span>=&quot;<span class="title">stylesheet</span>&quot; <span class="title">href</span>=&quot;&lt;?<span class="title">php</span> <span class="title">echo</span> <span class="title">get_template_directory_uri</span>(); ?&gt;/<span class="title">self</span>-<span class="title">innovate</span>/<span class="title">page</span>-<span class="title">links</span>.<span class="title">css</span>&quot;&gt;</span></span><br><span class="line"><span class="class"></span></span><br><span class="line"><span class="class">&lt;<span class="title">div</span> <span class="title">class</span>=&quot;<span class="title">page</span>-<span class="title">links</span>&quot;&gt;</span></span><br><span class="line"><span class="class"></span></span><br><span class="line"><span class="class">  &lt;!-- 搜索框 --&gt;</span></span><br><span class="line"><span class="class">  &lt;<span class="title">div</span> <span class="title">class</span>=&quot;<span class="title">link</span>-<span class="title">search</span>&quot;&gt;</span></span><br><span class="line"><span class="class">    &lt;<span class="title">input</span> <span class="title">type</span>=&quot;<span class="title">text</span>&quot; <span class="title">id</span>=&quot;<span class="title">link</span>-<span class="title">search</span>&quot; <span class="title">placeholder</span>=&quot;搜索站点名称或描述…&quot;&gt;</span></span><br><span class="line"><span class="class">  &lt;/<span class="title">div</span>&gt;</span></span><br><span class="line"><span class="class"></span></span><br><span class="line"><span class="class">  &lt;!-- 友链列表 --&gt;</span></span><br><span class="line"><span class="class">  &lt;?<span class="title">php</span></span></span><br><span class="line"><span class="class">  $<span class="title">cats</span> = <span class="title">get_terms</span>( &#x27;<span class="title">link_category</span>&#x27;, <span class="title">array</span>( &#x27;<span class="title">hide_empty</span>&#x27; =&gt; 0 ) );</span></span><br><span class="line"><span class="class">  <span class="title">foreach</span> ( $<span class="title">cats</span> <span class="title">as</span> $<span class="title">cat</span> ) :</span></span><br><span class="line"><span class="class">    $<span class="title">bookmarks</span> = <span class="title">get_bookmarks</span>( <span class="title">array</span>(</span></span><br><span class="line"><span class="class">      &#x27;<span class="title">category</span>&#x27; =&gt; $<span class="title">cat</span>-&gt;<span class="title">term_id</span>,</span></span><br><span class="line"><span class="class">      &#x27;<span class="title">orderby</span>&#x27;  =&gt; &#x27;<span class="title">rating</span>&#x27;,</span></span><br><span class="line"><span class="class">      &#x27;<span class="title">order</span>&#x27;    =&gt; &#x27;<span class="title">DESC</span>&#x27;</span></span><br><span class="line"><span class="class">    ) );</span></span><br><span class="line"><span class="class">    <span class="title">if</span> ( <span class="title">empty</span>( $<span class="title">bookmarks</span> ) ) <span class="title">continue</span>;</span></span><br><span class="line"><span class="class">  ?&gt;</span></span><br><span class="line"><span class="class">    &lt;<span class="title">div</span> <span class="title">class</span>=&quot;<span class="title">link</span>-<span class="title">cat</span>&quot;&gt;</span></span><br><span class="line"><span class="class">      &lt;<span class="title">h2</span>&gt;&lt;?<span class="title">php</span> <span class="title">echo</span> <span class="title">esc_html</span>( $<span class="title">cat</span>-&gt;<span class="title">name</span> ); ?&gt;&lt;/<span class="title">h2</span>&gt;</span></span><br><span class="line"><span class="class">      &lt;?<span class="title">php</span> <span class="title">if</span> ( ! <span class="title">empty</span>( $<span class="title">cat</span>-&gt;<span class="title">description</span> ) ) : ?&gt;</span></span><br><span class="line"><span class="class">        &lt;<span class="title">p</span> <span class="title">class</span>=&quot;<span class="title">link</span>-<span class="title">cat</span>-<span class="title">desc</span>&quot;&gt;&lt;?<span class="title">php</span> <span class="title">echo</span> <span class="title">esc_html</span>( $<span class="title">cat</span>-&gt;<span class="title">description</span> ); ?&gt;&lt;/<span class="title">p</span>&gt;</span></span><br><span class="line"><span class="class">      &lt;?<span class="title">php</span> <span class="title">endif</span>; ?&gt;</span></span><br><span class="line"><span class="class">      &lt;<span class="title">div</span> <span class="title">class</span>=&quot;<span class="title">links</span>&quot;&gt;</span></span><br><span class="line"><span class="class">      &lt;?<span class="title">php</span> <span class="title">foreach</span> ( $<span class="title">bookmarks</span> <span class="title">as</span> $<span class="title">link</span> ) : ?&gt;</span></span><br><span class="line"><span class="class">        &lt;<span class="title">a</span> <span class="title">class</span>=&quot;<span class="title">link</span>-<span class="title">card</span>&quot; <span class="title">href</span>=&quot;&lt;?<span class="title">php</span> <span class="title">echo</span> <span class="title">esc_url</span>( $<span class="title">link</span>-&gt;<span class="title">link_url</span> ); ?&gt;&quot; <span class="title">target</span>=&quot;<span class="title">_blank</span>&quot; <span class="title">rel</span>=&quot;<span class="title">noopener</span>&quot;&gt;</span></span><br><span class="line"><span class="class">        &lt;<span class="title">img</span></span></span><br><span class="line"><span class="class">          <span class="title">src</span>=&quot;&lt;?<span class="title">php</span> <span class="title">echo</span> <span class="title">esc_url</span>( $<span class="title">link</span>-&gt;<span class="title">link_image</span> ?: &#x27;&#x27; ); ?&gt;&quot;</span></span><br><span class="line"><span class="class">          <span class="title">data</span>-<span class="title">default</span>=&quot;&lt;?<span class="title">php</span> <span class="title">echo</span> <span class="title">esc_url</span>( <span class="title">get_template_directory_uri</span>() . &#x27;/<span class="title">self</span>-<span class="title">innovate</span>/<span class="title">default</span>-<span class="title">avatar</span>.<span class="title">jpg</span>&#x27; ); ?&gt;&quot;</span></span><br><span class="line"><span class="class">          <span class="title">alt</span>=&quot;&lt;?<span class="title">php</span> <span class="title">echo</span> <span class="title">esc_attr</span>( $<span class="title">link</span>-&gt;<span class="title">link_name</span> ); ?&gt;&quot;</span></span><br><span class="line"><span class="class">          <span class="title">onerror</span>=&quot;<span class="title">this</span>.<span class="title">src</span>=<span class="title">this</span>.<span class="title">dataset</span>.<span class="title">default</span>&quot;</span></span><br><span class="line"><span class="class">          <span class="title">onload</span>=&quot;<span class="title">clearTimeout</span>(<span class="title">this</span>.<span class="title">t</span>)&quot;</span></span><br><span class="line"><span class="class">          <span class="title">ontimeout</span>=&quot;<span class="title">this</span>.<span class="title">src</span>=<span class="title">this</span>.<span class="title">dataset</span>.<span class="title">default</span>&quot;</span></span><br><span class="line"><span class="class">      /&gt;</span></span><br><span class="line"><span class="class">        &lt;<span class="title">script</span>&gt;</span></span><br><span class="line"><span class="class">          (<span class="title">function</span>(<span class="title">img</span>)</span>&#123;</span><br><span class="line">            img.t = <span class="title function_ invoke__">setTimeout</span>(function()&#123; img.src = img.dataset.<span class="keyword">default</span>; &#125;, <span class="number">3000</span>);</span><br><span class="line">          &#125;)(document.currentScript.previousElementSibling);</span><br><span class="line">        &lt;/script&gt;</span><br><span class="line">          &lt;div <span class="class"><span class="keyword">class</span>=&quot;<span class="title">info</span>&quot;&gt;</span></span><br><span class="line"><span class="class">            &lt;<span class="title">div</span> <span class="title">class</span>=&quot;<span class="title">name</span>&quot;&gt;&lt;?<span class="title">php</span> <span class="title">echo</span> <span class="title">esc_html</span>( $<span class="title">link</span>-&gt;<span class="title">link_name</span> ); ?&gt;&lt;/<span class="title">div</span>&gt;</span></span><br><span class="line"><span class="class">            &lt;<span class="title">div</span> <span class="title">class</span>=&quot;<span class="title">desc</span>&quot;&gt;&lt;?<span class="title">php</span> <span class="title">echo</span> <span class="title">esc_html</span>( $<span class="title">link</span>-&gt;<span class="title">link_description</span> ); ?&gt;&lt;/<span class="title">div</span>&gt;</span></span><br><span class="line"><span class="class">          &lt;/<span class="title">div</span>&gt;</span></span><br><span class="line"><span class="class">        &lt;/<span class="title">a</span>&gt;</span></span><br><span class="line"><span class="class">      &lt;?<span class="title">php</span> <span class="title">endforeach</span>; ?&gt;</span></span><br><span class="line"><span class="class">      &lt;/<span class="title">div</span>&gt;</span></span><br><span class="line"><span class="class">    &lt;/<span class="title">div</span>&gt;</span></span><br><span class="line"><span class="class">  &lt;?<span class="title">php</span> <span class="title">endforeach</span>; ?&gt;</span></span><br><span class="line"><span class="class"></span></span><br><span class="line"><span class="class">    &lt;!-- 后台内容显示 --&gt;</span></span><br><span class="line"><span class="class">  &lt;?<span class="title">php</span> <span class="title">if</span> ( <span class="title">get_the_content</span>() ) : ?&gt;</span></span><br><span class="line"><span class="class">    &lt;<span class="title">div</span> <span class="title">class</span>=&quot;<span class="title">link</span>-<span class="title">intro</span>&quot; <span class="title">style</span>=&quot;<span class="title">margin</span>-<span class="title">bottom</span>:30<span class="title">px</span>;&quot;&gt;&lt;?<span class="title">php</span> <span class="title">the_content</span>(); ?&gt;&lt;/<span class="title">div</span>&gt;</span></span><br><span class="line"><span class="class">  &lt;?<span class="title">php</span> <span class="title">endif</span>; ?&gt;</span></span><br><span class="line"><span class="class"></span></span><br><span class="line"><span class="class">  </span></span><br><span class="line"><span class="class">  &lt;!-- 友链申请弹窗 --&gt;</span></span><br><span class="line"><span class="class">  &lt;<span class="title">div</span> <span class="title">class</span>=&quot;<span class="title">btn</span>-<span class="title">center</span>&quot;&gt;</span></span><br><span class="line"><span class="class">    &lt;<span class="title">button</span> <span class="title">id</span>=&quot;<span class="title">open</span>-<span class="title">link</span>-<span class="title">modal</span>&quot; <span class="title">class</span>=&quot;<span class="title">btn</span>-<span class="title">hero</span>&quot;&gt;</span></span><br><span class="line"><span class="class">      &lt;<span class="title">span</span>&gt;申请交换友链&lt;/<span class="title">span</span>&gt;</span></span><br><span class="line"><span class="class">    &lt;/<span class="title">button</span>&gt;</span></span><br><span class="line"><span class="class">  &lt;/<span class="title">div</span>&gt;</span></span><br><span class="line"><span class="class"></span></span><br><span class="line"><span class="class"></span></span><br><span class="line"><span class="class">  &lt;!-- ===== 评论区 ===== --&gt;</span></span><br><span class="line"><span class="class">    &lt;?<span class="title">php</span></span></span><br><span class="line"><span class="class">    // <span class="title">while</span> ( <span class="title">have_posts</span>() ) : <span class="title">the_post</span>();</span></span><br><span class="line"><span class="class">        <span class="title">if</span> ( <span class="title">comments_open</span>() || <span class="title">get_comments_number</span>() ) : ?&gt;</span></span><br><span class="line"><span class="class">            &lt;<span class="title">section</span> <span class="title">class</span>=&quot;<span class="title">link</span>-<span class="title">comments</span>&quot;&gt;</span></span><br><span class="line"><span class="class">                &lt;<span class="title">h2</span> <span class="title">class</span>=&quot;<span class="title">comments</span>-<span class="title">title</span>&quot;&gt;&lt;/<span class="title">h2</span>&gt;</span></span><br><span class="line"><span class="class">                &lt;?<span class="title">php</span> <span class="title">comments_template</span>(); ?&gt;</span></span><br><span class="line"><span class="class">            &lt;/<span class="title">section</span>&gt;</span></span><br><span class="line"><span class="class">        &lt;?<span class="title">php</span></span></span><br><span class="line"><span class="class">        <span class="title">endif</span>;</span></span><br><span class="line"><span class="class">    // <span class="title">endwhile</span>;</span></span><br><span class="line"><span class="class">    ?&gt;</span></span><br><span class="line"><span class="class"></span></span><br><span class="line"><span class="class">  &lt;!-- 弹窗 --&gt;</span></span><br><span class="line"><span class="class">  &lt;<span class="title">div</span> <span class="title">id</span>=&quot;<span class="title">link</span>-<span class="title">modal</span>-<span class="title">overlay</span>&quot; <span class="title">class</span>=&quot;<span class="title">modal</span>-<span class="title">fade</span>&quot;&gt;</span></span><br><span class="line"><span class="class">    &lt;<span class="title">div</span> <span class="title">class</span>=&quot;<span class="title">modal</span>-<span class="title">dialog</span>&quot;&gt;</span></span><br><span class="line"><span class="class">      &lt;<span class="title">button</span> <span class="title">class</span>=&quot;<span class="title">modal</span>-<span class="title">close</span>&quot; <span class="title">aria</span>-<span class="title">label</span>=&quot;关闭&quot;&gt;×&lt;/<span class="title">button</span>&gt;</span></span><br><span class="line"><span class="class">      &lt;<span class="title">h3</span> <span class="title">class</span>=&quot;<span class="title">modal</span>-<span class="title">title</span>&quot;&gt;申请交换友链&lt;/<span class="title">h3</span>&gt;</span></span><br><span class="line"><span class="class">      &lt;<span class="title">form</span> <span class="title">id</span>=&quot;<span class="title">link</span>-<span class="title">form</span>&quot; <span class="title">class</span>=&quot;<span class="title">modal</span>-<span class="title">form</span>&quot;&gt;</span></span><br><span class="line"><span class="class">        &lt;<span class="title">input</span> <span class="title">type</span>=&quot;<span class="title">text</span>&quot;   <span class="title">name</span>=&quot;<span class="title">link_name</span>&quot;        <span class="title">placeholder</span>=&quot;网站名称 *&quot; <span class="title">required</span>&gt;</span></span><br><span class="line"><span class="class">        &lt;<span class="title">input</span> <span class="title">type</span>=&quot;<span class="title">url</span>&quot;    <span class="title">name</span>=&quot;<span class="title">link_url</span>&quot;         <span class="title">placeholder</span>=&quot;网站地址 *&quot; <span class="title">required</span>&gt;</span></span><br><span class="line"><span class="class">        &lt;<span class="title">input</span> <span class="title">type</span>=&quot;<span class="title">email</span>&quot;  <span class="title">name</span>=&quot;<span class="title">link_owner_email</span>&quot; <span class="title">placeholder</span>=&quot;站长邮箱 *&quot; <span class="title">required</span>&gt;</span></span><br><span class="line"><span class="class">        &lt;<span class="title">input</span> <span class="title">type</span>=&quot;<span class="title">url</span>&quot;    <span class="title">name</span>=&quot;<span class="title">link_image</span>&quot;       <span class="title">placeholder</span>=&quot;头像 / <span class="title">Logo</span> *&quot; <span class="title">required</span>&gt;</span></span><br><span class="line"><span class="class">        &lt;<span class="title">textarea</span> <span class="title">name</span>=&quot;<span class="title">link_description</span>&quot; <span class="title">rows</span>=&quot;3&quot; <span class="title">placeholder</span>=&quot;一句话描述 *&quot; <span class="title">required</span>&gt;&lt;/<span class="title">textarea</span>&gt;</span></span><br><span class="line"><span class="class">        &lt;<span class="title">button</span> <span class="title">type</span>=&quot;<span class="title">submit</span>&quot; <span class="title">class</span>=&quot;<span class="title">btn</span>-<span class="title">submit</span>&quot;&gt;提交申请&lt;/<span class="title">button</span>&gt;</span></span><br><span class="line"><span class="class">        &lt;<span class="title">p</span> <span class="title">id</span>=&quot;<span class="title">form</span>-<span class="title">msg</span>&quot; <span class="title">class</span>=&quot;<span class="title">form</span>-<span class="title">msg</span>&quot;&gt;&lt;/<span class="title">p</span>&gt;</span></span><br><span class="line"><span class="class">      &lt;/<span class="title">form</span>&gt;</span></span><br><span class="line"><span class="class">    &lt;/<span class="title">div</span>&gt;</span></span><br><span class="line"><span class="class">  &lt;/<span class="title">div</span>&gt;</span></span><br><span class="line"><span class="class">&lt;/<span class="title">div</span>&gt;</span></span><br><span class="line"><span class="class"></span></span><br><span class="line"><span class="class">&lt;!-- 载入独立脚本 --&gt;</span></span><br><span class="line"><span class="class">&lt;<span class="title">script</span>&gt;</span></span><br><span class="line"><span class="class">  <span class="title">var</span> <span class="title">ajax_comment_obj</span> = &lt;?<span class="title">php</span> <span class="title">echo</span> <span class="title">wp_json_encode</span>(<span class="title">array</span>(&#x27;<span class="title">ajax_url</span>&#x27; =&gt; <span class="title">admin_url</span>(&#x27;<span class="title">admin</span>-<span class="title">ajax</span>.<span class="title">php</span>&#x27;))); ?&gt;;</span></span><br><span class="line"><span class="class">&lt;/<span class="title">script</span>&gt;</span></span><br><span class="line"><span class="class">&lt;<span class="title">script</span> <span class="title">src</span>=&quot;&lt;?<span class="title">php</span> <span class="title">echo</span> <span class="title">get_template_directory_uri</span>(); ?&gt;/<span class="title">self</span>-<span class="title">innovate</span>/<span class="title">page</span>-<span class="title">links</span>.<span class="title">js</span>&quot; <span class="title">defer</span>&gt;&lt;/<span class="title">script</span>&gt;</span></span><br><span class="line"><span class="class"></span></span><br><span class="line"><span class="class">&lt;?<span class="title">php</span> <span class="title">get_footer</span>(); ?&gt;</span></span><br></pre></td></tr></table></figure><p>下面是核心代码的css样式</p><figure class="highlight css"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br><span class="line">89</span><br><span class="line">90</span><br><span class="line">91</span><br><span class="line">92</span><br><span class="line">93</span><br><span class="line">94</span><br><span class="line">95</span><br><span class="line">96</span><br><span class="line">97</span><br><span class="line">98</span><br><span class="line">99</span><br><span class="line">100</span><br><span class="line">101</span><br><span class="line">102</span><br><span class="line">103</span><br><span class="line">104</span><br><span class="line">105</span><br><span class="line">106</span><br><span class="line">107</span><br><span class="line">108</span><br><span class="line">109</span><br><span class="line">110</span><br><span class="line">111</span><br><span class="line">112</span><br><span class="line">113</span><br><span class="line">114</span><br><span class="line">115</span><br><span class="line">116</span><br><span class="line">117</span><br><span class="line">118</span><br><span class="line">119</span><br><span class="line">120</span><br><span class="line">121</span><br><span class="line">122</span><br><span class="line">123</span><br><span class="line">124</span><br><span class="line">125</span><br><span class="line">126</span><br><span class="line">127</span><br><span class="line">128</span><br><span class="line">129</span><br><span class="line">130</span><br><span class="line">131</span><br><span class="line">132</span><br><span class="line">133</span><br><span class="line">134</span><br><span class="line">135</span><br><span class="line">136</span><br><span class="line">137</span><br><span class="line">138</span><br><span class="line">139</span><br><span class="line">140</span><br><span class="line">141</span><br><span class="line">142</span><br><span class="line">143</span><br><span class="line">144</span><br><span class="line">145</span><br><span class="line">146</span><br><span class="line">147</span><br><span class="line">148</span><br><span class="line">149</span><br><span class="line">150</span><br><span class="line">151</span><br><span class="line">152</span><br><span class="line">153</span><br><span class="line">154</span><br><span class="line">155</span><br><span class="line">156</span><br><span class="line">157</span><br><span class="line">158</span><br><span class="line">159</span><br><span class="line">160</span><br><span class="line">161</span><br><span class="line">162</span><br><span class="line">163</span><br><span class="line">164</span><br><span class="line">165</span><br><span class="line">166</span><br><span class="line">167</span><br><span class="line">168</span><br><span class="line">169</span><br><span class="line">170</span><br><span class="line">171</span><br><span class="line">172</span><br><span class="line">173</span><br><span class="line">174</span><br><span class="line">175</span><br><span class="line">176</span><br><span class="line">177</span><br><span class="line">178</span><br><span class="line">179</span><br><span class="line">180</span><br><span class="line">181</span><br><span class="line">182</span><br><span class="line">183</span><br><span class="line">184</span><br><span class="line">185</span><br><span class="line">186</span><br><span class="line">187</span><br><span class="line">188</span><br><span class="line">189</span><br><span class="line">190</span><br><span class="line">191</span><br><span class="line">192</span><br><span class="line">193</span><br><span class="line">194</span><br><span class="line">195</span><br><span class="line">196</span><br><span class="line">197</span><br><span class="line">198</span><br><span class="line">199</span><br><span class="line">200</span><br><span class="line">201</span><br><span class="line">202</span><br><span class="line">203</span><br><span class="line">204</span><br><span class="line">205</span><br><span class="line">206</span><br><span class="line">207</span><br><span class="line">208</span><br><span class="line">209</span><br><span class="line">210</span><br><span class="line">211</span><br><span class="line">212</span><br><span class="line">213</span><br><span class="line">214</span><br><span class="line">215</span><br><span class="line">216</span><br><span class="line">217</span><br><span class="line">218</span><br><span class="line">219</span><br><span class="line">220</span><br><span class="line">221</span><br><span class="line">222</span><br><span class="line">223</span><br><span class="line">224</span><br><span class="line">225</span><br><span class="line">226</span><br><span class="line">227</span><br><span class="line">228</span><br><span class="line">229</span><br><span class="line">230</span><br><span class="line">231</span><br><span class="line">232</span><br><span class="line">233</span><br><span class="line">234</span><br><span class="line">235</span><br><span class="line">236</span><br><span class="line">237</span><br><span class="line">238</span><br><span class="line">239</span><br><span class="line">240</span><br><span class="line">241</span><br><span class="line">242</span><br><span class="line">243</span><br><span class="line">244</span><br><span class="line">245</span><br><span class="line">246</span><br><span class="line">247</span><br><span class="line">248</span><br><span class="line">249</span><br><span class="line">250</span><br><span class="line">251</span><br><span class="line">252</span><br><span class="line">253</span><br><span class="line">254</span><br><span class="line">255</span><br><span class="line">256</span><br><span class="line">257</span><br><span class="line">258</span><br><span class="line">259</span><br><span class="line">260</span><br><span class="line">261</span><br><span class="line">262</span><br><span class="line">263</span><br><span class="line">264</span><br><span class="line">265</span><br><span class="line">266</span><br><span class="line">267</span><br><span class="line">268</span><br><span class="line">269</span><br><span class="line">270</span><br><span class="line">271</span><br><span class="line">272</span><br><span class="line">273</span><br><span class="line">274</span><br><span class="line">275</span><br><span class="line">276</span><br><span class="line">277</span><br><span class="line">278</span><br><span class="line">279</span><br><span class="line">280</span><br><span class="line">281</span><br><span class="line">282</span><br><span class="line">283</span><br><span class="line">284</span><br><span class="line">285</span><br><span class="line">286</span><br><span class="line">287</span><br><span class="line">288</span><br><span class="line">289</span><br><span class="line">290</span><br><span class="line">291</span><br><span class="line">292</span><br><span class="line">293</span><br><span class="line">294</span><br><span class="line">295</span><br><span class="line">296</span><br><span class="line">297</span><br><span class="line">298</span><br><span class="line">299</span><br><span class="line">300</span><br><span class="line">301</span><br><span class="line">302</span><br><span class="line">303</span><br><span class="line">304</span><br><span class="line">305</span><br><span class="line">306</span><br><span class="line">307</span><br><span class="line">308</span><br><span class="line">309</span><br><span class="line">310</span><br><span class="line">311</span><br><span class="line">312</span><br><span class="line">313</span><br><span class="line">314</span><br><span class="line">315</span><br><span class="line">316</span><br><span class="line">317</span><br><span class="line">318</span><br><span class="line">319</span><br><span class="line">320</span><br><span class="line">321</span><br><span class="line">322</span><br><span class="line">323</span><br><span class="line">324</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> *  * page-links.css</span></span><br><span class="line"><span class="comment"> *   * 版本：1.0.0</span></span><br><span class="line"><span class="comment"> *    * 描述：友情链接页面专用样式</span></span><br><span class="line"><span class="comment"> *     * 作者：Eucalyptus</span></span><br><span class="line"><span class="comment"> *      * 创建日期：2025-08-30</span></span><br><span class="line"><span class="comment"> *       * 修改记录：</span></span><br><span class="line"><span class="comment"> *        *   - 2025-08-30：首版，含友链卡片、搜索、弹窗</span></span><br><span class="line"><span class="comment"> *         */</span></span><br><span class="line"></span><br><span class="line"><span class="comment">/* ========= 1. 页面通用 ========= */</span></span><br><span class="line"><span class="selector-class">.page-links</span>&#123;</span><br><span class="line">  <span class="attribute">max-width</span>:<span class="number">960px</span>;</span><br><span class="line">  <span class="attribute">margin</span>:<span class="number">0</span> auto;</span><br><span class="line">  <span class="attribute">padding</span>:<span class="number">40px</span> <span class="number">20px</span>;</span><br><span class="line">  <span class="attribute">font-family</span>:-apple-system,BlinkMacSystemFont,<span class="string">&quot;Segoe UI&quot;</span>,Roboto,<span class="string">&quot;Helvetica Neue&quot;</span>,Arial,<span class="string">&quot;Noto Sans&quot;</span>,sans-serif;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">/* ============ 顶部标题（跟随深浅 + 移动端适配） ============ */</span></span><br><span class="line"><span class="comment">/* 友情链接横幅 */</span></span><br><span class="line"><span class="selector-class">.hero-title</span>&#123;</span><br><span class="line">  <span class="attribute">padding</span>:<span class="number">3.5rem</span> <span class="number">1.5rem</span> <span class="number">2.5rem</span>;</span><br><span class="line">  <span class="attribute">border-radius</span>:<span class="number">0</span> <span class="number">0</span> <span class="number">1.5rem</span> <span class="number">1.5rem</span>;</span><br><span class="line">  <span class="attribute">text-align</span>:center;</span><br><span class="line">&#125;</span><br><span class="line"><span class="selector-class">.hero-title</span> <span class="selector-tag">h1</span>&#123;</span><br><span class="line">  <span class="attribute">font-size</span>:<span class="number">2.2rem</span>;</span><br><span class="line">  <span class="attribute">font-weight</span>:<span class="number">700</span>;</span><br><span class="line">  <span class="attribute">letter-spacing</span>:.<span class="number">5px</span>;</span><br><span class="line">  <span class="attribute">position</span>:relative;</span><br><span class="line">&#125;</span><br><span class="line"><span class="selector-class">.hero-title</span> <span class="selector-tag">h1</span><span class="selector-pseudo">::after</span>&#123;</span><br><span class="line">  <span class="attribute">content</span>:<span class="string">&#x27;&#x27;</span>;</span><br><span class="line">  <span class="attribute">display</span>:block;</span><br><span class="line">  <span class="attribute">width</span>:<span class="number">60px</span>;</span><br><span class="line">  <span class="attribute">height</span>:<span class="number">3px</span>;</span><br><span class="line">  <span class="attribute">background</span>:<span class="number">#667eea</span>;</span><br><span class="line">  <span class="attribute">margin</span>:<span class="number">8px</span> auto <span class="number">0</span>;</span><br><span class="line">  <span class="attribute">border-radius</span>:<span class="number">2px</span>;</span><br><span class="line">&#125;</span><br><span class="line"><span class="selector-class">.hero-sub</span>&#123;</span><br><span class="line">  <span class="attribute">font-size</span>:<span class="number">1rem</span>;</span><br><span class="line">  <span class="attribute">color</span>:<span class="number">#666</span>;</span><br><span class="line">  <span class="attribute">margin-top</span>:.<span class="number">75rem</span>;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="comment">/* ========= 玻璃态卡片 3.0 ========= */</span></span><br><span class="line"><span class="selector-class">.links</span>&#123;</span><br><span class="line">  <span class="attribute">display</span>:grid;</span><br><span class="line">  <span class="attribute">grid-template-columns</span>:<span class="built_in">repeat</span>(<span class="number">3</span>, <span class="number">1</span>fr); <span class="comment">/* 固定 3 列 */</span></span><br><span class="line">  <span class="attribute">gap</span>:<span class="number">24px</span>;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="selector-class">.link-card</span>&#123;</span><br><span class="line">  <span class="attribute">position</span>:relative;</span><br><span class="line">  <span class="attribute">padding</span>:<span class="number">20px</span>;</span><br><span class="line">  <span class="attribute">background</span>:transparent;                <span class="comment">/* 跟随深浅 */</span></span><br><span class="line">  <span class="attribute">border</span>:<span class="number">1px</span> solid <span class="built_in">rgba</span>(<span class="built_in">var</span>(--color-border,<span class="number">0</span> <span class="number">0</span> <span class="number">0</span>),.<span class="number">1</span>);</span><br><span class="line">  <span class="attribute">border-radius</span>:<span class="number">20px</span>;</span><br><span class="line">  <span class="attribute">box-shadow</span>:<span class="number">0</span> <span class="number">8px</span> <span class="number">32px</span> <span class="built_in">rgba</span>(<span class="number">0</span>,<span class="number">0</span>,<span class="number">0</span>,.<span class="number">08</span>);  <span class="comment">/* 深浅通用阴影 */</span></span><br><span class="line">  <span class="attribute">transition</span>:.<span class="number">4s</span> <span class="built_in">cubic-bezier</span>(.<span class="number">175</span>,.<span class="number">885</span>,.<span class="number">32</span>,<span class="number">1.275</span>);</span><br><span class="line">  <span class="attribute">overflow</span>:hidden;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="selector-pseudo">:root</span>&#123;</span><br><span class="line">  <span class="attr">--color-border</span>: <span class="number">0</span> <span class="number">0</span> <span class="number">0</span>;       <span class="comment">/* 深色模式 */</span></span><br><span class="line">&#125;</span><br><span class="line"><span class="selector-attr">[data-theme=<span class="string">&quot;light&quot;</span>]</span>&#123;</span><br><span class="line">  <span class="attr">--color-border</span>: <span class="number">255</span> <span class="number">255</span> <span class="number">255</span>; <span class="comment">/* 浅色模式 */</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="selector-class">.link-card</span><span class="selector-pseudo">::before</span>&#123;                               <span class="comment">/* 光泽渐变 */</span></span><br><span class="line">  <span class="attribute">content</span>:<span class="string">&#x27;&#x27;</span>;</span><br><span class="line">  <span class="attribute">position</span>:absolute;</span><br><span class="line">  <span class="attribute">inset</span>:<span class="number">0</span>;</span><br><span class="line">  <span class="attribute">background</span>:<span class="built_in">linear-gradient</span>(<span class="number">135deg</span>,transparent <span class="number">40%</span>,<span class="built_in">rgba</span>(<span class="number">255</span>,<span class="number">255</span>,<span class="number">255</span>,.<span class="number">3</span>));</span><br><span class="line">  <span class="attribute">pointer-events</span>:none;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="selector-class">.link-card</span><span class="selector-pseudo">:hover</span>&#123;</span><br><span class="line">  <span class="attribute">transform</span>:<span class="built_in">translateY</span>(-<span class="number">6px</span>) <span class="built_in">scale</span>(<span class="number">1.02</span>);</span><br><span class="line">  <span class="attribute">box-shadow</span>:<span class="number">0</span> <span class="number">12px</span> <span class="number">48px</span> <span class="built_in">rgba</span>(<span class="number">102</span>,<span class="number">126</span>,<span class="number">234</span>,.<span class="number">25</span>);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">/* 头像 */</span></span><br><span class="line"><span class="selector-class">.link-card</span> <span class="selector-tag">img</span>&#123;</span><br><span class="line">  <span class="attribute">width</span>:<span class="number">64px</span>;</span><br><span class="line">  <span class="attribute">height</span>:<span class="number">64px</span>;</span><br><span class="line">  <span class="attribute">border-radius</span>:<span class="number">50%</span>;</span><br><span class="line">  <span class="attribute">border</span>:<span class="number">2px</span> solid <span class="number">#fff</span>;</span><br><span class="line">  <span class="attribute">box-shadow</span>:<span class="number">0</span> <span class="number">4px</span> <span class="number">12px</span> <span class="built_in">rgba</span>(<span class="number">0</span>,<span class="number">0</span>,<span class="number">0</span>,.<span class="number">08</span>);</span><br><span class="line">  <span class="attribute">object-fit</span>:cover;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">/* 文字区 */</span></span><br><span class="line"><span class="selector-class">.link-card</span> <span class="selector-class">.info</span>&#123;</span><br><span class="line">  <span class="attribute">margin-left</span>:<span class="number">16px</span>;</span><br><span class="line">&#125;</span><br><span class="line"><span class="selector-class">.link-card</span> <span class="selector-class">.name</span>&#123;</span><br><span class="line">  <span class="attribute">font-size</span>:<span class="number">18px</span>;</span><br><span class="line">  <span class="attribute">font-weight</span>:<span class="number">700</span>;</span><br><span class="line">  <span class="attribute">letter-spacing</span>:.<span class="number">5px</span>;</span><br><span class="line">  <span class="attribute">margin-bottom</span>:<span class="number">6px</span>;</span><br><span class="line">&#125;</span><br><span class="line"><span class="selector-class">.link-card</span> <span class="selector-class">.desc</span>&#123;</span><br><span class="line">  <span class="attribute">font-size</span>:<span class="number">14px</span>;</span><br><span class="line">  <span class="attribute">color</span>:<span class="number">#555</span>;</span><br><span class="line">  <span class="attribute">line-height</span>:<span class="number">1.5</span>;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">/* 右上角角标（纯 CSS） */</span></span><br><span class="line"><span class="selector-class">.link-card</span>&#123;</span><br><span class="line">  <span class="attribute">position</span>: relative;           <span class="comment">/* 为伪元素定位 */</span></span><br><span class="line">&#125;</span><br><span class="line"><span class="selector-class">.link-card</span><span class="selector-pseudo">::after</span>&#123;</span><br><span class="line">  <span class="attribute">content</span>: <span class="string">&quot;友链&quot;</span>;              <span class="comment">/* 角标文字 */</span></span><br><span class="line">  <span class="attribute">position</span>: absolute;</span><br><span class="line">  <span class="attribute">top</span>: <span class="number">8px</span>;</span><br><span class="line">  <span class="attribute">right</span>: <span class="number">8px</span>;</span><br><span class="line">  <span class="attribute">padding</span>: <span class="number">2px</span> <span class="number">6px</span>;</span><br><span class="line">  <span class="attribute">font-size</span>: <span class="number">11px</span>;</span><br><span class="line">  <span class="attribute">font-weight</span>: <span class="number">600</span>;</span><br><span class="line">  <span class="attribute">color</span>: <span class="number">#fff</span>;</span><br><span class="line">  <span class="attribute">background</span>: <span class="built_in">linear-gradient</span>(<span class="number">135deg</span>,<span class="number">#ea66d4</span>,<span class="number">#ac5196</span>);</span><br><span class="line">  <span class="attribute">border-radius</span>: <span class="number">4px</span>;</span><br><span class="line">  <span class="attribute">opacity</span>: <span class="number">0</span>;</span><br><span class="line">  <span class="attribute">transition</span>: opacity .<span class="number">3s</span>;</span><br><span class="line">  <span class="attribute">pointer-events</span>: none;         <span class="comment">/* 不影响点击 */</span></span><br><span class="line">&#125;</span><br><span class="line"><span class="selector-class">.link-card</span><span class="selector-pseudo">:hover</span><span class="selector-pseudo">::after</span>&#123;</span><br><span class="line">  <span class="attribute">opacity</span>: <span class="number">1</span>;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">/* ===== 分类标题 3.0 ===== */</span></span><br><span class="line"><span class="selector-class">.link-cat</span>&#123;</span><br><span class="line">  <span class="attribute">position</span>:relative;</span><br><span class="line">  <span class="attribute">margin-bottom</span>:<span class="number">40px</span>;</span><br><span class="line">&#125;</span><br><span class="line"><span class="selector-class">.link-cat</span> <span class="selector-tag">h2</span>&#123;</span><br><span class="line">  <span class="attribute">display</span>:inline-flex;</span><br><span class="line">  <span class="attribute">align-items</span>:center;</span><br><span class="line">  <span class="attribute">gap</span>:<span class="number">8px</span>;</span><br><span class="line">  <span class="attribute">padding</span>:<span class="number">8px</span> <span class="number">18px</span>;</span><br><span class="line">  <span class="attribute">font-size</span>:<span class="number">20px</span>;</span><br><span class="line">  <span class="attribute">margin-bottom</span>: <span class="number">15px</span>;</span><br><span class="line">  <span class="attribute">font-weight</span>:<span class="number">700</span>;</span><br><span class="line">  <span class="attribute">color</span>:<span class="number">#fff</span>;</span><br><span class="line">  <span class="attribute">background</span>:<span class="built_in">linear-gradient</span>(<span class="number">135deg</span>,<span class="number">#667eea</span>,<span class="number">#764ba2</span>);</span><br><span class="line">  <span class="attribute">border-radius</span>:<span class="number">20px</span>;</span><br><span class="line">  <span class="attribute">box-shadow</span>:<span class="number">0</span> <span class="number">4px</span> <span class="number">12px</span> <span class="built_in">rgba</span>(<span class="number">102</span>,<span class="number">126</span>,<span class="number">234</span>,.<span class="number">25</span>);</span><br><span class="line">  <span class="attribute">letter-spacing</span>:.<span class="number">5px</span>;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">/* 分类描述 */</span></span><br><span class="line"><span class="selector-class">.link-cat-desc</span>&#123;</span><br><span class="line">  <span class="attribute">margin</span>: -<span class="number">8px</span> <span class="number">0</span> <span class="number">24px</span> <span class="number">18px</span>;   <span class="comment">/* 负值贴紧标题，左侧与标题文字对齐 */</span></span><br><span class="line">  <span class="attribute">font-size</span>: <span class="number">0.9rem</span>;</span><br><span class="line">  <span class="attribute">color</span>: <span class="number">#555</span>;</span><br><span class="line">  <span class="attribute">letter-spacing</span>: .<span class="number">4px</span>;</span><br><span class="line">  <span class="attribute">line-height</span>: <span class="number">1.5</span>;</span><br><span class="line">  <span class="attribute">max-width</span>: <span class="number">540px</span>;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">/* ========= 移动端适配 ========= */</span></span><br><span class="line"><span class="keyword">@media</span> (<span class="attribute">max-width</span>: <span class="number">768px</span>) &#123;</span><br><span class="line">  <span class="selector-class">.links</span> &#123;</span><br><span class="line">    <span class="attribute">grid-template-columns</span>: <span class="number">1</span>fr; <span class="comment">/* 1 列 */</span></span><br><span class="line">    <span class="attribute">gap</span>: <span class="number">20px</span>;</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  <span class="selector-class">.link-card</span> &#123;</span><br><span class="line">    <span class="attribute">padding</span>: <span class="number">16px</span>;</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  <span class="selector-class">.link-card</span> <span class="selector-tag">img</span> &#123;</span><br><span class="line">    <span class="attribute">width</span>: <span class="number">48px</span>;</span><br><span class="line">    <span class="attribute">height</span>: <span class="number">48px</span>;</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  <span class="selector-class">.link-card</span> <span class="selector-class">.name</span> &#123;</span><br><span class="line">    <span class="attribute">font-size</span>: <span class="number">16px</span>;</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  <span class="selector-class">.link-card</span> <span class="selector-class">.desc</span> &#123;</span><br><span class="line">    <span class="attribute">font-size</span>: <span class="number">13px</span>;</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br><span class="line"><span class="comment">/* ========= 4. 搜索框 ========= */</span></span><br><span class="line"><span class="selector-class">.link-search</span>&#123;</span><br><span class="line">  <span class="attribute">display</span>:flex;</span><br><span class="line">  <span class="attribute">margin-bottom</span>:<span class="number">40px</span>;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="selector-class">.link-search</span> <span class="selector-tag">input</span>&#123;</span><br><span class="line">  <span class="attribute">width</span>:<span class="number">100%</span>;</span><br><span class="line">  <span class="attribute">max-width</span>:<span class="number">320px</span>;</span><br><span class="line">  <span class="attribute">padding</span>:<span class="number">12px</span> <span class="number">40px</span> <span class="number">12px</span> <span class="number">16px</span>;</span><br><span class="line">  <span class="attribute">font-size</span>:<span class="number">15px</span>;</span><br><span class="line">  <span class="attribute">color</span>:<span class="number">#333</span>;</span><br><span class="line">  <span class="attribute">background</span>:<span class="number">#fff</span>;</span><br><span class="line">  <span class="attribute">border</span>:<span class="number">2px</span> solid transparent;</span><br><span class="line">  <span class="attribute">border-radius</span>:<span class="number">30px</span>;</span><br><span class="line">  <span class="attribute">box-shadow</span>:<span class="number">0</span> <span class="number">4px</span> <span class="number">12px</span> <span class="built_in">rgba</span>(<span class="number">102</span>,<span class="number">126</span>,<span class="number">234</span>,.<span class="number">15</span>);</span><br><span class="line">  <span class="attribute">transition</span>:border-color .<span class="number">3s</span>, box-shadow .<span class="number">3s</span>;</span><br><span class="line">  <span class="attribute">outline</span>:none;</span><br><span class="line">  <span class="attribute">background-image</span>:<span class="built_in">url</span>(<span class="string">&#x27;data:image/svg+xml;utf8,&lt;svg xmlns=&quot;http://www.w3.org/2000/svg&quot; width=&quot;20&quot; height=&quot;20&quot; viewBox=&quot;0 0 24 24&quot; fill=&quot;none&quot; stroke=&quot;%23667eea&quot; stroke-width=&quot;2&quot; stroke-linecap=&quot;round&quot; stroke-linejoin=&quot;round&quot;&gt;&lt;circle cx=&quot;11&quot; cy=&quot;11&quot; r=&quot;8&quot;&gt;&lt;/circle&gt;&lt;line x1=&quot;21&quot; y1=&quot;21&quot; x2=&quot;16.65&quot; y2=&quot;16.65&quot;&gt;&lt;/line&gt;&lt;/svg&gt;&#x27;</span>);</span><br><span class="line">  <span class="attribute">background-repeat</span>:no-repeat;</span><br><span class="line">  <span class="attribute">background-position</span><span class="selector-pseudo">:right</span> <span class="number">14px</span> center;</span><br><span class="line">  <span class="attribute">background-size</span>:<span class="number">20px</span> <span class="number">20px</span>;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">/* 悬浮/聚焦高亮 */</span></span><br><span class="line"><span class="selector-class">.link-search</span> <span class="selector-tag">input</span><span class="selector-pseudo">:focus</span>&#123;</span><br><span class="line">  <span class="attribute">border-color</span>:<span class="number">#667eea</span>;</span><br><span class="line">  <span class="attribute">box-shadow</span>:<span class="number">0</span> <span class="number">0</span> <span class="number">0</span> <span class="number">4px</span> <span class="built_in">rgba</span>(<span class="number">102</span>,<span class="number">126</span>,<span class="number">234</span>,.<span class="number">25</span>);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">/* placeholder 样式 */</span></span><br><span class="line"><span class="selector-class">.link-search</span> <span class="selector-tag">input</span><span class="selector-pseudo">::placeholder</span>&#123;</span><br><span class="line">  <span class="attribute">color</span>:<span class="number">#7f8c8d</span>;</span><br><span class="line">  <span class="attribute">font-size</span>:<span class="number">14px</span>;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">/* ========= 5. 按钮 &amp; 弹窗 ========= */</span></span><br><span class="line"><span class="comment">/* 5.1 触发按钮居中容器 */</span></span><br><span class="line"><span class="selector-class">.btn-center</span>&#123;</span><br><span class="line">  <span class="attribute">display</span>:flex;</span><br><span class="line">  <span class="attribute">justify-content</span>:center;</span><br><span class="line">  <span class="attribute">margin</span>:<span class="number">30px</span> <span class="number">0</span>;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">/* 5.2 主按钮样式 */</span></span><br><span class="line"><span class="selector-class">.btn-hero</span>&#123;</span><br><span class="line">  <span class="attribute">position</span>:relative;</span><br><span class="line">  <span class="attribute">padding</span>:<span class="number">12px</span> <span class="number">32px</span>;</span><br><span class="line">  <span class="attribute">font-size</span>:<span class="number">16px</span>;</span><br><span class="line">  <span class="attribute">font-weight</span>:<span class="number">600</span>;</span><br><span class="line">  <span class="attribute">color</span>:<span class="number">#fff</span>;</span><br><span class="line">  <span class="attribute">background</span>:<span class="built_in">linear-gradient</span>(<span class="number">135deg</span>,<span class="number">#667eea</span> <span class="number">0%</span>, <span class="number">#764ba2</span> <span class="number">100%</span>);</span><br><span class="line">  <span class="attribute">border</span>:none;</span><br><span class="line">  <span class="attribute">border-radius</span>:<span class="number">30px</span>;</span><br><span class="line">  <span class="attribute">cursor</span>:pointer;</span><br><span class="line">  <span class="attribute">box-shadow</span>:<span class="number">0</span> <span class="number">6px</span> <span class="number">20px</span> <span class="built_in">rgba</span>(<span class="number">102</span>,<span class="number">126</span>,<span class="number">234</span>,.<span class="number">4</span>);</span><br><span class="line">  <span class="attribute">transition</span>:all .<span class="number">3s</span>;</span><br><span class="line">  <span class="attribute">overflow</span>:hidden;</span><br><span class="line">&#125;</span><br><span class="line"><span class="selector-class">.btn-hero</span><span class="selector-pseudo">:hover</span>&#123;</span><br><span class="line">  <span class="attribute">transform</span>:<span class="built_in">translateY</span>(-<span class="number">3px</span>) <span class="built_in">scale</span>(<span class="number">1.03</span>);</span><br><span class="line">  <span class="attribute">box-shadow</span>:<span class="number">0</span> <span class="number">10px</span> <span class="number">30px</span> <span class="built_in">rgba</span>(<span class="number">102</span>,<span class="number">126</span>,<span class="number">234</span>,.<span class="number">55</span>);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">/* 5.3 弹窗遮罩 */</span></span><br><span class="line"><span class="selector-class">.modal-fade</span>&#123;</span><br><span class="line">  <span class="attribute">position</span>:fixed;</span><br><span class="line">  <span class="attribute">inset</span>:<span class="number">0</span>;</span><br><span class="line">  <span class="attribute">display</span>:flex;</span><br><span class="line">  <span class="attribute">align-items</span>:center;</span><br><span class="line">  <span class="attribute">justify-content</span>:center;</span><br><span class="line">  <span class="attribute">background</span>:<span class="built_in">rgba</span>(<span class="number">0</span>,<span class="number">0</span>,<span class="number">0</span>,.<span class="number">55</span>);</span><br><span class="line">  <span class="attribute">backdrop-filter</span>:<span class="built_in">blur</span>(<span class="number">4px</span>);</span><br><span class="line">  <span class="attribute">opacity</span>:<span class="number">0</span>;</span><br><span class="line">  <span class="attribute">visibility</span>:hidden;</span><br><span class="line">  <span class="attribute">transition</span>:.<span class="number">35s</span>;</span><br><span class="line">  <span class="attribute">z-index</span>:<span class="number">9999</span>;</span><br><span class="line">&#125;</span><br><span class="line"><span class="selector-class">.modal-fade</span><span class="selector-class">.show</span>&#123;</span><br><span class="line">  <span class="attribute">opacity</span>:<span class="number">1</span>;</span><br><span class="line">  <span class="attribute">visibility</span>:visible;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">/* 5.4 弹窗主体 */</span></span><br><span class="line"><span class="selector-class">.modal-dialog</span>&#123;</span><br><span class="line">  <span class="attribute">width</span>:<span class="number">92%</span>;</span><br><span class="line">  <span class="attribute">max-width</span>:<span class="number">420px</span>;</span><br><span class="line">  <span class="attribute">background</span>:<span class="number">#fff</span>;</span><br><span class="line">  <span class="attribute">border-radius</span>:<span class="number">16px</span>;</span><br><span class="line">  <span class="attribute">padding</span>:<span class="number">32px</span> <span class="number">36px</span> <span class="number">36px</span>;</span><br><span class="line">  <span class="attribute">position</span>:relative;</span><br><span class="line">  <span class="attribute">transform</span>:<span class="built_in">translateY</span>(-<span class="number">30px</span>) <span class="built_in">scale</span>(.<span class="number">95</span>);</span><br><span class="line">  <span class="attribute">transition</span>:transform .<span class="number">35s</span>;</span><br><span class="line">&#125;</span><br><span class="line"><span class="selector-class">.modal-fade</span><span class="selector-class">.show</span> <span class="selector-class">.modal-dialog</span>&#123;</span><br><span class="line">  <span class="attribute">transform</span>:none;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">/* 5.5 关闭按钮 */</span></span><br><span class="line"><span class="selector-class">.modal-close</span>&#123;</span><br><span class="line">  <span class="attribute">position</span>:absolute;</span><br><span class="line">  <span class="attribute">top</span>:<span class="number">14px</span>;</span><br><span class="line">  <span class="attribute">right</span>:<span class="number">18px</span>;</span><br><span class="line">  <span class="attribute">font-size</span>:<span class="number">26px</span>;</span><br><span class="line">  <span class="attribute">background</span>:none;</span><br><span class="line">  <span class="attribute">border</span>:none;</span><br><span class="line">  <span class="attribute">color</span>:<span class="number">#999</span>;</span><br><span class="line">  <span class="attribute">cursor</span>:pointer;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">/* 5.6 表单元素 */</span></span><br><span class="line"><span class="selector-class">.modal-form</span>&#123;</span><br><span class="line">  <span class="attribute">display</span>:flex;</span><br><span class="line">  <span class="attribute">flex-direction</span>:column;</span><br><span class="line">  <span class="attribute">gap</span>:<span class="number">14px</span>;</span><br><span class="line">&#125;</span><br><span class="line"><span class="selector-class">.modal-form</span> <span class="selector-tag">input</span>,</span><br><span class="line"><span class="selector-class">.modal-form</span> <span class="selector-tag">textarea</span>&#123;</span><br><span class="line">  <span class="attribute">padding</span>:<span class="number">12px</span> <span class="number">14px</span>;</span><br><span class="line">  <span class="attribute">border</span>:<span class="number">1px</span> solid <span class="number">#e1e5e9</span>;</span><br><span class="line">  <span class="attribute">border-radius</span>:<span class="number">8px</span>;</span><br><span class="line">&#125;</span><br><span class="line"><span class="selector-class">.btn-submit</span>&#123;</span><br><span class="line">  <span class="attribute">margin-top</span>:<span class="number">8px</span>;</span><br><span class="line">  <span class="attribute">padding</span>:<span class="number">12px</span>;</span><br><span class="line">  <span class="attribute">color</span>:<span class="number">#fff</span>;</span><br><span class="line">  <span class="attribute">border</span>:none;</span><br><span class="line">  <span class="attribute">border-radius</span>:<span class="number">30px</span>;</span><br><span class="line">  <span class="attribute">background</span>:<span class="built_in">linear-gradient</span>(<span class="number">135deg</span>,<span class="number">#667eea</span> <span class="number">0%</span>,<span class="number">#764ba2</span> <span class="number">100%</span>);</span><br><span class="line">  <span class="attribute">cursor</span>:pointer;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">/* 移动端补丁，溢出锁死 */</span></span><br><span class="line"><span class="selector-class">.page-links</span>&#123;</span><br><span class="line">  <span class="attribute">overflow-x</span>: hidden;   <span class="comment">/* 关键：禁止横向滚动 */</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>以下是JavaScript代码，主要用来实现前端友链页面的实时搜索功能、友链提交弹窗（使用模态框组件展示表单，包含必填字段验证和样式反馈）以及后端提交申请内容到后端的功能</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">/* ========= 实时搜索 ========= */</span></span><br><span class="line"><span class="variable language_">document</span>.<span class="title function_">getElementById</span>(<span class="string">&#x27;link-search&#x27;</span>).<span class="title function_">addEventListener</span>(<span class="string">&#x27;input&#x27;</span>, <span class="keyword">function</span> (<span class="params"></span>) &#123;</span><br><span class="line">  <span class="keyword">const</span> kw = <span class="variable language_">this</span>.<span class="property">value</span>.<span class="title function_">toLowerCase</span>();</span><br><span class="line">  <span class="variable language_">document</span>.<span class="title function_">querySelectorAll</span>(<span class="string">&#x27;.link-card&#x27;</span>).<span class="title function_">forEach</span>(<span class="function"><span class="params">card</span> =&gt;</span> &#123;</span><br><span class="line">    card.<span class="property">style</span>.<span class="property">display</span> = card.<span class="property">textContent</span>.<span class="title function_">toLowerCase</span>().<span class="title function_">includes</span>(kw) ? <span class="string">&#x27;flex&#x27;</span> : <span class="string">&#x27;none&#x27;</span>;</span><br><span class="line">  &#125;);</span><br><span class="line">&#125;);</span><br><span class="line"></span><br><span class="line"><span class="comment">/* ========= 弹窗控制 ========= */</span></span><br><span class="line"><span class="keyword">const</span> overlay = <span class="variable language_">document</span>.<span class="title function_">getElementById</span>(<span class="string">&#x27;link-modal-overlay&#x27;</span>);</span><br><span class="line"><span class="variable language_">document</span>.<span class="title function_">getElementById</span>(<span class="string">&#x27;open-link-modal&#x27;</span>).<span class="title function_">addEventListener</span>(<span class="string">&#x27;click&#x27;</span>, <span class="function">() =&gt;</span> overlay.<span class="property">classList</span>.<span class="title function_">add</span>(<span class="string">&#x27;show&#x27;</span>));</span><br><span class="line">overlay.<span class="title function_">addEventListener</span>(<span class="string">&#x27;click&#x27;</span>, <span class="function"><span class="params">e</span> =&gt;</span> &#123;</span><br><span class="line">  <span class="keyword">if</span> (e.<span class="property">target</span> === overlay || e.<span class="property">target</span>.<span class="property">classList</span>.<span class="title function_">contains</span>(<span class="string">&#x27;modal-close&#x27;</span>)) overlay.<span class="property">classList</span>.<span class="title function_">remove</span>(<span class="string">&#x27;show&#x27;</span>);</span><br><span class="line">&#125;);</span><br><span class="line"></span><br><span class="line"><span class="comment">/* ========= Ajax 提交友链 ========= */</span></span><br><span class="line"><span class="variable language_">document</span>.<span class="title function_">getElementById</span>(<span class="string">&#x27;link-form&#x27;</span>).<span class="title function_">addEventListener</span>(<span class="string">&#x27;submit&#x27;</span>, <span class="keyword">function</span> (<span class="params">e</span>) &#123;</span><br><span class="line">  e.<span class="title function_">preventDefault</span>();</span><br><span class="line">  <span class="keyword">const</span> data = <span class="keyword">new</span> <span class="title class_">FormData</span>(<span class="variable language_">this</span>);</span><br><span class="line">  data.<span class="title function_">append</span>(<span class="string">&#x27;action&#x27;</span>, <span class="string">&#x27;submit_link_apply&#x27;</span>);</span><br><span class="line">  <span class="title function_">fetch</span>(ajax_comment_obj.<span class="property">ajax_url</span>, &#123; <span class="attr">method</span>: <span class="string">&#x27;POST&#x27;</span>, <span class="attr">body</span>: data &#125;)</span><br><span class="line">    .<span class="title function_">then</span>(<span class="function"><span class="params">r</span> =&gt;</span> r.<span class="title function_">json</span>())</span><br><span class="line">    .<span class="title function_">then</span>(<span class="function"><span class="params">res</span> =&gt;</span> &#123;</span><br><span class="line">      <span class="variable language_">document</span>.<span class="title function_">getElementById</span>(<span class="string">&#x27;form-msg&#x27;</span>).<span class="property">textContent</span> = res.<span class="property">data</span>;</span><br><span class="line">      <span class="keyword">if</span> (res.<span class="property">success</span>) &#123;</span><br><span class="line">        <span class="variable language_">this</span>.<span class="title function_">reset</span>();</span><br><span class="line">        <span class="built_in">setTimeout</span>(<span class="function">() =&gt;</span> overlay.<span class="property">classList</span>.<span class="title function_">remove</span>(<span class="string">&#x27;show&#x27;</span>), <span class="number">1500</span>);</span><br><span class="line">      &#125;</span><br><span class="line">    &#125;);</span><br><span class="line">&#125;);</span><br></pre></td></tr></table></figure><p>最后是后端处理前端请求并为link_manager新增待审核列的代码，审核逻辑基于链接是否在前端隐藏， 关闭私密功能即可前端展示</p><p><img src="https://img.eucalyptus.cc/images/20250831/1.avif"></p><p><img src="https://img.eucalyptus.cc/images/20250831/2.avif"></p><figure class="highlight php"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">&lt;?php</span></span><br><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * 待审核友链提示</span></span><br><span class="line"><span class="comment"> * 包含：后台列表新增“待审核”列 + 红色气泡菜单提示 + Ajax 接收</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"></span><br><span class="line"><span class="comment">/* --------------------------------------------------</span></span><br><span class="line"><span class="comment"> * 1. 后台列表新增“待审核”列，纯文本，系统默认样式</span></span><br><span class="line"><span class="comment"> * -------------------------------------------------- */</span></span><br><span class="line"><span class="comment">/* 新增“待审核”列（键名 review） */</span></span><br><span class="line"><span class="title function_ invoke__">add_filter</span>( <span class="string">&#x27;manage_link-manager_columns&#x27;</span>, function ( <span class="variable">$cols</span> ) &#123;</span><br><span class="line">    <span class="variable">$cols</span>[<span class="string">&#x27;review&#x27;</span>] = <span class="string">&#x27;待审核&#x27;</span>;</span><br><span class="line">    <span class="keyword">return</span> <span class="variable">$cols</span>;</span><br><span class="line">&#125; );</span><br><span class="line"></span><br><span class="line"><span class="comment">/* 单元格输出红色气泡 */</span></span><br><span class="line"><span class="title function_ invoke__">add_action</span>( <span class="string">&#x27;manage_link_custom_column&#x27;</span>, function ( <span class="variable">$col</span>, <span class="variable">$link_id</span> ) &#123;</span><br><span class="line">    <span class="keyword">if</span> ( <span class="variable">$col</span> === <span class="string">&#x27;review&#x27;</span> ) &#123;</span><br><span class="line">        <span class="keyword">echo</span> <span class="title function_ invoke__">get_bookmark_field</span>( <span class="string">&#x27;link_visible&#x27;</span>, <span class="variable">$link_id</span> ) === <span class="string">&#x27;N&#x27;</span></span><br><span class="line">            ? <span class="string">&#x27;&lt;span style=&quot;display:inline-block;background:#e60026;color:#fff;font-size:11px;padding:2px 6px;border-radius:10px;margin-left:4px;&quot;&gt;待审&lt;/span&gt;&#x27;</span></span><br><span class="line">            : <span class="string">&#x27;&#x27;</span>;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;, <span class="number">10</span>, <span class="number">2</span> );</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="comment">/* --------------------------------------------------</span></span><br><span class="line"><span class="comment"> * 2. 后台“链接”菜单右上角红色数字气泡</span></span><br><span class="line"><span class="comment"> * -------------------------------------------------- */</span></span><br><span class="line"><span class="title function_ invoke__">add_action</span>( <span class="string">&#x27;admin_menu&#x27;</span>, function () &#123;</span><br><span class="line">    <span class="variable">$count</span> = <span class="number">0</span>;</span><br><span class="line">    <span class="keyword">foreach</span> ( <span class="title function_ invoke__">get_bookmarks</span>( [ <span class="string">&#x27;hide_invisible&#x27;</span> =&gt; <span class="number">0</span> ] ) <span class="keyword">as</span> <span class="variable">$link</span> ) &#123;</span><br><span class="line">        <span class="keyword">if</span> ( <span class="variable">$link</span>-&gt;link_visible === <span class="string">&#x27;N&#x27;</span> ) <span class="variable">$count</span>++;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">if</span> ( <span class="variable">$count</span> ) &#123;</span><br><span class="line">        <span class="keyword">global</span> <span class="variable">$menu</span>;</span><br><span class="line">        <span class="keyword">foreach</span> ( <span class="variable">$menu</span> <span class="keyword">as</span> &amp;<span class="variable">$item</span> ) &#123;</span><br><span class="line">            <span class="keyword">if</span> ( <span class="variable">$item</span>[<span class="number">2</span>] === <span class="string">&#x27;link-manager.php&#x27;</span> ) &#123;</span><br><span class="line">                <span class="variable">$item</span>[<span class="number">0</span>] .= <span class="string">&#x27; &lt;span class=&quot;awaiting-mod&quot;&gt;&lt;span class=&quot;pending-count&quot;&gt;&#x27;</span> . <span class="variable">$count</span> . <span class="string">&#x27;&lt;/span&gt;&lt;/span&gt;&#x27;</span>;</span><br><span class="line">                <span class="keyword">break</span>;</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125; );</span><br><span class="line"></span><br><span class="line"><span class="comment">/* --------------------------------------------------</span></span><br><span class="line"><span class="comment"> * 3. Ajax 接收端（无邮件）</span></span><br><span class="line"><span class="comment"> * -------------------------------------------------- */</span></span><br><span class="line"><span class="title function_ invoke__">add_action</span>( <span class="string">&#x27;wp_ajax_nopriv_submit_link_apply&#x27;</span>, <span class="string">&#x27;handle_link_apply&#x27;</span> );</span><br><span class="line"><span class="title function_ invoke__">add_action</span>( <span class="string">&#x27;wp_ajax_submit_link_apply&#x27;</span>,        <span class="string">&#x27;handle_link_apply&#x27;</span> );</span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">handle_link_apply</span>(<span class="params"></span>) </span>&#123;</span><br><span class="line">    <span class="variable">$name</span>  = <span class="title function_ invoke__">sanitize_text_field</span>( <span class="variable">$_POST</span>[<span class="string">&#x27;link_name&#x27;</span>] ?? <span class="string">&#x27;&#x27;</span> );</span><br><span class="line">    <span class="variable">$url</span>   = <span class="title function_ invoke__">esc_url_raw</span>        ( <span class="variable">$_POST</span>[<span class="string">&#x27;link_url&#x27;</span>]  ?? <span class="string">&#x27;&#x27;</span> );</span><br><span class="line">    <span class="variable">$email</span> = <span class="title function_ invoke__">sanitize_email</span>      ( <span class="variable">$_POST</span>[<span class="string">&#x27;link_owner_email&#x27;</span>] ?? <span class="string">&#x27;&#x27;</span> );</span><br><span class="line">    <span class="variable">$desc</span>  = <span class="title function_ invoke__">sanitize_text_field</span>( <span class="variable">$_POST</span>[<span class="string">&#x27;link_description&#x27;</span>] ?? <span class="string">&#x27;&#x27;</span> );</span><br><span class="line">    <span class="variable">$img</span>   = <span class="title function_ invoke__">esc_url_raw</span>        ( <span class="variable">$_POST</span>[<span class="string">&#x27;link_image&#x27;</span>]       ?? <span class="string">&#x27;&#x27;</span> );</span><br><span class="line"></span><br><span class="line">    <span class="keyword">if</span> ( <span class="keyword">empty</span>( <span class="variable">$name</span> ) || <span class="keyword">empty</span>( <span class="variable">$url</span> ) || ! <span class="title function_ invoke__">is_email</span>( <span class="variable">$email</span> ) ) &#123;</span><br><span class="line">        <span class="title function_ invoke__">wp_send_json_error</span>( <span class="string">&#x27;请完整填写必填项&#x27;</span> );</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="title function_ invoke__">wp_insert_link</span>( [</span><br><span class="line">        <span class="string">&#x27;link_name&#x27;</span>        =&gt; <span class="variable">$name</span>,</span><br><span class="line">        <span class="string">&#x27;link_url&#x27;</span>         =&gt; <span class="variable">$url</span>,</span><br><span class="line">        <span class="string">&#x27;link_description&#x27;</span> =&gt; <span class="variable">$desc</span>,</span><br><span class="line">        <span class="string">&#x27;link_image&#x27;</span>       =&gt; <span class="variable">$img</span>,</span><br><span class="line">        <span class="string">&#x27;link_owner_email&#x27;</span> =&gt; <span class="variable">$email</span>,</span><br><span class="line">        <span class="string">&#x27;link_visible&#x27;</span>     =&gt; <span class="string">&#x27;N&#x27;</span>,</span><br><span class="line">    ] );</span><br><span class="line"></span><br><span class="line">    <span class="title function_ invoke__">wp_send_json_success</span>( <span class="string">&#x27;已收到申请，审核后显示，感谢！&#x27;</span> );</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>上传代码至Meteor主题目录完成部署，最后在functions.php中启用link_manager并调用pending-links.php处理后台任务。</p><p>WordPress的functions.php文件中添加指定代码可实现特定功能，需注意代码正确性和备份以防出错。</p><figure class="highlight php"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">/* 开启后台链接 */</span></span><br><span class="line"><span class="title function_ invoke__">add_filter</span>( <span class="string">&#x27;pre_option_link_manager_enabled&#x27;</span>, <span class="string">&#x27;__return_true&#x27;</span> );</span><br><span class="line"></span><br><span class="line"> <span class="comment">/* 后台新增友链提示功能 */</span></span><br><span class="line"><span class="keyword">require_once</span> <span class="title function_ invoke__">get_theme_file_path</span>( <span class="string">&#x27;/self-innovate/pending-links.php&#x27;</span> );</span><br></pre></td></tr></table></figure><blockquote><p>展示地址：<a href="https://img.eucalyptus.cc/%e5%8f%8b%e9%93%be/">https://img.eucalyptus.cc/%e5%8f%8b%e9%93%be/</a></p></blockquote>]]></content:encoded>
      
      
      <category domain="https://blog.eucalyptus.cc/categories/WordPress/">WordPress</category>
      
      
      <category domain="https://blog.eucalyptus.cc/tags/Meteor/">Meteor</category>
      
      
      <comments>https://blog.eucalyptus.cc/2025/08/31/Meteor%E4%B8%BB%E9%A2%98%E5%8F%8B%E9%93%BE%E9%A1%B5%E9%9D%A2%E8%87%AA%E7%A0%94/#disqus_thread</comments>
      
    </item>
    
    <item>
      <title>WPCode添加代码启用后网站报错怎么处理</title>
      <link>https://blog.eucalyptus.cc/2025/05/31/WPcode%E6%8A%A5%E9%94%99%E5%A4%84%E7%90%86/</link>
      <guid>https://blog.eucalyptus.cc/2025/05/31/WPcode%E6%8A%A5%E9%94%99%E5%A4%84%E7%90%86/</guid>
      <pubDate>Sat, 31 May 2025 13:25:07 GMT</pubDate>
      
        
        
          
          
      <description>&lt;p&gt;有时候我们使用WPCode插件添加的代码可能会出现问题，或者与其他代码产生冲突，这就可能导致网站出现报错的情况。&lt;/p&gt;
&lt;!-- 这是一张图片，ocr 内容为： --&gt;
&lt;p&gt;&lt;img</description>
          
        
      
      
      
      <content:encoded><![CDATA[<p>有时候我们使用WPCode插件添加的代码可能会出现问题，或者与其他代码产生冲突，这就可能导致网站出现报错的情况。</p><!-- 这是一张图片，ocr 内容为： --><p><img src="https://img.eucalyptus.cc/images/20250531/1.avif"></p><p>这种时候通常可以采取两种不同的应对方式：</p><h2 id="一、修改文件"><a href="#一、修改文件" class="headerlink" title="一、修改文件"></a>一、修改文件</h2><p>如果在添加代码后网站出现报错，可以尝试通过登录后台服务器或者使用FTP删除或重命名 WPCode 插件的文件夹，这个文件夹的名称通常是 “insert-headers-and-footers”，操作前建议先备份相关数据，以确保网站的安全。</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">#重命名</span></span><br><span class="line"> <span class="built_in">mv</span> insert-headers-and-footers insert-headers-and-footers-mv</span><br></pre></td></tr></table></figure><!-- 这是一张图片，ocr 内容为： --><p><img src="https://img.eucalyptus.cc/images/20250531/2.avif"></p><p>其实不建议使用这种方法，如果你是把代码添加到了functions.php文件中，可以直接编辑并删除。但这种情况涉及的是插件，使用的是WPCode插件，一旦你重新启用它，之前添加的代码又会被载入并保持开启状态，这样一来，你网站上的报错问题还会再次出现。</p><h2 id="二、进入WPCode插件的安全模式"><a href="#二、进入WPCode插件的安全模式" class="headerlink" title="二、进入WPCode插件的安全模式"></a>二、进入WPCode插件的安全模式</h2><p>在网站域名后面加上 <code>/wp-admin/?wpcode-safe-mode=1</code> 即可进入安全模式，例如：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">https://mingliang.net.cn//wp-admin/?wpcode-safe-mode=1</span><br></pre></td></tr></table></figure><p>进入安全模式后可以看到网站将不再执行 WPCode 插件所添加的任何代码。</p><!-- 这是一张图片，ocr 内容为： --><p><img src="https://img.eucalyptus.cc/images/20250531/4.avif"></p><p>这时你可以在后台的WPCode插件中找到刚刚添加并导致错误的代码片段，直接删除或禁用，以恢复正常运行。</p><!-- 这是一张图片，ocr 内容为： --><p><img src="https://img.eucalyptus.cc/images/20250531/3.avif"></p>]]></content:encoded>
      
      
      <category domain="https://blog.eucalyptus.cc/categories/WordPress/">WordPress</category>
      
      
      <category domain="https://blog.eucalyptus.cc/tags/%E6%8F%92%E4%BB%B6/">插件</category>
      
      
      <comments>https://blog.eucalyptus.cc/2025/05/31/WPcode%E6%8A%A5%E9%94%99%E5%A4%84%E7%90%86/#disqus_thread</comments>
      
    </item>
    
    <item>
      <title>WordPress通过简码插入bilibili视频</title>
      <link>https://blog.eucalyptus.cc/2025/03/25/BILIBILI%E7%AE%80%E7%A0%81/</link>
      <guid>https://blog.eucalyptus.cc/2025/03/25/BILIBILI%E7%AE%80%E7%A0%81/</guid>
      <pubDate>Tue, 25 Mar 2025 15:24:09 GMT</pubDate>
      
        
        
          
          
      <description>&lt;h2 id=&quot;一、前言&quot;&gt;&lt;a href=&quot;#一、前言&quot; class=&quot;headerlink&quot;</description>
          
        
      
      
      
      <content:encoded><![CDATA[<h2 id="一、前言"><a href="#一、前言" class="headerlink" title="一、前言"></a>一、前言</h2><p>B站是国内非常受欢迎的视频分享平台，上面不仅内容丰富，而且很多视频制作精良、趣味十足。很多人，比如我，就喜欢将B站的视频通过 iframe 嵌入到自己的网页中，但这段代码又长又复杂，字符长度大约有230个，每次使用都很麻烦。为了让大家更方便地在 WordPress 网站中插入B站视频，我准备了一个简单实用的短代码教程，轻松几步就能搞定。</p><h2 id="二、代码"><a href="#二、代码" class="headerlink" title="二、代码"></a>二、代码</h2><p>将以下代码复制到网站主题的 functions.php 文件末尾即可完成添加。</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br></pre></td><td class="code"><pre><span class="line">/**</span><br><span class="line"> * 添加插入Bilibili视频的短代码</span><br><span class="line"> *</span><br><span class="line"> * @param array <span class="variable">$atts</span> 短代码属性数组</span><br><span class="line"> * @param string|null <span class="variable">$content</span> 短代码内容（未使用）</span><br><span class="line"> * @<span class="built_in">return</span> string 返回生成的HTML代码</span><br><span class="line"> */</span><br><span class="line"><span class="keyword">function</span> vbilibili_shortcode( <span class="variable">$atts</span>, <span class="variable">$content</span> = null ) &#123;</span><br><span class="line">    // 默认参数值</span><br><span class="line">    <span class="variable">$defaults</span> = array(</span><br><span class="line">        <span class="string">&#x27;av&#x27;</span> =&gt; <span class="string">&#x27;&#x27;</span>,    // Bilibili视频的AV号</span><br><span class="line">        <span class="string">&#x27;bv&#x27;</span> =&gt; <span class="string">&#x27;&#x27;</span>,    // Bilibili视频的BV号</span><br><span class="line">        <span class="string">&#x27;w&#x27;</span>  =&gt; <span class="string">&#x27;100%&#x27;</span>, // 默认宽度为100%</span><br><span class="line">    );</span><br><span class="line"></span><br><span class="line">    // 解析短代码中的参数</span><br><span class="line">    <span class="variable">$atts</span> = shortcode_atts( <span class="variable">$defaults</span>, <span class="variable">$atts</span> );</span><br><span class="line">    </span><br><span class="line">    // 如果参数 av 和 bv 都为空，则返回错误信息</span><br><span class="line">    <span class="keyword">if</span> ( empty( <span class="variable">$atts</span>[<span class="string">&#x27;av&#x27;</span>] ) &amp;&amp; empty( <span class="variable">$atts</span>[<span class="string">&#x27;bv&#x27;</span>] ) ) &#123;</span><br><span class="line">        <span class="built_in">return</span> <span class="string">&#x27;&lt;p&gt;av 和 bv 不可为空&lt;/p&gt;&#x27;</span>;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    // 如果参数 av 和 bv 同时存在，则返回错误信息</span><br><span class="line">    <span class="keyword">if</span> ( ! empty( <span class="variable">$atts</span>[<span class="string">&#x27;av&#x27;</span>] ) &amp;&amp; ! empty( <span class="variable">$atts</span>[<span class="string">&#x27;bv&#x27;</span>] ) ) &#123;</span><br><span class="line">        <span class="built_in">return</span> <span class="string">&#x27;&lt;p&gt;av 和 bv 不可同时存在&lt;/p&gt;&#x27;</span>;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    // 根据参数 av 或 bv 构建 iframe 的 URL</span><br><span class="line">    <span class="keyword">if</span> ( ! empty( <span class="variable">$atts</span>[<span class="string">&#x27;av&#x27;</span>] ) ) &#123;</span><br><span class="line">        <span class="variable">$iframe_url</span> = <span class="string">&#x27;https://player.bilibili.com/player.html?aid=&#x27;</span> . esc_attr( <span class="variable">$atts</span>[<span class="string">&#x27;av&#x27;</span>] ) . <span class="string">&#x27;&amp;high_quality=1&#x27;</span>;</span><br><span class="line">    &#125; elseif ( ! empty( <span class="variable">$atts</span>[<span class="string">&#x27;bv&#x27;</span>] ) ) &#123;</span><br><span class="line">        <span class="variable">$iframe_url</span> = <span class="string">&#x27;https://player.bilibili.com/player.html?bvid=&#x27;</span> . esc_attr( <span class="variable">$atts</span>[<span class="string">&#x27;bv&#x27;</span>] ) . <span class="string">&#x27;&amp;high_quality=1&#x27;</span>;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    // 构建 iframe 的 HTML 代码，并直接应用内联样式</span><br><span class="line">    <span class="variable">$iframe</span> = <span class="string">&#x27;&lt;div style=&quot;position:relative; padding-bottom:56.25%; height:0; overflow:hidden; margin:0 auto;&quot;&gt;&lt;iframe src=&quot;&#x27;</span> . <span class="variable">$iframe_url</span> . <span class="string">&#x27;&quot; style=&quot;position:absolute; top:0; left:0; width:100%; height:100%;&quot; frameborder=&quot;no&quot; scrolling=&quot;no&quot; allowfullscreen=&quot;allowfullscreen&quot;&gt;&lt;/iframe&gt;&lt;/div&gt;&#x27;</span>;</span><br><span class="line"></span><br><span class="line">    // 返回 iframe 的 HTML 代码</span><br><span class="line">    <span class="built_in">return</span> <span class="variable">$iframe</span>;</span><br><span class="line">&#125;</span><br><span class="line">add_shortcode( <span class="string">&#x27;vbilibili&#x27;</span>, <span class="string">&#x27;vbilibili_shortcode&#x27;</span> );</span><br></pre></td></tr></table></figure><p>将内容嵌入到<code>&lt;iframe&gt;</code>中，并通过设置相关属性来限制<code>&lt;iframe&gt;</code> 内的页面无法在新窗口或新标签页中打开，从而防止跳转到 B 站。可以通过在 <code>&lt;iframe&gt;</code> 标签中添加 sandbox 属性，结合 allow 属性精确控制权限，确保用户在当前页面内浏览，不被引导至其他网站。这种方式有助于提升用户体验的连续性，同时防止意外跳转带来的干扰。</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">sandbox=<span class="string">&quot;allow-top-navigation allow-same-origin allow-forms allow-scripts&quot;</span></span><br></pre></td></tr></table></figure><h2 id="三、WPCode"><a href="#三、WPCode" class="headerlink" title="三、WPCode"></a>三、WPCode</h2><p>如果你使用的是一个经常更新的主题，每次更新后都需要将代码添加到 functions.php 文件中，这显然十分麻烦。为了解决这个问题，你可以使用 WPCode 插件，在不修改主题文件的情况下，轻松实现自定义代码片段的添加和管理。</p><h3 id="3-1、WPCode介绍"><a href="#3-1、WPCode介绍" class="headerlink" title="3.1、WPCode介绍"></a>3.1、WPCode介绍</h3><p>WPCode 是一款流行的WordPress插件，它允许用户在不修改主题文件的情况下，向WordPress网站添加自定义代码片段。这款插件非常适合需要在网站中添加额外功能的站长，例如，添加Google分析或百度统计的代码，或者插入自定义CSS、JavaScript等。</p><p>WPCode插件还提供了一个代码片段库，您可以在这个库里面找到经过验证的PHP代码片段，例如：禁用REST API、禁用 XML-RPC、禁用注释、允许 SVG 文件上传、禁用 Gutenberg、添加经典编辑器等，而无需为每个请求安装单独的插件。使用这个插件，还可以控制代码的激活状态，如果需要在调试情况下先不适用某个功能，可以先不激活，而不是直接删除。</p><p>总之非常方便，如果您的网站也是WordPress网站，那么站长建议您可以安装下这个插件试一试。</p><h3 id="3-2、自定义代码到WPCode"><a href="#3-2、自定义代码到WPCode" class="headerlink" title="3.2、自定义代码到WPCode"></a>3.2、自定义代码到WPCode</h3><p><img src="https://img.eucalyptus.cc/images/20250325/1.avif"></p><p><img src="https://img.eucalyptus.cc/images/20250325/2.avif"></p><p><img src="https://img.eucalyptus.cc/images/20250325/2.avif"></p><h3 id="3-3、使用简码"><a href="#3-3、使用简码" class="headerlink" title="3.3、使用简码"></a>3.3、使用简码</h3><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">[vbilibili 参数名=<span class="string">&quot;参数值&quot;</span>][/vbilibili]</span><br></pre></td></tr></table></figure><p>参数：</p><table><thead><tr><th>参数名</th><th>可选值</th><th>默认值</th><th>解释</th><th>是否必须</th></tr></thead><tbody><tr><td>av</td><td>–</td><td>–</td><td>Bilibili 视频的 AV 号</td><td>是</td></tr><tr><td>bv</td><td>–</td><td>–</td><td>Bilibili 视频的 BV 号</td><td>是</td></tr></tbody></table><p>示例：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">[vbilibili bv=BV1SoZ5YVEJg][/vbilibili]</span><br></pre></td></tr></table></figure><div style="position: relative; width: 100%; height: 0; padding-bottom: 56.25%;">  <iframe     src="https://player.bilibili.com/player.html?bvid=BV1SoZ5YVEJg&page=1&high_quality=1&as_wide=1&allowfullscreen=true&autoplay=0"     style="position: absolute; width: 100%; height: 100%; left: 0; top: 0;"     frameborder="0"     scrolling="no"     allowfullscreen>  </iframe></div><p>注意：  </p><ul><li>Bilibili 视频的 AV 号或 BV 号是必须的，但只要输入 AV 号或 BV 号的其中一个即可。  </li><li>Bilibili 视频的 AV 号不要带上 av</li></ul>]]></content:encoded>
      
      
      <category domain="https://blog.eucalyptus.cc/categories/WordPress/">WordPress</category>
      
      
      <category domain="https://blog.eucalyptus.cc/tags/%E7%AE%80%E7%A0%81/">简码</category>
      
      
      <comments>https://blog.eucalyptus.cc/2025/03/25/BILIBILI%E7%AE%80%E7%A0%81/#disqus_thread</comments>
      
    </item>
    
    <item>
      <title>Wordpress登录页面插件</title>
      <link>https://blog.eucalyptus.cc/2025/01/03/%E7%99%BB%E5%BD%95%E9%A1%B5%E9%9D%A2%E6%8F%92%E4%BB%B6/</link>
      <guid>https://blog.eucalyptus.cc/2025/01/03/%E7%99%BB%E5%BD%95%E9%A1%B5%E9%9D%A2%E6%8F%92%E4%BB%B6/</guid>
      <pubDate>Fri, 03 Jan 2025 03:36:28 GMT</pubDate>
      
        
        
          
          
      <description>&lt;h2 id=&quot;一、前言&quot;&gt;&lt;a href=&quot;#一、前言&quot; class=&quot;headerlink&quot; title=&quot;一、前言&quot;&gt;&lt;/a&gt;一、前言&lt;/h2&gt;&lt;p&gt;&lt;code&gt;LoginPress | wp-login Custom Login Page</description>
          
        
      
      
      
      <content:encoded><![CDATA[<h2 id="一、前言"><a href="#一、前言" class="headerlink" title="一、前言"></a>一、前言</h2><p><code>LoginPress | wp-login Custom Login Page Customizer</code> 是一款WordPress插件，它提供了丰富的自定义字段来改变WordPress登录页面的布局。使用该插件，您可以完全修改登录页面的外观和感觉，包括登录错误消息、忘记密码错误消息、注册错误消息、忘记密码提示消息等。LoginPress 插件使用 Customizer API 构建，允许您实时预览登录页面的更改。该插件非常易于使用，不需要任何编码技能，只需为登录表单的每个元素设置选项，就可以在几秒钟内创建一个新的自定义登录页面。</p><p>LoginPress 提供了免费和专业版插件，专业版提供了更多的功能，如Google字体、Google reCaptcha、选择主题等。如果您希望为您的WordPress网站添加一个外观酷炫的登录页面，LoginPress 是一个必备的插件。</p><h2 id="二、LoginPress功能"><a href="#二、LoginPress功能" class="headerlink" title="二、LoginPress功能"></a>二、LoginPress功能</h2><p>Logo：可以更改登录Logo {image，width，height，padding，URL and Title}。<br>背景：您可以自定义背景图像或颜色。设置其位置、大小和可重复属性。<br>登录表单：即使你可以自定义登录表单{图像或颜色，宽度，高度，填充和边框}。自定义输入文本字段{宽度，边距，文本颜色，标签颜色和背景颜色}。<br>忘记形式：美丽的插件在这里你可以改变背景或颜色的忘记形式。<br>按钮：您可以设置按钮所见即所得的颜色。<br>错误消息：插件的主要功能.您可以更改错误日志消息，因为您希望在LoginPress→ Error Messages中向用户显示。<br>欢迎消息：与错误日志消息相同，您可以通过LoginPress→ Welcome Messages自定义欢迎消息。<br>表单页脚：登录按钮后，这里是表单页脚.您可以自定义页脚{文本、显示、文本装饰、文本颜色、文本悬停颜色和文本大小}。在此部分中，您还可以自定义页脚文本。</p><p>定制后使WordPress登录页面移动的友好<br>这个插件也是响应式的。这意味着，当您自定义登录页面，它会在移动的上看起来不错，即它是响应式的.</p><h2 id="三、演示"><a href="#三、演示" class="headerlink" title="三、演示"></a>三、演示</h2><p>使用reCAPTCHA和Google Fonts需要购买LoginPressPro版，而系统中其他所有工具和服务均可免费使用。无论是进行基础的表单验证、网站数据分析，还是应用多样化的字体样式，平台都为用户提供了全面支持，让在线项目的创建和管理更加高效便捷。</p><p>LoginPress默认主题</p><!-- 这是一张图片，ocr 内容为： --><p><img src="https://img.eucalyptus.cc/images/20250103/1.avif"></p><p>LoginPressPro主题</p><!-- 这是一张图片，ocr 内容为： --><p><img src="https://img.eucalyptus.cc/images/20250103/2.avif"></p><h2 id="四、LoginPressPro百度网盘下载"><a href="#四、LoginPressPro百度网盘下载" class="headerlink" title="四、LoginPressPro百度网盘下载"></a>四、LoginPressPro百度网盘下载</h2><p>为了确保功能完整使用，建议将LoginPress默认主题与其付费升级版本LoginPressPro一并安装，这样可以充分发挥其全部潜力并提升用户体验。</p><div class='anzhiyu-tag-link'><a class="tag-Link" target="_blank" rel="noopener external nofollow noreferrer" href="https://pan.baidu.com/s/1gm3aBamBy7o9_7bRZ_WcZg?pwd=xqqn">    <div class="tag-link-tips">引用站外地址</div>    <div class="tag-link-bottom">        <div class="tag-link-left" data-icon="https://pan.baidu.com/favicon.ico" style="width:60px;height:60px;min-width:60px;min-height:60px;position:relative;border-radius:var(--anzhiyu-border-radius);overflow:hidden;">          <img class="tag-link-favicon" src="https://pan.baidu.com/favicon.ico" onerror="this.style.display='none';this.nextElementSibling.style.display='flex';" style="width:60px;height:60px;object-fit:cover;border-radius:var(--anzhiyu-border-radius);position:absolute;top:0;left:0;" alt="favicon"/>          <i class="fa-solid fa-link" style="display:flex;align-items:center;justify-content:center;width:100%;height:100%;"></i>        </div>        <div class="tag-link-right">            <div class="tag-link-title">LoginPressPro 百度网盘下载</div>            <div class="tag-link-sitename">提取码：xqqn</div>        </div>        <i class="eucalyptus icon-angle-right"></i>    </div>    </a></div><blockquote><p>提取码：xqqn</p></blockquote>]]></content:encoded>
      
      
      <category domain="https://blog.eucalyptus.cc/categories/WordPress/">WordPress</category>
      
      
      <category domain="https://blog.eucalyptus.cc/tags/%E6%8F%92%E4%BB%B6/">插件</category>
      
      
      <comments>https://blog.eucalyptus.cc/2025/01/03/%E7%99%BB%E5%BD%95%E9%A1%B5%E9%9D%A2%E6%8F%92%E4%BB%B6/#disqus_thread</comments>
      
    </item>
    
    <item>
      <title>轻博客的粒子浪漫</title>
      <link>https://blog.eucalyptus.cc/2024/12/13/%E8%BD%BB%E5%8D%9A%E5%AE%A2%E7%9A%84%E7%B2%92%E5%AD%90%E6%B5%AA%E6%BC%AB/</link>
      <guid>https://blog.eucalyptus.cc/2024/12/13/%E8%BD%BB%E5%8D%9A%E5%AE%A2%E7%9A%84%E7%B2%92%E5%AD%90%E6%B5%AA%E6%BC%AB/</guid>
      <pubDate>Fri, 13 Dec 2024 02:50:50 GMT</pubDate>
      
        
        
          
          
      <description>&lt;h2 id=&quot;一、鼠标点击烟花特效&quot;&gt;&lt;a href=&quot;#一、鼠标点击烟花特效&quot; class=&quot;headerlink&quot; title=&quot;一、鼠标点击烟花特效&quot;&gt;&lt;/a&gt;一、鼠标点击烟花特效&lt;/h2&gt;&lt;div class=&#39;anzhiyu-tag-link&#39;&gt;&lt;a</description>
          
        
      
      
      
      <content:encoded><![CDATA[<h2 id="一、鼠标点击烟花特效"><a href="#一、鼠标点击烟花特效" class="headerlink" title="一、鼠标点击烟花特效"></a>一、鼠标点击烟花特效</h2><div class='anzhiyu-tag-link'><a class="tag-Link" target="_blank" rel="noopener external nofollow noreferrer" href="https://juejin.cn/post/7106018120036253710">    <div class="tag-link-tips">引用站外地址</div>    <div class="tag-link-bottom">        <div class="tag-link-left" data-icon="https://juejin.cn/favicon.ico" style="width:60px;height:60px;min-width:60px;min-height:60px;position:relative;border-radius:var(--anzhiyu-border-radius);overflow:hidden;">          <img class="tag-link-favicon" src="https://juejin.cn/favicon.ico" onerror="this.style.display='none';this.nextElementSibling.style.display='flex';" style="width:60px;height:60px;object-fit:cover;border-radius:var(--anzhiyu-border-radius);position:absolute;top:0;left:0;" alt="favicon"/>          <i class="fa-solid fa-link" style="display:flex;align-items:center;justify-content:center;width:100%;height:100%;"></i>        </div>        <div class="tag-link-right">            <div class="tag-link-title">掘金 - 鼠标点击烟花特效</div>            <div class="tag-link-sitename">粒子特效教程</div>        </div>        <i class="eucalyptus icon-angle-right"></i>    </div>    </a></div><figure class="highlight html"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br><span class="line">89</span><br><span class="line">90</span><br><span class="line">91</span><br><span class="line">92</span><br><span class="line">93</span><br><span class="line">94</span><br><span class="line">95</span><br><span class="line">96</span><br><span class="line">97</span><br><span class="line">98</span><br><span class="line">99</span><br><span class="line">100</span><br><span class="line">101</span><br><span class="line">102</span><br><span class="line">103</span><br><span class="line">104</span><br><span class="line">105</span><br><span class="line">106</span><br><span class="line">107</span><br><span class="line">108</span><br><span class="line">109</span><br><span class="line">110</span><br><span class="line">111</span><br><span class="line">112</span><br><span class="line">113</span><br><span class="line">114</span><br><span class="line">115</span><br><span class="line">116</span><br><span class="line">117</span><br><span class="line">118</span><br><span class="line">119</span><br><span class="line">120</span><br><span class="line">121</span><br><span class="line">122</span><br><span class="line">123</span><br><span class="line">124</span><br><span class="line">125</span><br><span class="line">126</span><br><span class="line">127</span><br><span class="line">128</span><br><span class="line">129</span><br><span class="line">130</span><br><span class="line">131</span><br><span class="line">132</span><br><span class="line">133</span><br><span class="line">134</span><br><span class="line">135</span><br><span class="line">136</span><br><span class="line">137</span><br><span class="line">138</span><br><span class="line">139</span><br><span class="line">140</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">html</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">body</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">script</span>&gt;</span><span class="language-javascript"></span></span><br><span class="line"><span class="language-javascript"><span class="keyword">function</span> <span class="title function_">clickEffect</span>(<span class="params"></span>) &#123;</span></span><br><span class="line"><span class="language-javascript">  <span class="keyword">let</span> balls = [];</span></span><br><span class="line"><span class="language-javascript">  <span class="keyword">let</span> longPressed = <span class="literal">false</span>;</span></span><br><span class="line"><span class="language-javascript">  <span class="keyword">let</span> longPress;</span></span><br><span class="line"><span class="language-javascript">  <span class="keyword">let</span> multiplier = <span class="number">0</span>;</span></span><br><span class="line"><span class="language-javascript">  <span class="keyword">let</span> width, height;</span></span><br><span class="line"><span class="language-javascript">  <span class="keyword">let</span> origin;</span></span><br><span class="line"><span class="language-javascript">  <span class="keyword">let</span> normal;</span></span><br><span class="line"><span class="language-javascript">  <span class="keyword">let</span> ctx;</span></span><br><span class="line"><span class="language-javascript">  <span class="keyword">const</span> colours = [<span class="string">&quot;#F73859&quot;</span>, <span class="string">&quot;#14FFEC&quot;</span>, <span class="string">&quot;#00E0FF&quot;</span>, <span class="string">&quot;#FF99FE&quot;</span>, <span class="string">&quot;#FAF15D&quot;</span>];</span></span><br><span class="line"><span class="language-javascript">  <span class="keyword">const</span> canvas = <span class="variable language_">document</span>.<span class="title function_">createElement</span>(<span class="string">&quot;canvas&quot;</span>);</span></span><br><span class="line"><span class="language-javascript">  <span class="variable language_">document</span>.<span class="property">body</span>.<span class="title function_">appendChild</span>(canvas);</span></span><br><span class="line"><span class="language-javascript">  canvas.<span class="title function_">setAttribute</span>(<span class="string">&quot;style&quot;</span>, <span class="string">&quot;width: 100%; height: 100%; top: 0; left: 0; z-index: 99999; position: fixed; pointer-events: none;&quot;</span>);</span></span><br><span class="line"><span class="language-javascript">  <span class="keyword">const</span> pointer = <span class="variable language_">document</span>.<span class="title function_">createElement</span>(<span class="string">&quot;span&quot;</span>);</span></span><br><span class="line"><span class="language-javascript">  pointer.<span class="property">classList</span>.<span class="title function_">add</span>(<span class="string">&quot;pointer&quot;</span>);</span></span><br><span class="line"><span class="language-javascript">  <span class="variable language_">document</span>.<span class="property">body</span>.<span class="title function_">appendChild</span>(pointer);</span></span><br><span class="line"><span class="language-javascript"> </span></span><br><span class="line"><span class="language-javascript">  <span class="keyword">if</span> (canvas.<span class="property">getContext</span> &amp;&amp; <span class="variable language_">window</span>.<span class="property">addEventListener</span>) &#123;</span></span><br><span class="line"><span class="language-javascript">    ctx = canvas.<span class="title function_">getContext</span>(<span class="string">&quot;2d&quot;</span>);</span></span><br><span class="line"><span class="language-javascript">    <span class="title function_">updateSize</span>();</span></span><br><span class="line"><span class="language-javascript">    <span class="variable language_">window</span>.<span class="title function_">addEventListener</span>(<span class="string">&#x27;resize&#x27;</span>, updateSize, <span class="literal">false</span>);</span></span><br><span class="line"><span class="language-javascript">    <span class="title function_">loop</span>();</span></span><br><span class="line"><span class="language-javascript">    <span class="variable language_">window</span>.<span class="title function_">addEventListener</span>(<span class="string">&quot;mousedown&quot;</span>, <span class="keyword">function</span>(<span class="params">e</span>) &#123;</span></span><br><span class="line"><span class="language-javascript">      <span class="title function_">pushBalls</span>(<span class="title function_">randBetween</span>(<span class="number">10</span>, <span class="number">20</span>), e.<span class="property">clientX</span>, e.<span class="property">clientY</span>);</span></span><br><span class="line"><span class="language-javascript">      <span class="variable language_">document</span>.<span class="property">body</span>.<span class="property">classList</span>.<span class="title function_">add</span>(<span class="string">&quot;is-pressed&quot;</span>);</span></span><br><span class="line"><span class="language-javascript">      longPress = <span class="built_in">setTimeout</span>(<span class="keyword">function</span>(<span class="params"></span>)&#123;</span></span><br><span class="line"><span class="language-javascript">        <span class="variable language_">document</span>.<span class="property">body</span>.<span class="property">classList</span>.<span class="title function_">add</span>(<span class="string">&quot;is-longpress&quot;</span>);</span></span><br><span class="line"><span class="language-javascript">        longPressed = <span class="literal">true</span>;</span></span><br><span class="line"><span class="language-javascript">      &#125;, <span class="number">500</span>);</span></span><br><span class="line"><span class="language-javascript">    &#125;, <span class="literal">false</span>);</span></span><br><span class="line"><span class="language-javascript">    <span class="variable language_">window</span>.<span class="title function_">addEventListener</span>(<span class="string">&quot;mouseup&quot;</span>, <span class="keyword">function</span>(<span class="params">e</span>) &#123;</span></span><br><span class="line"><span class="language-javascript">      <span class="built_in">clearInterval</span>(longPress);</span></span><br><span class="line"><span class="language-javascript">      <span class="keyword">if</span> (longPressed == <span class="literal">true</span>) &#123;</span></span><br><span class="line"><span class="language-javascript">        <span class="variable language_">document</span>.<span class="property">body</span>.<span class="property">classList</span>.<span class="title function_">remove</span>(<span class="string">&quot;is-longpress&quot;</span>);</span></span><br><span class="line"><span class="language-javascript">        <span class="title function_">pushBalls</span>(<span class="title function_">randBetween</span>(<span class="number">50</span> + <span class="title class_">Math</span>.<span class="title function_">ceil</span>(multiplier), <span class="number">100</span> + <span class="title class_">Math</span>.<span class="title function_">ceil</span>(multiplier)), e.<span class="property">clientX</span>, e.<span class="property">clientY</span>);</span></span><br><span class="line"><span class="language-javascript">        longPressed = <span class="literal">false</span>;</span></span><br><span class="line"><span class="language-javascript">      &#125;</span></span><br><span class="line"><span class="language-javascript">      <span class="variable language_">document</span>.<span class="property">body</span>.<span class="property">classList</span>.<span class="title function_">remove</span>(<span class="string">&quot;is-pressed&quot;</span>);</span></span><br><span class="line"><span class="language-javascript">    &#125;, <span class="literal">false</span>);</span></span><br><span class="line"><span class="language-javascript">    <span class="variable language_">window</span>.<span class="title function_">addEventListener</span>(<span class="string">&quot;mousemove&quot;</span>, <span class="keyword">function</span>(<span class="params">e</span>) &#123;</span></span><br><span class="line"><span class="language-javascript">      <span class="keyword">let</span> x = e.<span class="property">clientX</span>;</span></span><br><span class="line"><span class="language-javascript">      <span class="keyword">let</span> y = e.<span class="property">clientY</span>;</span></span><br><span class="line"><span class="language-javascript">      pointer.<span class="property">style</span>.<span class="property">top</span> = y + <span class="string">&quot;px&quot;</span>;</span></span><br><span class="line"><span class="language-javascript">      pointer.<span class="property">style</span>.<span class="property">left</span> = x + <span class="string">&quot;px&quot;</span>;</span></span><br><span class="line"><span class="language-javascript">    &#125;, <span class="literal">false</span>);</span></span><br><span class="line"><span class="language-javascript">  &#125; <span class="keyword">else</span> &#123;</span></span><br><span class="line"><span class="language-javascript">    <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">&quot;canvas or addEventListener is unsupported!&quot;</span>);</span></span><br><span class="line"><span class="language-javascript">  &#125;</span></span><br><span class="line"><span class="language-javascript"> </span></span><br><span class="line"><span class="language-javascript"> </span></span><br><span class="line"><span class="language-javascript">  <span class="keyword">function</span> <span class="title function_">updateSize</span>(<span class="params"></span>) &#123;</span></span><br><span class="line"><span class="language-javascript">    canvas.<span class="property">width</span> = <span class="variable language_">window</span>.<span class="property">innerWidth</span> * <span class="number">2</span>;</span></span><br><span class="line"><span class="language-javascript">    canvas.<span class="property">height</span> = <span class="variable language_">window</span>.<span class="property">innerHeight</span> * <span class="number">2</span>;</span></span><br><span class="line"><span class="language-javascript">    canvas.<span class="property">style</span>.<span class="property">width</span> = <span class="variable language_">window</span>.<span class="property">innerWidth</span> + <span class="string">&#x27;px&#x27;</span>;</span></span><br><span class="line"><span class="language-javascript">    canvas.<span class="property">style</span>.<span class="property">height</span> = <span class="variable language_">window</span>.<span class="property">innerHeight</span> + <span class="string">&#x27;px&#x27;</span>;</span></span><br><span class="line"><span class="language-javascript">    ctx.<span class="title function_">scale</span>(<span class="number">2</span>, <span class="number">2</span>);</span></span><br><span class="line"><span class="language-javascript">    width = (canvas.<span class="property">width</span> = <span class="variable language_">window</span>.<span class="property">innerWidth</span>);</span></span><br><span class="line"><span class="language-javascript">    height = (canvas.<span class="property">height</span> = <span class="variable language_">window</span>.<span class="property">innerHeight</span>);</span></span><br><span class="line"><span class="language-javascript">    origin = &#123;</span></span><br><span class="line"><span class="language-javascript">      <span class="attr">x</span>: width / <span class="number">2</span>,</span></span><br><span class="line"><span class="language-javascript">      <span class="attr">y</span>: height / <span class="number">2</span></span></span><br><span class="line"><span class="language-javascript">    &#125;;</span></span><br><span class="line"><span class="language-javascript">    normal = &#123;</span></span><br><span class="line"><span class="language-javascript">      <span class="attr">x</span>: width / <span class="number">2</span>,</span></span><br><span class="line"><span class="language-javascript">      <span class="attr">y</span>: height / <span class="number">2</span></span></span><br><span class="line"><span class="language-javascript">    &#125;;</span></span><br><span class="line"><span class="language-javascript">  &#125;</span></span><br><span class="line"><span class="language-javascript">  <span class="keyword">class</span> <span class="title class_">Ball</span> &#123;</span></span><br><span class="line"><span class="language-javascript">    <span class="title function_">constructor</span>(<span class="params">x = origin.x, y = origin.y</span>) &#123;</span></span><br><span class="line"><span class="language-javascript">      <span class="variable language_">this</span>.<span class="property">x</span> = x;</span></span><br><span class="line"><span class="language-javascript">      <span class="variable language_">this</span>.<span class="property">y</span> = y;</span></span><br><span class="line"><span class="language-javascript">      <span class="variable language_">this</span>.<span class="property">angle</span> = <span class="title class_">Math</span>.<span class="property">PI</span> * <span class="number">2</span> * <span class="title class_">Math</span>.<span class="title function_">random</span>();</span></span><br><span class="line"><span class="language-javascript">      <span class="keyword">if</span> (longPressed == <span class="literal">true</span>) &#123;</span></span><br><span class="line"><span class="language-javascript">        <span class="variable language_">this</span>.<span class="property">multiplier</span> = <span class="title function_">randBetween</span>(<span class="number">14</span> + multiplier, <span class="number">15</span> + multiplier);</span></span><br><span class="line"><span class="language-javascript">      &#125; <span class="keyword">else</span> &#123;</span></span><br><span class="line"><span class="language-javascript">        <span class="variable language_">this</span>.<span class="property">multiplier</span> = <span class="title function_">randBetween</span>(<span class="number">6</span>, <span class="number">12</span>);</span></span><br><span class="line"><span class="language-javascript">      &#125;</span></span><br><span class="line"><span class="language-javascript">      <span class="variable language_">this</span>.<span class="property">vx</span> = (<span class="variable language_">this</span>.<span class="property">multiplier</span> + <span class="title class_">Math</span>.<span class="title function_">random</span>() * <span class="number">0.5</span>) * <span class="title class_">Math</span>.<span class="title function_">cos</span>(<span class="variable language_">this</span>.<span class="property">angle</span>);</span></span><br><span class="line"><span class="language-javascript">      <span class="variable language_">this</span>.<span class="property">vy</span> = (<span class="variable language_">this</span>.<span class="property">multiplier</span> + <span class="title class_">Math</span>.<span class="title function_">random</span>() * <span class="number">0.5</span>) * <span class="title class_">Math</span>.<span class="title function_">sin</span>(<span class="variable language_">this</span>.<span class="property">angle</span>);</span></span><br><span class="line"><span class="language-javascript">      <span class="variable language_">this</span>.<span class="property">r</span> = <span class="title function_">randBetween</span>(<span class="number">8</span>, <span class="number">12</span>) + <span class="number">3</span> * <span class="title class_">Math</span>.<span class="title function_">random</span>();</span></span><br><span class="line"><span class="language-javascript">      <span class="variable language_">this</span>.<span class="property">color</span> = colours[<span class="title class_">Math</span>.<span class="title function_">floor</span>(<span class="title class_">Math</span>.<span class="title function_">random</span>() * colours.<span class="property">length</span>)];</span></span><br><span class="line"><span class="language-javascript">    &#125;</span></span><br><span class="line"><span class="language-javascript">    <span class="title function_">update</span>(<span class="params"></span>) &#123;</span></span><br><span class="line"><span class="language-javascript">      <span class="variable language_">this</span>.<span class="property">x</span> += <span class="variable language_">this</span>.<span class="property">vx</span> - normal.<span class="property">x</span>;</span></span><br><span class="line"><span class="language-javascript">      <span class="variable language_">this</span>.<span class="property">y</span> += <span class="variable language_">this</span>.<span class="property">vy</span> - normal.<span class="property">y</span>;</span></span><br><span class="line"><span class="language-javascript">      normal.<span class="property">x</span> = -<span class="number">2</span> / <span class="variable language_">window</span>.<span class="property">innerWidth</span> * <span class="title class_">Math</span>.<span class="title function_">sin</span>(<span class="variable language_">this</span>.<span class="property">angle</span>);</span></span><br><span class="line"><span class="language-javascript">      normal.<span class="property">y</span> = -<span class="number">2</span> / <span class="variable language_">window</span>.<span class="property">innerHeight</span> * <span class="title class_">Math</span>.<span class="title function_">cos</span>(<span class="variable language_">this</span>.<span class="property">angle</span>);</span></span><br><span class="line"><span class="language-javascript">      <span class="variable language_">this</span>.<span class="property">r</span> -= <span class="number">0.3</span>;</span></span><br><span class="line"><span class="language-javascript">      <span class="variable language_">this</span>.<span class="property">vx</span> *= <span class="number">0.9</span>;</span></span><br><span class="line"><span class="language-javascript">      <span class="variable language_">this</span>.<span class="property">vy</span> *= <span class="number">0.9</span>;</span></span><br><span class="line"><span class="language-javascript">    &#125;</span></span><br><span class="line"><span class="language-javascript">  &#125;</span></span><br><span class="line"><span class="language-javascript"> </span></span><br><span class="line"><span class="language-javascript">  <span class="keyword">function</span> <span class="title function_">pushBalls</span>(<span class="params">count = <span class="number">1</span>, x = origin.x, y = origin.y</span>) &#123;</span></span><br><span class="line"><span class="language-javascript">    <span class="keyword">for</span> (<span class="keyword">let</span> i = <span class="number">0</span>; i &lt; count; i++) &#123;</span></span><br><span class="line"><span class="language-javascript">      balls.<span class="title function_">push</span>(<span class="keyword">new</span> <span class="title class_">Ball</span>(x, y));</span></span><br><span class="line"><span class="language-javascript">    &#125;</span></span><br><span class="line"><span class="language-javascript">  &#125;</span></span><br><span class="line"><span class="language-javascript"> </span></span><br><span class="line"><span class="language-javascript">  <span class="keyword">function</span> <span class="title function_">randBetween</span>(<span class="params">min, max</span>) &#123;</span></span><br><span class="line"><span class="language-javascript">    <span class="keyword">return</span> <span class="title class_">Math</span>.<span class="title function_">floor</span>(<span class="title class_">Math</span>.<span class="title function_">random</span>() * max) + min;</span></span><br><span class="line"><span class="language-javascript">  &#125;</span></span><br><span class="line"><span class="language-javascript"> </span></span><br><span class="line"><span class="language-javascript">  <span class="keyword">function</span> <span class="title function_">loop</span>(<span class="params"></span>) &#123;</span></span><br><span class="line"><span class="language-javascript">    ctx.<span class="property">fillStyle</span> = <span class="string">&quot;rgba(255, 255, 255, 0)&quot;</span>;</span></span><br><span class="line"><span class="language-javascript">    ctx.<span class="title function_">clearRect</span>(<span class="number">0</span>, <span class="number">0</span>, canvas.<span class="property">width</span>, canvas.<span class="property">height</span>);</span></span><br><span class="line"><span class="language-javascript">    <span class="keyword">for</span> (<span class="keyword">let</span> i = <span class="number">0</span>; i &lt; balls.<span class="property">length</span>; i++) &#123;</span></span><br><span class="line"><span class="language-javascript">      <span class="keyword">let</span> b = balls[i];</span></span><br><span class="line"><span class="language-javascript">      <span class="keyword">if</span> (b.<span class="property">r</span> &lt; <span class="number">0</span>) <span class="keyword">continue</span>;</span></span><br><span class="line"><span class="language-javascript">      ctx.<span class="property">fillStyle</span> = b.<span class="property">color</span>;</span></span><br><span class="line"><span class="language-javascript">      ctx.<span class="title function_">beginPath</span>();</span></span><br><span class="line"><span class="language-javascript">      ctx.<span class="title function_">arc</span>(b.<span class="property">x</span>, b.<span class="property">y</span>, b.<span class="property">r</span>, <span class="number">0</span>, <span class="title class_">Math</span>.<span class="property">PI</span> * <span class="number">2</span>, <span class="literal">false</span>);</span></span><br><span class="line"><span class="language-javascript">      ctx.<span class="title function_">fill</span>();</span></span><br><span class="line"><span class="language-javascript">      b.<span class="title function_">update</span>();</span></span><br><span class="line"><span class="language-javascript">    &#125;</span></span><br><span class="line"><span class="language-javascript">    <span class="keyword">if</span> (longPressed == <span class="literal">true</span>) &#123;</span></span><br><span class="line"><span class="language-javascript">      multiplier += <span class="number">0.2</span>;</span></span><br><span class="line"><span class="language-javascript">    &#125; <span class="keyword">else</span> <span class="keyword">if</span> (!longPressed &amp;&amp; multiplier &gt;= <span class="number">0</span>) &#123;</span></span><br><span class="line"><span class="language-javascript">      multiplier -= <span class="number">0.4</span>;</span></span><br><span class="line"><span class="language-javascript">    &#125;</span></span><br><span class="line"><span class="language-javascript">    <span class="title function_">removeBall</span>();</span></span><br><span class="line"><span class="language-javascript">    <span class="title function_">requestAnimationFrame</span>(loop);</span></span><br><span class="line"><span class="language-javascript">  &#125;</span></span><br><span class="line"><span class="language-javascript"> </span></span><br><span class="line"><span class="language-javascript">  <span class="keyword">function</span> <span class="title function_">removeBall</span>(<span class="params"></span>) &#123;</span></span><br><span class="line"><span class="language-javascript">    <span class="keyword">for</span> (<span class="keyword">let</span> i = <span class="number">0</span>; i &lt; balls.<span class="property">length</span>; i++) &#123;</span></span><br><span class="line"><span class="language-javascript">      <span class="keyword">let</span> b = balls[i];</span></span><br><span class="line"><span class="language-javascript">      <span class="keyword">if</span> (b.<span class="property">x</span> + b.<span class="property">r</span> &lt; <span class="number">0</span> || b.<span class="property">x</span> - b.<span class="property">r</span> &gt; width || b.<span class="property">y</span> + b.<span class="property">r</span> &lt; <span class="number">0</span> || b.<span class="property">y</span> - b.<span class="property">r</span> &gt; height || b.<span class="property">r</span> &lt; <span class="number">0</span>) &#123;</span></span><br><span class="line"><span class="language-javascript">        balls.<span class="title function_">splice</span>(i, <span class="number">1</span>);</span></span><br><span class="line"><span class="language-javascript">      &#125;</span></span><br><span class="line"><span class="language-javascript">    &#125;</span></span><br><span class="line"><span class="language-javascript">  &#125;</span></span><br><span class="line"><span class="language-javascript">&#125;</span></span><br><span class="line"><span class="language-javascript"><span class="title function_">clickEffect</span>();<span class="comment">//调用特效函数</span></span></span><br><span class="line"><span class="language-javascript"></span><span class="tag">&lt;/<span class="name">script</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">body</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">html</span>&gt;</span></span><br></pre></td></tr></table></figure><h2 id="二、鼠标点击爱心特效"><a href="#二、鼠标点击爱心特效" class="headerlink" title="二、鼠标点击爱心特效"></a>二、鼠标点击爱心特效</h2><figure class="highlight html"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">&lt;!DOCTYPE <span class="keyword">html</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">html</span> <span class="attr">lang</span>=<span class="string">&quot;en&quot;</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">head</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">meta</span> <span class="attr">charset</span>=<span class="string">&quot;UTF-8&quot;</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">meta</span> <span class="attr">name</span>=<span class="string">&quot;viewport&quot;</span> <span class="attr">content</span>=<span class="string">&quot;width=device-width, initial-scale=1.0&quot;</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">title</span>&gt;</span>Document<span class="tag">&lt;/<span class="name">title</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">head</span>&gt;</span></span><br><span class="line"> </span><br><span class="line"><span class="tag">&lt;<span class="name">body</span>&gt;</span></span><br><span class="line">    <span class="comment">&lt;!-- 网页鼠标点击特效（爱心） --&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">script</span> <span class="attr">type</span>=<span class="string">&quot;text/javascript&quot;</span>&gt;</span><span class="language-javascript"></span></span><br><span class="line"><span class="language-javascript">         ! <span class="keyword">function</span> (<span class="params">e, t, a</span>) &#123;</span></span><br><span class="line"><span class="language-javascript">            <span class="keyword">function</span> <span class="title function_">r</span>(<span class="params"></span>) &#123;</span></span><br><span class="line"><span class="language-javascript">                <span class="keyword">for</span> (<span class="keyword">var</span> e = <span class="number">0</span>; e &lt; s.<span class="property">length</span>; e++) s[e].<span class="property">alpha</span> &lt;= <span class="number">0</span> ? (t.<span class="property">body</span>.<span class="title function_">removeChild</span>(s[e].<span class="property">el</span>), s.<span class="title function_">splice</span>(e, <span class="number">1</span>)) : (s[</span></span><br><span class="line"><span class="language-javascript">                        e].<span class="property">y</span>--, s[e].<span class="property">scale</span> += <span class="number">.004</span>, s[e].<span class="property">alpha</span> -= <span class="number">.013</span>, s[e].<span class="property">el</span>.<span class="property">style</span>.<span class="property">cssText</span> = <span class="string">&quot;left:&quot;</span> + s[e].<span class="property">x</span> +</span></span><br><span class="line"><span class="language-javascript">                    <span class="string">&quot;px;top:&quot;</span> + s[e].<span class="property">y</span> + <span class="string">&quot;px;opacity:&quot;</span> + s[e].<span class="property">alpha</span> + <span class="string">&quot;;transform:scale(&quot;</span> + s[e].<span class="property">scale</span> + <span class="string">&quot;,&quot;</span> + s[e]</span></span><br><span class="line"><span class="language-javascript">                    .<span class="property">scale</span> + <span class="string">&quot;) rotate(45deg);background:&quot;</span> + s[e].<span class="property">color</span> + <span class="string">&quot;;z-index:99999&quot;</span>);</span></span><br><span class="line"><span class="language-javascript">                <span class="title function_">requestAnimationFrame</span>(r)</span></span><br><span class="line"><span class="language-javascript">            &#125;</span></span><br><span class="line"><span class="language-javascript"> </span></span><br><span class="line"><span class="language-javascript">            <span class="keyword">function</span> <span class="title function_">n</span>(<span class="params"></span>) &#123;</span></span><br><span class="line"><span class="language-javascript">                <span class="keyword">var</span> t = <span class="string">&quot;function&quot;</span> == <span class="keyword">typeof</span> e.<span class="property">onclick</span> &amp;&amp; e.<span class="property">onclick</span>;</span></span><br><span class="line"><span class="language-javascript">                e.<span class="property">onclick</span> = <span class="keyword">function</span> (<span class="params">e</span>) &#123;</span></span><br><span class="line"><span class="language-javascript">                    t &amp;&amp; <span class="title function_">t</span>(), <span class="title function_">o</span>(e)</span></span><br><span class="line"><span class="language-javascript">                &#125;</span></span><br><span class="line"><span class="language-javascript">            &#125;</span></span><br><span class="line"><span class="language-javascript"> </span></span><br><span class="line"><span class="language-javascript">            <span class="keyword">function</span> <span class="title function_">o</span>(<span class="params">e</span>) &#123;</span></span><br><span class="line"><span class="language-javascript">                <span class="keyword">var</span> a = t.<span class="title function_">createElement</span>(<span class="string">&quot;div&quot;</span>);</span></span><br><span class="line"><span class="language-javascript">                a.<span class="property">className</span> = <span class="string">&quot;heart&quot;</span>, s.<span class="title function_">push</span>(&#123;</span></span><br><span class="line"><span class="language-javascript">                    <span class="attr">el</span>: a,</span></span><br><span class="line"><span class="language-javascript">                    <span class="attr">x</span>: e.<span class="property">clientX</span> - <span class="number">5</span>,</span></span><br><span class="line"><span class="language-javascript">                    <span class="attr">y</span>: e.<span class="property">clientY</span> - <span class="number">5</span>,</span></span><br><span class="line"><span class="language-javascript">                    <span class="attr">scale</span>: <span class="number">1</span>,</span></span><br><span class="line"><span class="language-javascript">                    <span class="attr">alpha</span>: <span class="number">1</span>,</span></span><br><span class="line"><span class="language-javascript">                    <span class="attr">color</span>: <span class="title function_">c</span>()</span></span><br><span class="line"><span class="language-javascript">                &#125;), t.<span class="property">body</span>.<span class="title function_">appendChild</span>(a)</span></span><br><span class="line"><span class="language-javascript">            &#125;</span></span><br><span class="line"><span class="language-javascript"> </span></span><br><span class="line"><span class="language-javascript">            <span class="keyword">function</span> <span class="title function_">i</span>(<span class="params">e</span>) &#123;</span></span><br><span class="line"><span class="language-javascript">                <span class="keyword">var</span> a = t.<span class="title function_">createElement</span>(<span class="string">&quot;style&quot;</span>);</span></span><br><span class="line"><span class="language-javascript">                a.<span class="property">type</span> = <span class="string">&quot;text/css&quot;</span>;</span></span><br><span class="line"><span class="language-javascript">                <span class="keyword">try</span> &#123;</span></span><br><span class="line"><span class="language-javascript">                    a.<span class="title function_">appendChild</span>(t.<span class="title function_">createTextNode</span>(e))</span></span><br><span class="line"><span class="language-javascript">                &#125; <span class="keyword">catch</span> (t) &#123;</span></span><br><span class="line"><span class="language-javascript">                    a.<span class="property">styleSheet</span>.<span class="property">cssText</span> = e</span></span><br><span class="line"><span class="language-javascript">                &#125;</span></span><br><span class="line"><span class="language-javascript">                t.<span class="title function_">getElementsByTagName</span>(<span class="string">&quot;head&quot;</span>)[<span class="number">0</span>].<span class="title function_">appendChild</span>(a)</span></span><br><span class="line"><span class="language-javascript">            &#125;</span></span><br><span class="line"><span class="language-javascript"> </span></span><br><span class="line"><span class="language-javascript">            <span class="keyword">function</span> <span class="title function_">c</span>(<span class="params"></span>) &#123;</span></span><br><span class="line"><span class="language-javascript">                <span class="keyword">return</span> <span class="string">&quot;rgb(&quot;</span> + ~~(<span class="number">255</span> * <span class="title class_">Math</span>.<span class="title function_">random</span>()) + <span class="string">&quot;,&quot;</span> + ~~(<span class="number">255</span> * <span class="title class_">Math</span>.<span class="title function_">random</span>()) + <span class="string">&quot;,&quot;</span> + ~~(<span class="number">255</span> * <span class="title class_">Math</span></span></span><br><span class="line"><span class="language-javascript">                    .<span class="title function_">random</span>()) + <span class="string">&quot;)&quot;</span></span></span><br><span class="line"><span class="language-javascript">            &#125;</span></span><br><span class="line"><span class="language-javascript">            <span class="keyword">var</span> s = [];</span></span><br><span class="line"><span class="language-javascript">            e.<span class="property">requestAnimationFrame</span> = e.<span class="property">requestAnimationFrame</span> || e.<span class="property">webkitRequestAnimationFrame</span> || e</span></span><br><span class="line"><span class="language-javascript">                .<span class="property">mozRequestAnimationFrame</span> || e.<span class="property">oRequestAnimationFrame</span> || e.<span class="property">msRequestAnimationFrame</span> || <span class="keyword">function</span> (<span class="params">e</span>) &#123;</span></span><br><span class="line"><span class="language-javascript">                    <span class="built_in">setTimeout</span>(e, <span class="number">1e3</span> / <span class="number">60</span>)</span></span><br><span class="line"><span class="language-javascript">                &#125;, <span class="title function_">i</span>(</span></span><br><span class="line"><span class="language-javascript">                    <span class="string">&quot;.heart&#123;width: 10px;height: 10px;position: fixed;background: #f00;transform: rotate(45deg);-webkit-transform: rotate(45deg);-moz-transform: rotate(45deg);&#125;.heart:after,.heart:before&#123;content: &#x27;&#x27;;width: inherit;height: inherit;background: inherit;border-radius: 50%;-webkit-border-radius: 50%;-moz-border-radius: 50%;position: fixed;&#125;.heart:after&#123;top: -5px;&#125;.heart:before&#123;left: -5px;&#125;&quot;</span></span></span><br><span class="line"><span class="language-javascript">                ), <span class="title function_">n</span>(), <span class="title function_">r</span>()</span></span><br><span class="line"><span class="language-javascript">        &#125;(<span class="variable language_">window</span>, <span class="variable language_">document</span>);</span></span><br><span class="line"><span class="language-javascript">    </span><span class="tag">&lt;/<span class="name">script</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">body</span>&gt;</span></span><br><span class="line"> </span><br><span class="line"><span class="tag">&lt;/<span class="name">html</span>&gt;</span></span><br></pre></td></tr></table></figure><h2 id="三、鼠标点击文字特效"><a href="#三、鼠标点击文字特效" class="headerlink" title="三、鼠标点击文字特效"></a>三、鼠标点击文字特效</h2><figure class="highlight html"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">&lt;!DOCTYPE <span class="keyword">html</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">html</span> <span class="attr">lang</span>=<span class="string">&quot;en&quot;</span>&gt;</span></span><br><span class="line"> </span><br><span class="line"><span class="tag">&lt;<span class="name">head</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">meta</span> <span class="attr">charset</span>=<span class="string">&quot;UTF-8&quot;</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">title</span>&gt;</span>Document<span class="tag">&lt;/<span class="name">title</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">head</span>&gt;</span></span><br><span class="line"> </span><br><span class="line"><span class="tag">&lt;<span class="name">body</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">script</span>&gt;</span><span class="language-javascript"></span></span><br><span class="line"><span class="language-javascript">        (<span class="keyword">function</span> (<span class="params"></span>) &#123;</span></span><br><span class="line"><span class="language-javascript">            <span class="keyword">var</span> a_idx = <span class="number">0</span>;</span></span><br><span class="line"><span class="language-javascript">            <span class="variable language_">window</span>.<span class="property">onclick</span> = <span class="keyword">function</span> (<span class="params">event</span>) &#123;</span></span><br><span class="line"><span class="language-javascript">                <span class="keyword">var</span> a = <span class="keyword">new</span> <span class="title class_">Array</span>(<span class="string">&quot;❤富强❤&quot;</span>, <span class="string">&quot;❤民主❤&quot;</span>, <span class="string">&quot;❤文明❤&quot;</span>, <span class="string">&quot;❤和谐❤&quot;</span>, <span class="string">&quot;❤自由❤&quot;</span>, <span class="string">&quot;❤平等❤&quot;</span>, <span class="string">&quot;❤公正❤&quot;</span>, <span class="string">&quot;❤法治❤&quot;</span>, <span class="string">&quot;❤爱国❤&quot;</span>,</span></span><br><span class="line"><span class="language-javascript">                    <span class="string">&quot;❤敬业❤&quot;</span>, <span class="string">&quot;❤诚信❤&quot;</span>, <span class="string">&quot;❤友善❤&quot;</span>);</span></span><br><span class="line"><span class="language-javascript"> </span></span><br><span class="line"><span class="language-javascript">                <span class="keyword">var</span> heart = <span class="variable language_">document</span>.<span class="title function_">createElement</span>(<span class="string">&quot;b&quot;</span>); <span class="comment">//创建b元素</span></span></span><br><span class="line"><span class="language-javascript">                heart.<span class="property">onselectstart</span> = <span class="keyword">new</span> <span class="title class_">Function</span>(<span class="string">&#x27;event.returnValue=false&#x27;</span>); <span class="comment">//防止拖动</span></span></span><br><span class="line"><span class="language-javascript"> </span></span><br><span class="line"><span class="language-javascript">                <span class="variable language_">document</span>.<span class="property">body</span>.<span class="title function_">appendChild</span>(heart).<span class="property">innerHTML</span> = a[a_idx]; <span class="comment">//将b元素添加到页面上</span></span></span><br><span class="line"><span class="language-javascript">                a_idx = (a_idx + <span class="number">1</span>) % a.<span class="property">length</span>;</span></span><br><span class="line"><span class="language-javascript">                heart.<span class="property">style</span>.<span class="property">cssText</span> = <span class="string">&quot;position: fixed;left:-100%;&quot;</span>; <span class="comment">//给p元素设置样式</span></span></span><br><span class="line"><span class="language-javascript"> </span></span><br><span class="line"><span class="language-javascript">                <span class="keyword">var</span> f = <span class="number">16</span>, <span class="comment">// 字体大小</span></span></span><br><span class="line"><span class="language-javascript">                    x = event.<span class="property">clientX</span> - f / <span class="number">2</span>, <span class="comment">// 横坐标</span></span></span><br><span class="line"><span class="language-javascript">                    y = event.<span class="property">clientY</span> - f, <span class="comment">// 纵坐标</span></span></span><br><span class="line"><span class="language-javascript">                    c = <span class="title function_">randomColor</span>(), <span class="comment">// 随机颜色</span></span></span><br><span class="line"><span class="language-javascript">                    a = <span class="number">1</span>, <span class="comment">// 透明度</span></span></span><br><span class="line"><span class="language-javascript">                    s = <span class="number">1.2</span>; <span class="comment">// 放大缩小</span></span></span><br><span class="line"><span class="language-javascript"> </span></span><br><span class="line"><span class="language-javascript">                <span class="keyword">var</span> timer = <span class="built_in">setInterval</span>(<span class="keyword">function</span> (<span class="params"></span>) &#123; <span class="comment">//添加定时器</span></span></span><br><span class="line"><span class="language-javascript">                    <span class="keyword">if</span> (a &lt;= <span class="number">0</span>) &#123;</span></span><br><span class="line"><span class="language-javascript">                        <span class="variable language_">document</span>.<span class="property">body</span>.<span class="title function_">removeChild</span>(heart);</span></span><br><span class="line"><span class="language-javascript">                        <span class="built_in">clearInterval</span>(timer);</span></span><br><span class="line"><span class="language-javascript">                    &#125; <span class="keyword">else</span> &#123;</span></span><br><span class="line"><span class="language-javascript">                        heart.<span class="property">style</span>.<span class="property">cssText</span> = <span class="string">&quot;font-size:16px;cursor: default;position: fixed;color:&quot;</span> +</span></span><br><span class="line"><span class="language-javascript">                            c + <span class="string">&quot;;left:&quot;</span> + x + <span class="string">&quot;px;top:&quot;</span> + y + <span class="string">&quot;px;opacity:&quot;</span> + a + <span class="string">&quot;;transform:scale(&quot;</span> +</span></span><br><span class="line"><span class="language-javascript">                            s + <span class="string">&quot;);&quot;</span>;</span></span><br><span class="line"><span class="language-javascript"> </span></span><br><span class="line"><span class="language-javascript">                        y--;</span></span><br><span class="line"><span class="language-javascript">                        a -= <span class="number">0.016</span>;</span></span><br><span class="line"><span class="language-javascript">                        s += <span class="number">0.002</span>;</span></span><br><span class="line"><span class="language-javascript">                    &#125;</span></span><br><span class="line"><span class="language-javascript">                &#125;, <span class="number">15</span>)</span></span><br><span class="line"><span class="language-javascript"> </span></span><br><span class="line"><span class="language-javascript">            &#125;</span></span><br><span class="line"><span class="language-javascript">            <span class="comment">// 随机颜色</span></span></span><br><span class="line"><span class="language-javascript">            <span class="keyword">function</span> <span class="title function_">randomColor</span>(<span class="params"></span>) &#123;</span></span><br><span class="line"><span class="language-javascript"> </span></span><br><span class="line"><span class="language-javascript">                <span class="keyword">return</span> <span class="string">&quot;rgb(&quot;</span> + (~~(<span class="title class_">Math</span>.<span class="title function_">random</span>() * <span class="number">255</span>)) + <span class="string">&quot;,&quot;</span> + (~~(<span class="title class_">Math</span>.<span class="title function_">random</span>() * <span class="number">255</span>)) + <span class="string">&quot;,&quot;</span> + (~~(<span class="title class_">Math</span></span></span><br><span class="line"><span class="language-javascript">                .<span class="title function_">random</span>() * <span class="number">255</span>)) + <span class="string">&quot;)&quot;</span>;</span></span><br><span class="line"><span class="language-javascript"> </span></span><br><span class="line"><span class="language-javascript">            &#125;</span></span><br><span class="line"><span class="language-javascript">        &#125;());</span></span><br><span class="line"><span class="language-javascript">    </span><span class="tag">&lt;/<span class="name">script</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">body</span>&gt;</span></span><br><span class="line"> </span><br><span class="line"><span class="tag">&lt;/<span class="name">html</span>&gt;</span></span><br></pre></td></tr></table></figure><h2 id="四、鼠标星星拖尾特效"><a href="#四、鼠标星星拖尾特效" class="headerlink" title="四、鼠标星星拖尾特效"></a>四、鼠标星星拖尾特效</h2><blockquote><p>鸣谢：<div class='anzhiyu-tag-link'><a class="tag-Link" target="_blank" rel="noopener external nofollow noreferrer" href="https://blog.csdn.net/weixin_44461275/article/details/131402453">    <div class="tag-link-tips">引用站外地址</div>    <div class="tag-link-bottom">        <div class="tag-link-left" data-icon="https://blog.csdn.net/favicon.ico" style="width:60px;height:60px;min-width:60px;min-height:60px;position:relative;border-radius:var(--anzhiyu-border-radius);overflow:hidden;">          <img class="tag-link-favicon" src="https://blog.csdn.net/favicon.ico" onerror="this.style.display='none';this.nextElementSibling.style.display='flex';" style="width:60px;height:60px;object-fit:cover;border-radius:var(--anzhiyu-border-radius);position:absolute;top:0;left:0;" alt="favicon"/>          <i class="fa-solid fa-link" style="display:flex;align-items:center;justify-content:center;width:100%;height:100%;"></i>        </div>        <div class="tag-link-right">            <div class="tag-link-title">鼠标星星拖尾特效</div>            <div class="tag-link-sitename">CSDN文章</div>        </div>        <i class="eucalyptus icon-angle-right"></i>    </div>    </a></div></p></blockquote><h3 id="JS"><a href="#JS" class="headerlink" title="JS"></a>JS</h3><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br><span class="line">89</span><br><span class="line">90</span><br><span class="line">91</span><br><span class="line">92</span><br><span class="line">93</span><br><span class="line">94</span><br><span class="line">95</span><br><span class="line">96</span><br><span class="line">97</span><br><span class="line">98</span><br><span class="line">99</span><br><span class="line">100</span><br><span class="line">101</span><br><span class="line">102</span><br><span class="line">103</span><br><span class="line">104</span><br><span class="line">105</span><br><span class="line">106</span><br><span class="line">107</span><br><span class="line">108</span><br><span class="line">109</span><br><span class="line">110</span><br><span class="line">111</span><br><span class="line">112</span><br><span class="line">113</span><br><span class="line">114</span><br><span class="line">115</span><br><span class="line">116</span><br><span class="line">117</span><br><span class="line">118</span><br><span class="line">119</span><br><span class="line">120</span><br><span class="line">121</span><br><span class="line">122</span><br><span class="line">123</span><br><span class="line">124</span><br><span class="line">125</span><br><span class="line">126</span><br><span class="line">127</span><br><span class="line">128</span><br><span class="line">129</span><br><span class="line">130</span><br><span class="line">131</span><br><span class="line">132</span><br><span class="line">133</span><br><span class="line">134</span><br><span class="line">135</span><br><span class="line">136</span><br><span class="line">137</span><br><span class="line">138</span><br><span class="line">139</span><br><span class="line">140</span><br><span class="line">141</span><br><span class="line">142</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">function</span> <span class="title function_">drawStar</span>(<span class="params"></span>) &#123;</span><br><span class="line">  <span class="comment">//定义数组，arr存放每个小星星的信息，colour为颜色数组，存几个好看的颜色</span></span><br><span class="line">  <span class="keyword">var</span> arr = [];</span><br><span class="line">  <span class="keyword">var</span> colours = [<span class="string">&quot;#ffff00&quot;</span>, <span class="string">&quot;#66ffff&quot;</span>, <span class="string">&quot;#3399ff&quot;</span>, <span class="string">&quot;#99ff00&quot;</span>, <span class="string">&quot;#ff9900&quot;</span>];</span><br><span class="line">  <span class="keyword">var</span> timeoutList = []; <span class="comment">// 计时器列表-用于后续清理计时器</span></span><br><span class="line"></span><br><span class="line">  <span class="comment">// 创建画布</span></span><br><span class="line">  <span class="keyword">const</span> canvas = <span class="variable language_">document</span>.<span class="title function_">createElement</span>(<span class="string">&#x27;canvas&#x27;</span>)</span><br><span class="line">  <span class="variable language_">document</span>.<span class="property">body</span>.<span class="title function_">appendChild</span>(canvas)</span><br><span class="line">  <span class="keyword">var</span> ctx = canvas.<span class="title function_">getContext</span>(<span class="string">&quot;2d&quot;</span>);</span><br><span class="line"></span><br><span class="line">  <span class="comment">// 让画布自适应窗口大小，这个复制即可</span></span><br><span class="line">  <span class="keyword">function</span> <span class="title function_">resizeCanvas</span>(<span class="params"></span>) &#123;</span><br><span class="line">    canvas.<span class="property">width</span> = <span class="variable language_">window</span>.<span class="property">innerWidth</span>;</span><br><span class="line">    canvas.<span class="property">height</span> = <span class="variable language_">window</span>.<span class="property">innerHeight</span>;</span><br><span class="line">  &#125;</span><br><span class="line">  <span class="title function_">resizeCanvas</span>();</span><br><span class="line"></span><br><span class="line">  <span class="comment">// 给画布css样式，固定定位，且阻止用户的鼠标事件</span></span><br><span class="line">  canvas.<span class="property">style</span>.<span class="property">cssText</span> = <span class="string">`</span></span><br><span class="line"><span class="string">    position: fixed;</span></span><br><span class="line"><span class="string">    z-index: 1000;</span></span><br><span class="line"><span class="string">    top: 0;</span></span><br><span class="line"><span class="string">    left: 0;</span></span><br><span class="line"><span class="string">    pointer-events: none;</span></span><br><span class="line"><span class="string">  `</span>;</span><br><span class="line"></span><br><span class="line">  <span class="comment">// 封装绘制一个五角星函数</span></span><br><span class="line">  <span class="comment">// x是圆心横坐标，y是圆心纵坐标，其实就是鼠标位置（x ，y）</span></span><br><span class="line">  <span class="comment">// r是里面小圆半径 ，l是大圆半径</span></span><br><span class="line">  <span class="comment">// rot是初始旋转角度</span></span><br><span class="line">  <span class="keyword">function</span> <span class="title function_">star</span>(<span class="params">x, y, r, l, rot</span>) &#123;</span><br><span class="line">    ctx.<span class="title function_">beginPath</span>();</span><br><span class="line">    <span class="comment">// 循环5次，因为5个点</span></span><br><span class="line">    <span class="keyword">for</span> (<span class="keyword">let</span> i = <span class="number">0</span>; i &lt; <span class="number">5</span>; i++) &#123;</span><br><span class="line">      <span class="comment">//先绘制小圆上一个点</span></span><br><span class="line">      ctx.<span class="title function_">lineTo</span>(</span><br><span class="line">        <span class="title class_">Math</span>.<span class="title function_">cos</span>(((<span class="number">18</span> + i * <span class="number">72</span> - rot) * <span class="title class_">Math</span>.<span class="property">PI</span>) / <span class="number">180</span>) * r + x,</span><br><span class="line">        -<span class="title class_">Math</span>.<span class="title function_">sin</span>(((<span class="number">18</span> + i * <span class="number">72</span> - rot) * <span class="title class_">Math</span>.<span class="property">PI</span>) / <span class="number">180</span>) * r + y</span><br><span class="line">      );</span><br><span class="line">      <span class="comment">//连线到大圆上一个点</span></span><br><span class="line">      ctx.<span class="title function_">lineTo</span>(</span><br><span class="line">        <span class="title class_">Math</span>.<span class="title function_">cos</span>(((<span class="number">54</span> + i * <span class="number">72</span> - rot) * <span class="title class_">Math</span>.<span class="property">PI</span>) / <span class="number">180</span>) * l + x,</span><br><span class="line">        -<span class="title class_">Math</span>.<span class="title function_">sin</span>(((<span class="number">54</span> + i * <span class="number">72</span> - rot) * <span class="title class_">Math</span>.<span class="property">PI</span>) / <span class="number">180</span>) * l + y</span><br><span class="line">      );</span><br><span class="line">    &#125;</span><br><span class="line">    ctx.<span class="title function_">closePath</span>();</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  <span class="comment">// 绘制一堆星星</span></span><br><span class="line">  <span class="keyword">function</span> <span class="title function_">draw</span>(<span class="params"></span>) &#123;</span><br><span class="line">    <span class="comment">//循环数组</span></span><br><span class="line">    <span class="keyword">for</span> (<span class="keyword">let</span> i = <span class="number">0</span>; i &lt; arr.<span class="property">length</span>; i++) &#123;</span><br><span class="line">      <span class="keyword">let</span> temp = arr[i];</span><br><span class="line">      <span class="comment">//调用绘制一个星星函数</span></span><br><span class="line">      <span class="title function_">star</span>(temp.<span class="property">x</span>, temp.<span class="property">y</span>, temp.<span class="property">r</span>, temp.<span class="property">r</span> * <span class="number">3</span>, temp.<span class="property">rot</span>);</span><br><span class="line">      <span class="comment">//星星颜色</span></span><br><span class="line">      ctx.<span class="property">fillStyle</span> = temp.<span class="property">color</span>;</span><br><span class="line">      <span class="comment">//星星边框颜色</span></span><br><span class="line">      ctx.<span class="property">strokeStyle</span> = temp.<span class="property">color</span>;</span><br><span class="line">      <span class="comment">//线宽度</span></span><br><span class="line">      ctx.<span class="property">lineWidth</span> = <span class="number">0.1</span>;</span><br><span class="line">      <span class="comment">//角有弧度</span></span><br><span class="line">      ctx.<span class="property">lineJoin</span> = <span class="string">&quot;round&quot;</span>;</span><br><span class="line">      <span class="comment">// 填充</span></span><br><span class="line">      ctx.<span class="title function_">fill</span>();</span><br><span class="line">      <span class="comment">// 绘制路径</span></span><br><span class="line">      ctx.<span class="title function_">stroke</span>();</span><br><span class="line">    &#125;</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  <span class="comment">//更新动画</span></span><br><span class="line">  <span class="keyword">function</span> <span class="title function_">update</span>(<span class="params"></span>) &#123;</span><br><span class="line">    <span class="comment">//循环数组</span></span><br><span class="line">    <span class="keyword">for</span> (<span class="keyword">let</span> i = <span class="number">0</span>; i &lt; arr.<span class="property">length</span>; i++) &#123;</span><br><span class="line">      <span class="comment">// x坐标+dx移动距离</span></span><br><span class="line">      arr[i].<span class="property">x</span> += arr[i].<span class="property">dx</span>;</span><br><span class="line">      <span class="comment">// y坐标+dy移动距离</span></span><br><span class="line">      arr[i].<span class="property">y</span> += arr[i].<span class="property">dy</span>;</span><br><span class="line">      <span class="comment">// 加上旋转角度</span></span><br><span class="line">      arr[i].<span class="property">rot</span> += arr[i].<span class="property">td</span>;</span><br><span class="line">      <span class="comment">// 半径慢慢减小</span></span><br><span class="line">      arr[i].<span class="property">r</span> -= <span class="number">0.015</span>;</span><br><span class="line">      <span class="comment">// 当半径小于0时</span></span><br><span class="line">      <span class="keyword">if</span> (arr[i].<span class="property">r</span> &lt; <span class="number">0</span>) &#123;</span><br><span class="line">        <span class="comment">//删除该星星</span></span><br><span class="line">        arr.<span class="title function_">splice</span>(i, <span class="number">1</span>);</span><br><span class="line">      &#125;</span><br><span class="line">    &#125;</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  <span class="comment">// 添加当前位置星星数据</span></span><br><span class="line">  <span class="keyword">function</span> <span class="title function_">addStarts</span>(<span class="params">e</span>) &#123;</span><br><span class="line">    <span class="comment">// 每移动触发一次事件给arr数组添加一个星星</span></span><br><span class="line">    arr.<span class="title function_">push</span>(&#123;</span><br><span class="line">      <span class="comment">// x是初始横坐标</span></span><br><span class="line">      <span class="attr">x</span>: e.<span class="property">clientX</span>,</span><br><span class="line">      <span class="comment">//y是初始纵坐标</span></span><br><span class="line">      <span class="attr">y</span>: e.<span class="property">clientY</span>,</span><br><span class="line">      <span class="comment">//r是星星里面那个小圆半径，哪来的小圆等会说</span></span><br><span class="line">      <span class="attr">r</span>: <span class="title class_">Math</span>.<span class="title function_">random</span>() * <span class="number">0.5</span> + <span class="number">1.5</span>,</span><br><span class="line">      <span class="comment">//运动时旋转的角度</span></span><br><span class="line">      <span class="attr">td</span>: <span class="title class_">Math</span>.<span class="title function_">random</span>() * <span class="number">4</span> - <span class="number">2</span>,</span><br><span class="line">      <span class="comment">// X轴移动距离</span></span><br><span class="line">      <span class="attr">dx</span>: <span class="title class_">Math</span>.<span class="title function_">random</span>() * <span class="number">2</span> - <span class="number">1</span>,</span><br><span class="line">      <span class="comment">// y轴移动距离</span></span><br><span class="line">      <span class="attr">dy</span>: <span class="title class_">Math</span>.<span class="title function_">random</span>() * <span class="number">1</span> + <span class="number">1</span>,</span><br><span class="line">      <span class="comment">// 初始的旋转角度</span></span><br><span class="line">      <span class="attr">rot</span>: <span class="title class_">Math</span>.<span class="title function_">random</span>() * <span class="number">90</span> + <span class="number">90</span>,</span><br><span class="line">      <span class="comment">// 颜色</span></span><br><span class="line">      <span class="attr">color</span>: colours[<span class="title class_">Math</span>.<span class="title function_">floor</span>(<span class="title class_">Math</span>.<span class="title function_">random</span>() * colours.<span class="property">length</span>)],</span><br><span class="line">    &#125;);</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  <span class="comment">// 监听屏幕变化事件</span></span><br><span class="line">  <span class="variable language_">window</span>.<span class="property">onresize</span> = resizeCanvas;</span><br><span class="line"></span><br><span class="line">  <span class="comment">// 监听鼠标移动事件</span></span><br><span class="line">  <span class="variable language_">window</span>.<span class="title function_">addEventListener</span>(<span class="string">&quot;mousemove&quot;</span>, <span class="function">(<span class="params">e</span>) =&gt;</span> &#123;</span><br><span class="line">    <span class="comment">// 添加星星数据</span></span><br><span class="line">    <span class="title function_">addStarts</span>(e);</span><br><span class="line"></span><br><span class="line">    <span class="comment">//设置100毫秒内效果</span></span><br><span class="line">    <span class="keyword">for</span> (<span class="keyword">let</span> index = <span class="number">0</span>; index &lt; <span class="number">200</span>; index++) &#123;</span><br><span class="line">      <span class="keyword">if</span> (index === <span class="number">0</span> &amp;&amp; timeoutList.<span class="property">length</span> &gt; <span class="number">0</span>) &#123;</span><br><span class="line">        <span class="keyword">for</span> (<span class="keyword">const</span> timeoutName <span class="keyword">of</span> timeoutList) &#123;</span><br><span class="line">          <span class="built_in">clearTimeout</span>(timeoutName)</span><br><span class="line">        &#125;</span><br><span class="line">      &#125;</span><br><span class="line">      timeoutList[index] = <span class="built_in">setTimeout</span>(<span class="function">() =&gt;</span> &#123;</span><br><span class="line">        <span class="comment">//清屏</span></span><br><span class="line">        ctx.<span class="title function_">clearRect</span>(<span class="number">0</span>, <span class="number">0</span>, canvas.<span class="property">width</span>, canvas.<span class="property">height</span>);</span><br><span class="line">        <span class="comment">//绘制</span></span><br><span class="line">        <span class="title function_">draw</span>();</span><br><span class="line">        <span class="comment">//更新</span></span><br><span class="line">        <span class="title function_">update</span>();</span><br><span class="line">      &#125;, index * <span class="number">20</span>);</span><br><span class="line">    &#125;</span><br><span class="line">  &#125;);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="title function_">drawStar</span>()</span><br></pre></td></tr></table></figure><h3 id="HTML"><a href="#HTML" class="headerlink" title="HTML"></a>HTML</h3><figure class="highlight html"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br><span class="line">89</span><br><span class="line">90</span><br><span class="line">91</span><br><span class="line">92</span><br><span class="line">93</span><br><span class="line">94</span><br><span class="line">95</span><br><span class="line">96</span><br><span class="line">97</span><br><span class="line">98</span><br><span class="line">99</span><br><span class="line">100</span><br><span class="line">101</span><br><span class="line">102</span><br><span class="line">103</span><br><span class="line">104</span><br><span class="line">105</span><br><span class="line">106</span><br><span class="line">107</span><br><span class="line">108</span><br><span class="line">109</span><br><span class="line">110</span><br><span class="line">111</span><br><span class="line">112</span><br><span class="line">113</span><br><span class="line">114</span><br><span class="line">115</span><br><span class="line">116</span><br><span class="line">117</span><br><span class="line">118</span><br><span class="line">119</span><br><span class="line">120</span><br><span class="line">121</span><br><span class="line">122</span><br><span class="line">123</span><br><span class="line">124</span><br><span class="line">125</span><br><span class="line">126</span><br><span class="line">127</span><br><span class="line">128</span><br><span class="line">129</span><br><span class="line">130</span><br><span class="line">131</span><br><span class="line">132</span><br><span class="line">133</span><br><span class="line">134</span><br><span class="line">135</span><br><span class="line">136</span><br><span class="line">137</span><br><span class="line">138</span><br><span class="line">139</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">&lt;!DOCTYPE <span class="keyword">html</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">html</span> <span class="attr">lang</span>=<span class="string">&quot;en&quot;</span>&gt;</span></span><br><span class="line"></span><br><span class="line"><span class="tag">&lt;<span class="name">head</span>&gt;</span></span><br><span class="line">    </span><br><span class="line"><span class="tag">&lt;/<span class="name">head</span>&gt;</span></span><br><span class="line"></span><br><span class="line"><span class="tag">&lt;<span class="name">body</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">span</span> <span class="attr">class</span>=<span class="string">&quot;js-cursor-container&quot;</span>&gt;</span><span class="tag">&lt;/<span class="name">span</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">script</span>&gt;</span><span class="language-javascript"></span></span><br><span class="line"><span class="language-javascript">        (<span class="keyword">function</span> <span class="title function_">fairyDustCursor</span>(<span class="params"></span>) &#123;</span></span><br><span class="line"><span class="language-javascript"></span></span><br><span class="line"><span class="language-javascript">            <span class="keyword">var</span> possibleColors = [<span class="string">&quot;#D61C59&quot;</span>, <span class="string">&quot;#E7D84B&quot;</span>, <span class="string">&quot;#1B8798&quot;</span>]</span></span><br><span class="line"><span class="language-javascript">            <span class="keyword">var</span> width = <span class="variable language_">window</span>.<span class="property">innerWidth</span>;</span></span><br><span class="line"><span class="language-javascript">            <span class="keyword">var</span> height = <span class="variable language_">window</span>.<span class="property">innerHeight</span>;</span></span><br><span class="line"><span class="language-javascript">            <span class="keyword">var</span> cursor = &#123; <span class="attr">x</span>: width / <span class="number">2</span>, <span class="attr">y</span>: width / <span class="number">2</span> &#125;;</span></span><br><span class="line"><span class="language-javascript">            <span class="keyword">var</span> particles = [];</span></span><br><span class="line"><span class="language-javascript"></span></span><br><span class="line"><span class="language-javascript">            <span class="keyword">function</span> <span class="title function_">init</span>(<span class="params"></span>) &#123;</span></span><br><span class="line"><span class="language-javascript">                <span class="title function_">bindEvents</span>();</span></span><br><span class="line"><span class="language-javascript">                <span class="title function_">loop</span>();</span></span><br><span class="line"><span class="language-javascript">            &#125;</span></span><br><span class="line"><span class="language-javascript"></span></span><br><span class="line"><span class="language-javascript">            <span class="comment">// Bind events that are needed</span></span></span><br><span class="line"><span class="language-javascript">            <span class="keyword">function</span> <span class="title function_">bindEvents</span>(<span class="params"></span>) &#123;</span></span><br><span class="line"><span class="language-javascript">                <span class="variable language_">document</span>.<span class="title function_">addEventListener</span>(<span class="string">&#x27;mousemove&#x27;</span>, onMouseMove);</span></span><br><span class="line"><span class="language-javascript">                <span class="variable language_">window</span>.<span class="title function_">addEventListener</span>(<span class="string">&#x27;resize&#x27;</span>, onWindowResize);</span></span><br><span class="line"><span class="language-javascript">            &#125;</span></span><br><span class="line"><span class="language-javascript"></span></span><br><span class="line"><span class="language-javascript">            <span class="keyword">function</span> <span class="title function_">onWindowResize</span>(<span class="params">e</span>) &#123;</span></span><br><span class="line"><span class="language-javascript">                width = <span class="variable language_">window</span>.<span class="property">innerWidth</span>;</span></span><br><span class="line"><span class="language-javascript">                height = <span class="variable language_">window</span>.<span class="property">innerHeight</span>;</span></span><br><span class="line"><span class="language-javascript">            &#125;</span></span><br><span class="line"><span class="language-javascript"></span></span><br><span class="line"><span class="language-javascript">            <span class="keyword">function</span> <span class="title function_">onMouseMove</span>(<span class="params">e</span>) &#123;</span></span><br><span class="line"><span class="language-javascript">                cursor.<span class="property">x</span> = e.<span class="property">clientX</span>;</span></span><br><span class="line"><span class="language-javascript">                cursor.<span class="property">y</span> = e.<span class="property">clientY</span>;</span></span><br><span class="line"><span class="language-javascript"></span></span><br><span class="line"><span class="language-javascript">                <span class="title function_">addParticle</span>(cursor.<span class="property">x</span>, cursor.<span class="property">y</span>, possibleColors[<span class="title class_">Math</span>.<span class="title function_">floor</span>(<span class="title class_">Math</span>.<span class="title function_">random</span>() * possibleColors.<span class="property">length</span>)]);</span></span><br><span class="line"><span class="language-javascript">            &#125;</span></span><br><span class="line"><span class="language-javascript"></span></span><br><span class="line"><span class="language-javascript">            <span class="keyword">function</span> <span class="title function_">addParticle</span>(<span class="params">x, y, color</span>) &#123;</span></span><br><span class="line"><span class="language-javascript">                <span class="keyword">var</span> particle = <span class="keyword">new</span> <span class="title class_">Particle</span>();</span></span><br><span class="line"><span class="language-javascript">                particle.<span class="title function_">init</span>(x, y, color);</span></span><br><span class="line"><span class="language-javascript">                particles.<span class="title function_">push</span>(particle);</span></span><br><span class="line"><span class="language-javascript">            &#125;</span></span><br><span class="line"><span class="language-javascript"></span></span><br><span class="line"><span class="language-javascript">            <span class="keyword">function</span> <span class="title function_">updateParticles</span>(<span class="params"></span>) &#123;</span></span><br><span class="line"><span class="language-javascript"></span></span><br><span class="line"><span class="language-javascript">                <span class="comment">// Updated</span></span></span><br><span class="line"><span class="language-javascript">                <span class="keyword">for</span> (<span class="keyword">var</span> i = <span class="number">0</span>; i &lt; particles.<span class="property">length</span>; i++) &#123;</span></span><br><span class="line"><span class="language-javascript">                    particles[i].<span class="title function_">update</span>();</span></span><br><span class="line"><span class="language-javascript">                &#125;</span></span><br><span class="line"><span class="language-javascript"></span></span><br><span class="line"><span class="language-javascript">                <span class="comment">// Remove dead particles</span></span></span><br><span class="line"><span class="language-javascript">                <span class="keyword">for</span> (<span class="keyword">var</span> i = particles.<span class="property">length</span> - <span class="number">1</span>; i &gt;= <span class="number">0</span>; i-) &#123;</span></span><br><span class="line"><span class="language-javascript">                    <span class="keyword">if</span> (particles[i].<span class="property">lifeSpan</span> &lt; <span class="number">0</span>) &#123;</span></span><br><span class="line"><span class="language-javascript">                        particles[i].<span class="title function_">die</span>();</span></span><br><span class="line"><span class="language-javascript">                        particles.<span class="title function_">splice</span>(i, <span class="number">1</span>);</span></span><br><span class="line"><span class="language-javascript">                    &#125;</span></span><br><span class="line"><span class="language-javascript">                &#125;</span></span><br><span class="line"><span class="language-javascript"></span></span><br><span class="line"><span class="language-javascript">            &#125;</span></span><br><span class="line"><span class="language-javascript"></span></span><br><span class="line"><span class="language-javascript">            <span class="keyword">function</span> <span class="title function_">loop</span>(<span class="params"></span>) &#123;</span></span><br><span class="line"><span class="language-javascript">                <span class="title function_">requestAnimationFrame</span>(loop);</span></span><br><span class="line"><span class="language-javascript">                <span class="title function_">updateParticles</span>();</span></span><br><span class="line"><span class="language-javascript">            &#125;</span></span><br><span class="line"><span class="language-javascript"></span></span><br><span class="line"><span class="language-javascript">            <span class="comment">/**</span></span></span><br><span class="line"><span class="comment"><span class="language-javascript">             * Particles</span></span></span><br><span class="line"><span class="comment"><span class="language-javascript">             */</span></span></span><br><span class="line"><span class="language-javascript"></span></span><br><span class="line"><span class="language-javascript">            <span class="keyword">function</span> <span class="title function_">Particle</span>(<span class="params"></span>) &#123;</span></span><br><span class="line"><span class="language-javascript"></span></span><br><span class="line"><span class="language-javascript">                <span class="variable language_">this</span>.<span class="property">character</span> = <span class="string">&quot;*&quot;</span>;</span></span><br><span class="line"><span class="language-javascript">                <span class="variable language_">this</span>.<span class="property">lifeSpan</span> = <span class="number">120</span>; <span class="comment">//ms</span></span></span><br><span class="line"><span class="language-javascript">                <span class="variable language_">this</span>.<span class="property">initialStyles</span> = &#123;</span></span><br><span class="line"><span class="language-javascript">                    <span class="string">&quot;position&quot;</span>: <span class="string">&quot;fixed&quot;</span>,</span></span><br><span class="line"><span class="language-javascript">                    <span class="string">&quot;display&quot;</span>: <span class="string">&quot;inline-block&quot;</span>,</span></span><br><span class="line"><span class="language-javascript">                    <span class="string">&quot;top&quot;</span>: <span class="string">&quot;0px&quot;</span>,</span></span><br><span class="line"><span class="language-javascript">                    <span class="string">&quot;left&quot;</span>: <span class="string">&quot;0px&quot;</span>,</span></span><br><span class="line"><span class="language-javascript">                    <span class="string">&quot;pointerEvents&quot;</span>: <span class="string">&quot;none&quot;</span>,</span></span><br><span class="line"><span class="language-javascript">                    <span class="string">&quot;touch-action&quot;</span>: <span class="string">&quot;none&quot;</span>,</span></span><br><span class="line"><span class="language-javascript">                    <span class="string">&quot;z-index&quot;</span>: <span class="string">&quot;10000000&quot;</span>,</span></span><br><span class="line"><span class="language-javascript">                    <span class="string">&quot;fontSize&quot;</span>: <span class="string">&quot;25px&quot;</span>,</span></span><br><span class="line"><span class="language-javascript">                    <span class="string">&quot;will-change&quot;</span>: <span class="string">&quot;transform&quot;</span></span></span><br><span class="line"><span class="language-javascript">                &#125;;</span></span><br><span class="line"><span class="language-javascript"></span></span><br><span class="line"><span class="language-javascript">                <span class="comment">// Init, and set properties</span></span></span><br><span class="line"><span class="language-javascript">                <span class="variable language_">this</span>.<span class="property">init</span> = <span class="keyword">function</span> (<span class="params">x, y, color</span>) &#123;</span></span><br><span class="line"><span class="language-javascript"></span></span><br><span class="line"><span class="language-javascript">                    <span class="variable language_">this</span>.<span class="property">velocity</span> = &#123;</span></span><br><span class="line"><span class="language-javascript">                        <span class="attr">x</span>: (<span class="title class_">Math</span>.<span class="title function_">random</span>() &lt; <span class="number">0.5</span> ? -<span class="number">1</span> : <span class="number">1</span>) * (<span class="title class_">Math</span>.<span class="title function_">random</span>() / <span class="number">2</span>),</span></span><br><span class="line"><span class="language-javascript">                        <span class="attr">y</span>: <span class="number">1</span></span></span><br><span class="line"><span class="language-javascript">                    &#125;;</span></span><br><span class="line"><span class="language-javascript"></span></span><br><span class="line"><span class="language-javascript">                    <span class="variable language_">this</span>.<span class="property">position</span> = &#123; <span class="attr">x</span>: x + <span class="number">10</span>, <span class="attr">y</span>: y + <span class="number">10</span> &#125;;</span></span><br><span class="line"><span class="language-javascript">                    <span class="variable language_">this</span>.<span class="property">initialStyles</span>.<span class="property">color</span> = color;</span></span><br><span class="line"><span class="language-javascript"></span></span><br><span class="line"><span class="language-javascript">                    <span class="variable language_">this</span>.<span class="property">element</span> = <span class="variable language_">document</span>.<span class="title function_">createElement</span>(<span class="string">&#x27;span&#x27;</span>);</span></span><br><span class="line"><span class="language-javascript">                    <span class="variable language_">this</span>.<span class="property">element</span>.<span class="property">innerHTML</span> = <span class="variable language_">this</span>.<span class="property">character</span>;</span></span><br><span class="line"><span class="language-javascript">                    <span class="title function_">applyProperties</span>(<span class="variable language_">this</span>.<span class="property">element</span>, <span class="variable language_">this</span>.<span class="property">initialStyles</span>);</span></span><br><span class="line"><span class="language-javascript">                    <span class="variable language_">this</span>.<span class="title function_">update</span>();</span></span><br><span class="line"><span class="language-javascript"></span></span><br><span class="line"><span class="language-javascript">                    <span class="variable language_">document</span>.<span class="title function_">querySelector</span>(<span class="string">&#x27;.js-cursor-container&#x27;</span>).<span class="title function_">appendChild</span>(<span class="variable language_">this</span>.<span class="property">element</span>);</span></span><br><span class="line"><span class="language-javascript">                &#125;;</span></span><br><span class="line"><span class="language-javascript"></span></span><br><span class="line"><span class="language-javascript">                <span class="variable language_">this</span>.<span class="property">update</span> = <span class="keyword">function</span> (<span class="params"></span>) &#123;</span></span><br><span class="line"><span class="language-javascript">                    <span class="variable language_">this</span>.<span class="property">position</span>.<span class="property">x</span> += <span class="variable language_">this</span>.<span class="property">velocity</span>.<span class="property">x</span>;</span></span><br><span class="line"><span class="language-javascript">                    <span class="variable language_">this</span>.<span class="property">position</span>.<span class="property">y</span> += <span class="variable language_">this</span>.<span class="property">velocity</span>.<span class="property">y</span>;</span></span><br><span class="line"><span class="language-javascript">                    <span class="variable language_">this</span>.<span class="property">lifeSpan</span>-;</span></span><br><span class="line"><span class="language-javascript"></span></span><br><span class="line"><span class="language-javascript">                    <span class="variable language_">this</span>.<span class="property">element</span>.<span class="property">style</span>.<span class="property">transform</span> = <span class="string">&quot;translate3d(&quot;</span> + <span class="variable language_">this</span>.<span class="property">position</span>.<span class="property">x</span> + <span class="string">&quot;px,&quot;</span> + <span class="variable language_">this</span>.<span class="property">position</span>.<span class="property">y</span> + <span class="string">&quot;px, 0) scale(&quot;</span> + (<span class="variable language_">this</span>.<span class="property">lifeSpan</span> / <span class="number">120</span>) + <span class="string">&quot;)&quot;</span>;</span></span><br><span class="line"><span class="language-javascript">                &#125;</span></span><br><span class="line"><span class="language-javascript"></span></span><br><span class="line"><span class="language-javascript">                <span class="variable language_">this</span>.<span class="property">die</span> = <span class="keyword">function</span> (<span class="params"></span>) &#123;</span></span><br><span class="line"><span class="language-javascript">                    <span class="variable language_">this</span>.<span class="property">element</span>.<span class="property">parentNode</span>.<span class="title function_">removeChild</span>(<span class="variable language_">this</span>.<span class="property">element</span>);</span></span><br><span class="line"><span class="language-javascript">                &#125;</span></span><br><span class="line"><span class="language-javascript"></span></span><br><span class="line"><span class="language-javascript">            &#125;</span></span><br><span class="line"><span class="language-javascript"></span></span><br><span class="line"><span class="language-javascript">            <span class="comment">/**</span></span></span><br><span class="line"><span class="comment"><span class="language-javascript">             * Utils</span></span></span><br><span class="line"><span class="comment"><span class="language-javascript">             */</span></span></span><br><span class="line"><span class="language-javascript"></span></span><br><span class="line"><span class="language-javascript">            <span class="comment">// Applies css `properties` to an element.</span></span></span><br><span class="line"><span class="language-javascript">            <span class="keyword">function</span> <span class="title function_">applyProperties</span>(<span class="params">target, properties</span>) &#123;</span></span><br><span class="line"><span class="language-javascript">                <span class="keyword">for</span> (<span class="keyword">var</span> key <span class="keyword">in</span> properties) &#123;</span></span><br><span class="line"><span class="language-javascript">                    target.<span class="property">style</span>[key] = properties[key];</span></span><br><span class="line"><span class="language-javascript">                &#125;</span></span><br><span class="line"><span class="language-javascript">            &#125;</span></span><br><span class="line"><span class="language-javascript"></span></span><br><span class="line"><span class="language-javascript">            <span class="keyword">if</span> (!(<span class="string">&#x27;ontouchstart&#x27;</span> <span class="keyword">in</span> <span class="variable language_">window</span> || navigator.<span class="property">msMaxTouchPoints</span>)) <span class="title function_">init</span>();</span></span><br><span class="line"><span class="language-javascript">        &#125;)();    </span></span><br><span class="line"><span class="language-javascript">    </span><span class="tag">&lt;/<span class="name">script</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">body</span>&gt;</span></span><br><span class="line"></span><br><span class="line"><span class="tag">&lt;/<span class="name">html</span>&gt;</span></span><br></pre></td></tr></table></figure>]]></content:encoded>
      
      
      <category domain="https://blog.eucalyptus.cc/categories/WordPress/">WordPress</category>
      
      
      <category domain="https://blog.eucalyptus.cc/tags/%E9%BC%A0%E6%A0%87%E7%89%B9%E6%95%88/">鼠标特效</category>
      
      
      <comments>https://blog.eucalyptus.cc/2024/12/13/%E8%BD%BB%E5%8D%9A%E5%AE%A2%E7%9A%84%E7%B2%92%E5%AD%90%E6%B5%AA%E6%BC%AB/#disqus_thread</comments>
      
    </item>
    
    <item>
      <title>WordPress纯代码实现圆角彩色背景标签云</title>
      <link>https://blog.eucalyptus.cc/2024/08/20/%E6%A0%87%E7%AD%BE%E4%BA%91/</link>
      <guid>https://blog.eucalyptus.cc/2024/08/20/%E6%A0%87%E7%AD%BE%E4%BA%91/</guid>
      <pubDate>Tue, 20 Aug 2024 14:17:35 GMT</pubDate>
      
        
        
          
          
      <description>&lt;h2 id=&quot;一、前言&quot;&gt;&lt;a href=&quot;#一、前言&quot; class=&quot;headerlink&quot; title=&quot;一、前言&quot;&gt;&lt;/a&gt;一、前言&lt;/h2&gt;&lt;p&gt;**标签云</description>
          
        
      
      
      
      <content:encoded><![CDATA[<h2 id="一、前言"><a href="#一、前言" class="headerlink" title="一、前言"></a>一、前言</h2><p>**标签云 **对我们的文章画龙点睛，如果让我们的标签云随机产生彩色效果，更是增加了不是个性化，我们现在抛弃插件，自己动手从网上学习DIY自己的彩色标签云。</p><h2 id="二、代码部署"><a href="#二、代码部署" class="headerlink" title="二、代码部署"></a>二、代码部署</h2><p>代码需放入主题目录下的tag-color-cloud.php文件中</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br></pre></td><td class="code"><pre><span class="line">&lt;?php</span><br><span class="line">/**</span><br><span class="line"> * 彩色圆角标签云</span><br><span class="line"> */</span><br><span class="line"><span class="keyword">if</span> ( ! defined( <span class="string">&#x27;ABSPATH&#x27;</span> ) ) &#123; <span class="built_in">exit</span>; &#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">function</span> colorCloud( <span class="variable">$text</span> ) &#123;</span><br><span class="line">    <span class="built_in">return</span> preg_replace_callback(</span><br><span class="line">        <span class="string">&#x27;|&lt;a (.+?)&gt;|i&#x27;</span>,</span><br><span class="line">        <span class="keyword">function</span> ( <span class="variable">$m</span> ) &#123;</span><br><span class="line">            <span class="variable">$colors</span> = array( <span class="string">&#x27;F99&#x27;</span>, <span class="string">&#x27;C9C&#x27;</span>, <span class="string">&#x27;F96&#x27;</span>, <span class="string">&#x27;6CC&#x27;</span>, <span class="string">&#x27;6C9&#x27;</span>, <span class="string">&#x27;37A7FF&#x27;</span>, <span class="string">&#x27;B0D686&#x27;</span>, <span class="string">&#x27;E6CC6E&#x27;</span> );</span><br><span class="line">            <span class="variable">$color</span>  = <span class="variable">$colors</span>[ array_rand( <span class="variable">$colors</span> ) ];</span><br><span class="line"></span><br><span class="line">            // 去掉原有的 style 属性（若有）</span><br><span class="line">            <span class="variable">$attrs</span> = preg_replace( <span class="string">&#x27;/style=([&quot;\&#x27;</span>])(.*?)\1/i<span class="string">&#x27;, &#x27;</span><span class="string">&#x27;, $m[1] );</span></span><br><span class="line"><span class="string"></span></span><br><span class="line"><span class="string">            // 追加新样式</span></span><br><span class="line"><span class="string">            $style  = &quot;display:inline-block;color:#fff;padding:1px 5px;margin:0 5px 5px 0;&quot;;</span></span><br><span class="line"><span class="string">            $style .= &quot;background-color:#&#123;$color&#125;;border-radius:3px;&quot;;</span></span><br><span class="line"><span class="string">            $style .= &quot;transition:background-color .4s linear;&quot;;</span></span><br><span class="line"><span class="string"></span></span><br><span class="line"><span class="string">            return &#x27;</span>&lt;a <span class="string">&#x27; . $attrs . &#x27;</span> style=<span class="string">&quot;&#x27; . <span class="variable">$style</span> . &#x27;&quot;</span>&gt;<span class="string">&#x27;;</span></span><br><span class="line"><span class="string">        &#125;,</span></span><br><span class="line"><span class="string">        $text</span></span><br><span class="line"><span class="string">    );</span></span><br><span class="line"><span class="string">&#125;</span></span><br><span class="line"><span class="string">add_filter( &#x27;</span>wp_tag_cloud<span class="string">&#x27;, &#x27;</span>colorCloud<span class="string">&#x27;, 10 );</span></span><br></pre></td></tr></table></figure><p>最后在functions.php中调用即可</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">/* 彩色圆角标签云 */</span><br><span class="line">require_once get_template_directory() . <span class="string">&#x27;/self-innovate/tag-color-cloud.php&#x27;</span>;</span><br></pre></td></tr></table></figure>]]></content:encoded>
      
      
      <category domain="https://blog.eucalyptus.cc/categories/WordPress/">WordPress</category>
      
      
      <category domain="https://blog.eucalyptus.cc/tags/tags/">tags</category>
      
      
      <comments>https://blog.eucalyptus.cc/2024/08/20/%E6%A0%87%E7%AD%BE%E4%BA%91/#disqus_thread</comments>
      
    </item>
    
    <item>
      <title>快速迁移网站</title>
      <link>https://blog.eucalyptus.cc/2024/07/22/%E5%BF%AB%E9%80%9F%E8%BF%81%E7%A7%BB%E7%BD%91%E7%AB%99/</link>
      <guid>https://blog.eucalyptus.cc/2024/07/22/%E5%BF%AB%E9%80%9F%E8%BF%81%E7%A7%BB%E7%BD%91%E7%AB%99/</guid>
      <pubDate>Mon, 22 Jul 2024 12:34:22 GMT</pubDate>
      
        
        
          
          
      <description>&lt;h2 id=&quot;一、前言&quot;&gt;&lt;a href=&quot;#一、前言&quot; class=&quot;headerlink&quot;</description>
          
        
      
      
      
      <content:encoded><![CDATA[<h2 id="一、前言"><a href="#一、前言" class="headerlink" title="一、前言"></a>一、前言</h2><p>从建站开始到现在也有一年了，也看着服务器马上就要到期了。现在有俩个选择可以解决这个问题，其一是在现有的服务器上选择续费，但是成本很高，由于笔者的网站在建站的这一年之内也没有什么收入，所以很难拿出这么一大笔的费用，那么只能选择其二了，那就是购买一台新的服务器，毕竟在有活动的时候够买，相对来说还是很便宜的，但是如果购买新的服务器的话，也会面临一定的问题比如如何将网站从旧的服务器迁移到新的服务器。</p><p>先总结一下迁移的思路吧：</p><ul><li>首先要给新的服务器安装上和旧服务器一样的宝塔环境，包括php版本、mysql版本，这样能避免网站数据出错。</li><li>网站数据一定要提前备份，防止出现其他问题可以方便恢复。</li><li>然后在发送数据服务器上安装一键迁移工具，填写接收数据服务器信息，包括秘钥，就可以开始自动迁移了。</li></ul><h2 id="二、安装迁移工具"><a href="#二、安装迁移工具" class="headerlink" title="二、安装迁移工具"></a>二、安装迁移工具</h2><p>直接在宝塔面板的软件商店搜索<code>一键迁移</code>，即可找到宝塔一键迁移API版本，点击安装即可。</p><p><img src="https://img.eucalyptus.cc/images/20240722/1.avif"></p><h2 id="三、配置API"><a href="#三、配置API" class="headerlink" title="三、配置API"></a>三、配置API</h2><p>接收数据服务器上，点击面板设置，开启API，获得接口秘钥，并将发送数据端IP加入接收数据服务器API的IP白名单，最后点击保存即可。</p><p><img src="https://img.eucalyptus.cc/images/20240722/2.avif"></p><h2 id="四、开启迁移"><a href="#四、开启迁移" class="headerlink" title="四、开启迁移"></a>四、开启迁移</h2><p>发送数据服务器上，找到我们刚刚下载的宝塔一键迁移API版本，填写面板地址和API秘钥。<br>注意：面板地址和API秘钥都是接收数据服务器、接收数据服务器、接收数据服务器上的，千万别填错了。<br>面板地址包含 协议 + 主机IP地址 + 面板端口<br>例如：<a href="http://192.168.0.1:8888/">http://192.168.0.1:8888</a></p><p><img src="https://img.eucalyptus.cc/images/20240722/3.avif"></p><p><img src="https://img.eucalyptus.cc/images/20240722/4.avif"></p><p><img src="https://img.eucalyptus.cc/images/20240722/5.avif"></p><p><img src="https://img.eucalyptus.cc/images/20240722/6.avif"></p><p><img src="https://img.eucalyptus.cc/images/20240722/7.avif"></p><h2 id="五、解析测试"><a href="#五、解析测试" class="headerlink" title="五、解析测试"></a>五、解析测试</h2><p>迁移完成后，网站文件和数据库就都已经到新的网站了，这个时候域名再重新解析到新的网站就可以正常访问了。</p>]]></content:encoded>
      
      
      <category domain="https://blog.eucalyptus.cc/categories/WordPress/">WordPress</category>
      
      
      <category domain="https://blog.eucalyptus.cc/tags/%E7%BD%91%E7%AB%99%E8%BF%81%E7%A7%BB/">网站迁移</category>
      
      
      <comments>https://blog.eucalyptus.cc/2024/07/22/%E5%BF%AB%E9%80%9F%E8%BF%81%E7%A7%BB%E7%BD%91%E7%AB%99/#disqus_thread</comments>
      
    </item>
    
    <item>
      <title>人机验证解决VAPTCHA</title>
      <link>https://blog.eucalyptus.cc/2023/12/14/%E4%BA%BA%E6%9C%BA%E9%AA%8C%E8%AF%81%E8%A7%A3%E5%86%B3VAPTCHA/</link>
      <guid>https://blog.eucalyptus.cc/2023/12/14/%E4%BA%BA%E6%9C%BA%E9%AA%8C%E8%AF%81%E8%A7%A3%E5%86%B3VAPTCHA/</guid>
      <pubDate>Thu, 14 Dec 2023 12:51:49 GMT</pubDate>
      
        
        
          
          
      <description>&lt;h2 id=&quot;一、引言&quot;&gt;&lt;a href=&quot;#一、引言&quot; class=&quot;headerlink&quot; title=&quot;一、引言&quot;&gt;&lt;/a&gt;一、引言&lt;/h2&gt;&lt;p&gt;CAPTCHA 是“Completely Automated Public Turing Test* to Tell</description>
          
        
      
      
      
      <content:encoded><![CDATA[<h2 id="一、引言"><a href="#一、引言" class="headerlink" title="一、引言"></a>一、引言</h2><p>CAPTCHA 是“Completely Automated Public Turing Test* to Tell Humans Apart（全自动区分计算机和人类的图灵测试）”的首字母缩写。它是指各种认证方法，这些方法利用一个对于人类来说很简单但对机器来说很难的挑战来测试用户，以验证用户是否为人类。 CAPTCHA 可防止诈骗者和垃圾邮件发送者使用机器人填写用于恶意目的的 Web 表单。</p><p>传统 CAPTCHA 要求用户阅读并正确重新输入无法通过光学字符识别 (OCR) 技术解释的失真文本。 最新的 CAPTCHA 技术使用 AI 驱动的行为和风险分析，根据活动模式（而不是单个离散任务）对人类用户进行认证。</p><p>许多网站都要求用户在登录到帐户档案、提交注册表、发表评论或执行黑客可能使用机器人执行的某些其他操作之前回答 CAPTCHA 问题。 回答这种问题，即表明用户是人类，并被允许在网站上继续执行活动。</p><p>注意事项：</p><ul><li>如果你使用的主题后台有CAPTCHA的API调用接口，您直接调用即可  </li><li>如果你使用的主题后台没有CAPTCHA的API调用接口，则您需要自己部署代码然后调用CAPTCHA的API接口实现人机验证</li></ul><h2 id="二、主题有自带"><a href="#二、主题有自带" class="headerlink" title="二、主题有自带"></a>二、主题有自带</h2><h3 id="2-1、注册"><a href="#2-1、注册" class="headerlink" title="2.1、注册"></a>2.1、注册</h3><p>CAPTCHA官网：<div class='anzhiyu-tag-link'><a class="tag-Link" target="_blank" rel="noopener external nofollow noreferrer" href="https://www.vaptcha.com/">    <div class="tag-link-tips">引用站外地址</div>    <div class="tag-link-bottom">        <div class="tag-link-left" data-icon="https://www.vaptcha.com/favicon.ico" style="width:60px;height:60px;min-width:60px;min-height:60px;position:relative;border-radius:var(--anzhiyu-border-radius);overflow:hidden;">          <img class="tag-link-favicon" src="https://www.vaptcha.com/favicon.ico" onerror="this.style.display='none';this.nextElementSibling.style.display='flex';" style="width:60px;height:60px;object-fit:cover;border-radius:var(--anzhiyu-border-radius);position:absolute;top:0;left:0;" alt="favicon"/>          <i class="fa-solid fa-link" style="display:flex;align-items:center;justify-content:center;width:100%;height:100%;"></i>        </div>        <div class="tag-link-right">            <div class="tag-link-title">VAPTCHA 官方网站</div>            <div class="tag-link-sitename">官网</div>        </div>        <i class="eucalyptus icon-angle-right"></i>    </div>    </a></div></p><h3 id="2-2、创建验证单元"><a href="#2-2、创建验证单元" class="headerlink" title="2.2、创建验证单元"></a>2.2、创建验证单元</h3><p>完成注册后，您将能够在管理界面中生成CAPTCHA的验证ID（VID）和密钥（KEY）。随后，只需在后台的主题设置中进行相应的配置即可启用CAPTCHA功能。</p><p><img src="https://img.eucalyptus.cc/images/20231214/1.avif"></p><p><img src="https://img.eucalyptus.cc/images/20231214/2.avif"></p><h3 id="2-3、后台设置"><a href="#2-3、后台设置" class="headerlink" title="2.3、后台设置"></a>2.3、后台设置</h3><p><img src="https://img.eucalyptus.cc/images/20231214/3.avif"></p><h2 id="三、主题没有自带"><a href="#三、主题没有自带" class="headerlink" title="三、主题没有自带"></a>三、主题没有自带</h2><h3 id="3-1、代码配置"><a href="#3-1、代码配置" class="headerlink" title="3.1、代码配置"></a>3.1、代码配置</h3><p>配置流程：</p><ul><li>创建验证单元，获取VID和Key 。点击创建。  </li><li>将<code>https://v-cn.vaptcha.com/v3.js</code>引入到你的页面。  </li><li>将 VAPTCHA 初始化到你需要的位置，具体 web 端部署请查看：Web 客户端部署。  </li><li>用户验证通过得到token，与表单数据一同提交到服务端。  </li><li>服务端得到token后，向 VAPTCHA 服务器验证token的有效性，验证通过说明此次请求有效，服务端验证流程请查看：服务端二次验证。</li></ul><p>本文不深入探讨具体的部署步骤，若需了解详细的部署流程，您请参阅 <div class='anzhiyu-tag-link'><a class="tag-Link" target="_blank" rel="noopener external nofollow noreferrer" href="https://www.vaptcha.com/">    <div class="tag-link-tips">引用站外地址</div>    <div class="tag-link-bottom">        <div class="tag-link-left" data-icon="https://www.vaptcha.com/favicon.ico" style="width:60px;height:60px;min-width:60px;min-height:60px;position:relative;border-radius:var(--anzhiyu-border-radius);overflow:hidden;">          <img class="tag-link-favicon" src="https://www.vaptcha.com/favicon.ico" onerror="this.style.display='none';this.nextElementSibling.style.display='flex';" style="width:60px;height:60px;object-fit:cover;border-radius:var(--anzhiyu-border-radius);position:absolute;top:0;left:0;" alt="favicon"/>          <i class="fa-solid fa-link" style="display:flex;align-items:center;justify-content:center;width:100%;height:100%;"></i>        </div>        <div class="tag-link-right">            <div class="tag-link-title">VAPTCHA 官方配置指南</div>            <div class="tag-link-sitename">官方文档</div>        </div>        <i class="eucalyptus icon-angle-right"></i>    </div>    </a></div>。</p><h3 id="3-2、插件"><a href="#3-2、插件" class="headerlink" title="3.2、插件"></a>3.2、插件</h3><div class='anzhiyu-tag-link'><a class="tag-Link" target="_blank" rel="noopener external nofollow noreferrer" href="https://pan.baidu.com/s/1sqwhLY4g8t0-0iQoPOHKJQ?pwd=kupa">    <div class="tag-link-tips">引用站外地址</div>    <div class="tag-link-bottom">        <div class="tag-link-left" data-icon="https://pan.baidu.com/favicon.ico" style="width:60px;height:60px;min-width:60px;min-height:60px;position:relative;border-radius:var(--anzhiyu-border-radius);overflow:hidden;">          <img class="tag-link-favicon" src="https://pan.baidu.com/favicon.ico" onerror="this.style.display='none';this.nextElementSibling.style.display='flex';" style="width:60px;height:60px;object-fit:cover;border-radius:var(--anzhiyu-border-radius);position:absolute;top:0;left:0;" alt="favicon"/>          <i class="fa-solid fa-link" style="display:flex;align-items:center;justify-content:center;width:100%;height:100%;"></i>        </div>        <div class="tag-link-right">            <div class="tag-link-title">VAPTCHA 插件百度网盘下载</div>            <div class="tag-link-sitename">提取码：kupa</div>        </div>        <i class="eucalyptus icon-angle-right"></i>    </div>    </a></div><p>上传安装插件之后即可在后台配置</p><p><img src="https://img.eucalyptus.cc/images/20231214/4.avif"></p>]]></content:encoded>
      
      
      <category domain="https://blog.eucalyptus.cc/categories/WordPress/">WordPress</category>
      
      
      <category domain="https://blog.eucalyptus.cc/tags/VAPTCHA/">VAPTCHA</category>
      
      
      <comments>https://blog.eucalyptus.cc/2023/12/14/%E4%BA%BA%E6%9C%BA%E9%AA%8C%E8%AF%81%E8%A7%A3%E5%86%B3VAPTCHA/#disqus_thread</comments>
      
    </item>
    
    <item>
      <title>网站提交搜索引擎</title>
      <link>https://blog.eucalyptus.cc/2023/05/08/%E7%BD%91%E7%AB%99%E6%8F%90%E4%BA%A4%E6%90%9C%E7%B4%A2%E5%BC%95%E6%93%8E/</link>
      <guid>https://blog.eucalyptus.cc/2023/05/08/%E7%BD%91%E7%AB%99%E6%8F%90%E4%BA%A4%E6%90%9C%E7%B4%A2%E5%BC%95%E6%93%8E/</guid>
      <pubDate>Mon, 08 May 2023 08:35:26 GMT</pubDate>
      
        
        
          
          
      <description>&lt;h2 id=&quot;一、前言&quot;&gt;&lt;a href=&quot;#一、前言&quot; class=&quot;headerlink&quot; title=&quot;一、前言&quot;&gt;&lt;/a&gt;一、前言&lt;/h2&gt;&lt;p&gt;将&lt;div class=&#39;anzhiyu-tag-link&#39;&gt;&lt;a class=&quot;tag-Link&quot;</description>
          
        
      
      
      
      <content:encoded><![CDATA[<h2 id="一、前言"><a href="#一、前言" class="headerlink" title="一、前言"></a>一、前言</h2><p>将<div class='anzhiyu-tag-link'><a class="tag-Link" target="_blank" rel="noopener external nofollow noreferrer" href="https://blog.eucalyptus.cc/">    <div class="tag-link-tips">引用站外地址</div>    <div class="tag-link-bottom">        <div class="tag-link-left" data-icon="https://blog.eucalyptus.cc/favicon.ico" style="width:60px;height:60px;min-width:60px;min-height:60px;position:relative;border-radius:var(--anzhiyu-border-radius);overflow:hidden;">          <img class="tag-link-favicon" src="https://blog.eucalyptus.cc/favicon.ico" onerror="this.style.display='none';this.nextElementSibling.style.display='flex';" style="width:60px;height:60px;object-fit:cover;border-radius:var(--anzhiyu-border-radius);position:absolute;top:0;left:0;" alt="favicon"/>          <i class="fa-solid fa-link" style="display:flex;align-items:center;justify-content:center;width:100%;height:100%;"></i>        </div>        <div class="tag-link-right">            <div class="tag-link-title">我的网站</div>            <div class="tag-link-sitename">个人网站</div>        </div>        <i class="eucalyptus icon-angle-right"></i>    </div>    </a></div>提交给搜索引擎是为了让搜索引擎更早地了解、索引和显示您的网站内容。以下是一些提交网站给搜索引擎的理由：</p><p>提高可见性：通过将您的网站提交给搜索引擎，可以提高您的网站在搜索结果中出现的机会。当用户搜索与您的网站相关的关键词时，搜索引擎将更容易找到并显示您的网站。<br>更快索引网页：搜索引擎通过网络爬虫（也称为<strong>蜘蛛</strong>）来发现新的网页内容。尽管搜索引擎蜘蛛会定期自动抓取和索引网页，但对于新建立的网站或刚发布的新内容，手动提交可以加快搜索引擎对您网站的索引速度。<br>更新通知：如果您的网站经常发布新的内容或更新现有内容，手动提交网站给搜索引擎可以让它们意识到您的网站有新的变化，以便更及时地更新索引。<br>修复索引问题：有时候，搜索引擎可能无法正确索引您的网站或内容，导致网站排名下降或无法出现在搜索结果中。在这种情况下，通过重新提交网站可以帮助搜索引擎重新审视和索引您的网站，以解决问题。</p><p>需要注意的是，尽管提交网站给搜索引擎可以加快索引速度和提高可见性，但并不保证搜索引擎会立即或完全索引您的网站。搜索引擎的索引算法是复杂的，排名和显示结果是基于多个因素综合考虑的。因此，除了提交网站给搜索引擎，还应该关注网站内容的质量、结构优化、外部链接等方面，以提高搜索引擎优化（<em><strong>SEO</strong></em>）效果。</p><h2 id="二、sitemap"><a href="#二、sitemap" class="headerlink" title="二、sitemap"></a>二、sitemap</h2><p>什么是网站地图？</p><p><strong>sitemap</strong>是一种视觉指南，它以图形化的形式展示网站的结构和内容布局。它帮助用户轻松导航，提升用户体验，并辅助搜索引擎优化。网站地图可以是规划、设计和维护网站时的关键工具，确保信息架构的清晰和易于访问。</p><h3 id="2-1、插件生成站点地图"><a href="#2-1、插件生成站点地图" class="headerlink" title="2.1、插件生成站点地图"></a>2.1、插件生成站点地图</h3><p>使用<strong>Yoast SEO</strong>插件可以自动生成<strong>sitemap</strong></p><p><img src="https://img.eucalyptus.cc/images/20230508/1.avif"></p><h3 id="2-2、工具生成网站地图"><a href="#2-2、工具生成网站地图" class="headerlink" title="2.2、工具生成网站地图"></a>2.2、工具生成网站地图</h3><p>工具链接：<div class='anzhiyu-tag-link'><a class="tag-Link" target="_blank" rel="noopener external nofollow noreferrer" href="https://www.xml-sitemaps.com/">    <div class="tag-link-tips">引用站外地址</div>    <div class="tag-link-bottom">        <div class="tag-link-left" data-icon="https://www.xml-sitemaps.com/favicon.ico" style="width:60px;height:60px;min-width:60px;min-height:60px;position:relative;border-radius:var(--anzhiyu-border-radius);overflow:hidden;">          <img class="tag-link-favicon" src="https://www.xml-sitemaps.com/favicon.ico" onerror="this.style.display='none';this.nextElementSibling.style.display='flex';" style="width:60px;height:60px;object-fit:cover;border-radius:var(--anzhiyu-border-radius);position:absolute;top:0;left:0;" alt="favicon"/>          <i class="fa-solid fa-link" style="display:flex;align-items:center;justify-content:center;width:100%;height:100%;"></i>        </div>        <div class="tag-link-right">            <div class="tag-link-title">XML Sitemaps Generator</div>            <div class="tag-link-sitename">在线工具</div>        </div>        <i class="eucalyptus icon-angle-right"></i>    </div>    </a></div></p><p><img src="https://img.eucalyptus.cc/images/20230508/2.avif"></p><p>将生成的网站 <strong>sitemap.xml</strong> 文件上传至网站根目录后，搜索引擎就能更高效地抓取和索引页面内容，有助于提升网站在搜索结果中的可见性。确保文件路径正确，一般应为类似<code>https://www.yourdomain.com/sitemap.xml</code>的访问方式。</p><p><img src="https://img.eucalyptus.cc/images/20230508/3.avif"></p><h2 id="三、Bing"><a href="#三、Bing" class="headerlink" title="三、Bing"></a>三、Bing</h2><p><strong>Bing</strong>搜索引擎提交入口：<div class='anzhiyu-tag-link'><a class="tag-Link" target="_blank" rel="noopener external nofollow noreferrer" href="https://www.bing.com/webmasters">    <div class="tag-link-tips">引用站外地址</div>    <div class="tag-link-bottom">        <div class="tag-link-left" data-icon="https://www.bing.com/favicon.ico" style="width:60px;height:60px;min-width:60px;min-height:60px;position:relative;border-radius:var(--anzhiyu-border-radius);overflow:hidden;">          <img class="tag-link-favicon" src="https://www.bing.com/favicon.ico" onerror="this.style.display='none';this.nextElementSibling.style.display='flex';" style="width:60px;height:60px;object-fit:cover;border-radius:var(--anzhiyu-border-radius);position:absolute;top:0;left:0;" alt="favicon"/>          <i class="fa-solid fa-link" style="display:flex;align-items:center;justify-content:center;width:100%;height:100%;"></i>        </div>        <div class="tag-link-right">            <div class="tag-link-title">Bing Webmaster Tools</div>            <div class="tag-link-sitename">Bing</div>        </div>        <i class="eucalyptus icon-angle-right"></i>    </div>    </a></div></p><p><strong>Bing</strong>网站管理后台提交站点<strong>sitemap</strong></p><p><img src="https://img.eucalyptus.cc/images/20230508/4.avif"></p><div class='anzhiyu-tag-link'><a class="tag-Link" target="_blank" rel="noopener external nofollow noreferrer" href="https://www.szlogic.net/learn/seo/site-submit/">    <div class="tag-link-tips">引用站外地址</div>    <div class="tag-link-bottom">        <div class="tag-link-left" data-icon="https://www.szlogic.net/favicon.ico" style="width:60px;height:60px;min-width:60px;min-height:60px;position:relative;border-radius:var(--anzhiyu-border-radius);overflow:hidden;">          <img class="tag-link-favicon" src="https://www.szlogic.net/favicon.ico" onerror="this.style.display='none';this.nextElementSibling.style.display='flex';" style="width:60px;height:60px;object-fit:cover;border-radius:var(--anzhiyu-border-radius);position:absolute;top:0;left:0;" alt="favicon"/>          <i class="fa-solid fa-link" style="display:flex;align-items:center;justify-content:center;width:100%;height:100%;"></i>        </div>        <div class="tag-link-right">            <div class="tag-link-title">SEO网站提交教程</div>            <div class="tag-link-sitename">SEO教程</div>        </div>        <i class="eucalyptus icon-angle-right"></i>    </div>    </a></div><h2 id="四、百度"><a href="#四、百度" class="headerlink" title="四、百度"></a>四、百度</h2><div class='anzhiyu-tag-link'><a class="tag-Link" target="_blank" rel="noopener external nofollow noreferrer" href="https://ziyuan.baidu.com/property/index">    <div class="tag-link-tips">引用站外地址</div>    <div class="tag-link-bottom">        <div class="tag-link-left" data-icon="https://ziyuan.baidu.com/favicon.ico" style="width:60px;height:60px;min-width:60px;min-height:60px;position:relative;border-radius:var(--anzhiyu-border-radius);overflow:hidden;">          <img class="tag-link-favicon" src="https://ziyuan.baidu.com/favicon.ico" onerror="this.style.display='none';this.nextElementSibling.style.display='flex';" style="width:60px;height:60px;object-fit:cover;border-radius:var(--anzhiyu-border-radius);position:absolute;top:0;left:0;" alt="favicon"/>          <i class="fa-solid fa-link" style="display:flex;align-items:center;justify-content:center;width:100%;height:100%;"></i>        </div>        <div class="tag-link-right">            <div class="tag-link-title">百度搜索资源平台</div>            <div class="tag-link-sitename">百度SEO</div>        </div>        <i class="eucalyptus icon-angle-right"></i>    </div>    </a></div><p>百度的平台公告已经发布了“关于回收网站提交配额的通知”该政策是因为需要打击灰产、低质量和未实名认证的网站，官方的完整说明：“由于近期黑产、作弊行为肆虐，大量抢占平台资源，严重影响用户体验，搜索资源平台针对非实名账户内站点、低质站点关停sitemap提交能力并调整API每日推送额度。请各站点做好日常维护和质量管理，资源平台将在节后陆续为合规站点恢复权益、调配额度。</p><h3 id="4-1、sitemap提交"><a href="#4-1、sitemap提交" class="headerlink" title="4.1、sitemap提交"></a>4.1、sitemap提交</h3><p><img src="https://img.eucalyptus.cc/images/20230508/5.avif"></p><h3 id="4-2、手动提交"><a href="#4-2、手动提交" class="headerlink" title="4.2、手动提交"></a>4.2、手动提交</h3><p><img src="https://img.eucalyptus.cc/images/20230508/6.avif"></p><h2 id="五、Google"><a href="#五、Google" class="headerlink" title="五、Google"></a>五、Google</h2><p><strong>Google</strong>搜索引擎提交入口：<div class='anzhiyu-tag-link'><a class="tag-Link" target="_blank" rel="noopener external nofollow noreferrer" href="https://search.google.com/search-console/about">    <div class="tag-link-tips">引用站外地址</div>    <div class="tag-link-bottom">        <div class="tag-link-left" data-icon="https://search.google.com/favicon.ico" style="width:60px;height:60px;min-width:60px;min-height:60px;position:relative;border-radius:var(--anzhiyu-border-radius);overflow:hidden;">          <img class="tag-link-favicon" src="https://search.google.com/favicon.ico" onerror="this.style.display='none';this.nextElementSibling.style.display='flex';" style="width:60px;height:60px;object-fit:cover;border-radius:var(--anzhiyu-border-radius);position:absolute;top:0;left:0;" alt="favicon"/>          <i class="fa-solid fa-link" style="display:flex;align-items:center;justify-content:center;width:100%;height:100%;"></i>        </div>        <div class="tag-link-right">            <div class="tag-link-title">Google Search Console</div>            <div class="tag-link-sitename">Google</div>        </div>        <i class="eucalyptus icon-angle-right"></i>    </div>    </a></div></p><p><img src="https://img.eucalyptus.cc/images/20230508/7.avif"></p><p>使用<strong>Yoast SEO</strong>插件生成并提交网站的<strong>sitemap</strong>后，有时候可能会遇到<strong>Google</strong>搜索控制台提示<strong>无法检索</strong>的情况，其实这时候不必过于担心，通常过一会儿系统会自动显示成功，或者在你提交第二个网站地图后，问题也会自动解决。</p><p>当然你可以直接使用上面通过在线工具生成的<strong>sitemap.xml</strong>地图，这样能够方便快捷地为网站整理出结构清晰的索引，有助于搜索引擎更好地抓取和识别网页内容。 </p>]]></content:encoded>
      
      
      <category domain="https://blog.eucalyptus.cc/categories/WordPress/">WordPress</category>
      
      
      <category domain="https://blog.eucalyptus.cc/tags/SEO/">SEO</category>
      
      
      <comments>https://blog.eucalyptus.cc/2023/05/08/%E7%BD%91%E7%AB%99%E6%8F%90%E4%BA%A4%E6%90%9C%E7%B4%A2%E5%BC%95%E6%93%8E/#disqus_thread</comments>
      
    </item>
    
  </channel>
</rss>
