<?xml version="1.0" encoding="UTF-8"?><?xml-stylesheet href="/rss-styles.xsl" type="text/xsl"?><rss version="2.0" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>SuuS</title><description>个人博客 - 工作生活记录、折腾备忘, 同时分享下日常使用的软件、 观看的影视、喜欢的好物等~~</description><link>https://yourdomain.com/</link><language>zh-CN</language><item><title>使用cf为静态站点添加访问统计</title><link>https://yourdomain.com/blog/202601171500/</link><guid isPermaLink="true">https://yourdomain.com/blog/202601171500/</guid><description>分类：tech | 标签：访问统计, cloudflare</description><pubDate>Sat, 17 Jan 2026 15:00:00 GMT</pubDate><content:encoded>&lt;p&gt;本文是基于&lt;a href=&quot;/blog/add-like-feature-to-static-site&quot;&gt;为静态站点添加点赞功能&lt;/a&gt;来实现站点的访问统计。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;✅ &lt;strong&gt;访问统计&lt;/strong&gt;：自动记录文章访问次数，防刷机制&lt;/li&gt;
&lt;li&gt;✅ &lt;strong&gt;完全免费&lt;/strong&gt;：使用 Cloudflare 免费额度&lt;/li&gt;
&lt;li&gt;✅ &lt;strong&gt;隐私保护&lt;/strong&gt;：使用哈希保护用户身份&lt;/li&gt;
&lt;li&gt;✅ &lt;strong&gt;易于部署&lt;/strong&gt;：无需数据库，几分钟完成部署&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;在点赞功能的基础上，我们可以轻松添加访问统计功能，无需创建新的 KV namespace。&lt;/p&gt;
&lt;h3&gt;更新 Worker 代码&lt;/h3&gt;
&lt;p&gt;将以下完整代码替换到你的 Worker 中（包含点赞 + 访问统计）：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;// Cloudflare Worker - 点赞 + 访问统计&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;addEventListener&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&apos;fetch&apos;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;event&lt;/span&gt;&lt;span&gt; =&amp;gt;&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  event.&lt;/span&gt;&lt;span&gt;respondWith&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;handleRequest&lt;/span&gt;&lt;span&gt;(event.request))&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;})&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;async&lt;/span&gt;&lt;span&gt; function&lt;/span&gt;&lt;span&gt; handleRequest&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;request&lt;/span&gt;&lt;span&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  const&lt;/span&gt;&lt;span&gt; url&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; new&lt;/span&gt;&lt;span&gt; URL&lt;/span&gt;&lt;span&gt;(request.url)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  const&lt;/span&gt;&lt;span&gt; path&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; url.pathname&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  // CORS 头&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  const&lt;/span&gt;&lt;span&gt; corsHeaders&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    &apos;Access-Control-Allow-Origin&apos;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&apos;*&apos;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    &apos;Access-Control-Allow-Methods&apos;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&apos;GET, POST, OPTIONS&apos;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    &apos;Access-Control-Allow-Headers&apos;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&apos;Content-Type&apos;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  // 处理 OPTIONS 请求&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  if&lt;/span&gt;&lt;span&gt; (request.method &lt;/span&gt;&lt;span&gt;===&lt;/span&gt;&lt;span&gt; &apos;OPTIONS&apos;&lt;/span&gt;&lt;span&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    return&lt;/span&gt;&lt;span&gt; new&lt;/span&gt;&lt;span&gt; Response&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;null&lt;/span&gt;&lt;span&gt;, { headers: corsHeaders })&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  // 路由处理&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  if&lt;/span&gt;&lt;span&gt; (path &lt;/span&gt;&lt;span&gt;===&lt;/span&gt;&lt;span&gt; &apos;/like&apos;&lt;/span&gt;&lt;span&gt; ||&lt;/span&gt;&lt;span&gt; path &lt;/span&gt;&lt;span&gt;===&lt;/span&gt;&lt;span&gt; &apos;/upvote&apos;&lt;/span&gt;&lt;span&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    return&lt;/span&gt;&lt;span&gt; handleLike&lt;/span&gt;&lt;span&gt;(request, corsHeaders)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  } &lt;/span&gt;&lt;span&gt;else&lt;/span&gt;&lt;span&gt; if&lt;/span&gt;&lt;span&gt; (path &lt;/span&gt;&lt;span&gt;===&lt;/span&gt;&lt;span&gt; &apos;/count&apos;&lt;/span&gt;&lt;span&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    return&lt;/span&gt;&lt;span&gt; handleGetCount&lt;/span&gt;&lt;span&gt;(request, corsHeaders)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  } &lt;/span&gt;&lt;span&gt;else&lt;/span&gt;&lt;span&gt; if&lt;/span&gt;&lt;span&gt; (path &lt;/span&gt;&lt;span&gt;===&lt;/span&gt;&lt;span&gt; &apos;/view&apos;&lt;/span&gt;&lt;span&gt; ||&lt;/span&gt;&lt;span&gt; path &lt;/span&gt;&lt;span&gt;===&lt;/span&gt;&lt;span&gt; &apos;/pageview&apos;&lt;/span&gt;&lt;span&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    return&lt;/span&gt;&lt;span&gt; handlePageView&lt;/span&gt;&lt;span&gt;(request, corsHeaders)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  } &lt;/span&gt;&lt;span&gt;else&lt;/span&gt;&lt;span&gt; if&lt;/span&gt;&lt;span&gt; (path &lt;/span&gt;&lt;span&gt;===&lt;/span&gt;&lt;span&gt; &apos;/stats&apos;&lt;/span&gt;&lt;span&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    return&lt;/span&gt;&lt;span&gt; handleGetStats&lt;/span&gt;&lt;span&gt;(request, corsHeaders)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  } &lt;/span&gt;&lt;span&gt;else&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    return&lt;/span&gt;&lt;span&gt; new&lt;/span&gt;&lt;span&gt; Response&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;JSON&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;stringify&lt;/span&gt;&lt;span&gt;({&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      code: &lt;/span&gt;&lt;span&gt;0&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      message: &lt;/span&gt;&lt;span&gt;&apos;API is running&apos;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      endpoints: {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        like: &lt;/span&gt;&lt;span&gt;&apos;POST /like - 点赞/取消点赞&apos;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        count: &lt;/span&gt;&lt;span&gt;&apos;GET /count?post=xxx - 获取点赞数&apos;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        view: &lt;/span&gt;&lt;span&gt;&apos;POST /view - 记录访问&apos;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        stats: &lt;/span&gt;&lt;span&gt;&apos;GET /stats?post=xxx - 获取统计数据（点赞+访问）&apos;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    }), {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      headers: { &lt;/span&gt;&lt;span&gt;...&lt;/span&gt;&lt;span&gt;corsHeaders, &lt;/span&gt;&lt;span&gt;&apos;Content-Type&apos;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&apos;application/json&apos;&lt;/span&gt;&lt;span&gt; }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    })&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;// 处理点赞&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;async&lt;/span&gt;&lt;span&gt; function&lt;/span&gt;&lt;span&gt; handleLike&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;request&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;corsHeaders&lt;/span&gt;&lt;span&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  if&lt;/span&gt;&lt;span&gt; (request.method &lt;/span&gt;&lt;span&gt;!==&lt;/span&gt;&lt;span&gt; &apos;POST&apos;&lt;/span&gt;&lt;span&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    return&lt;/span&gt;&lt;span&gt; new&lt;/span&gt;&lt;span&gt; Response&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;JSON&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;stringify&lt;/span&gt;&lt;span&gt;({ code: &lt;/span&gt;&lt;span&gt;1&lt;/span&gt;&lt;span&gt;, message: &lt;/span&gt;&lt;span&gt;&apos;Method not allowed&apos;&lt;/span&gt;&lt;span&gt; }), {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      status: &lt;/span&gt;&lt;span&gt;405&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      headers: { &lt;/span&gt;&lt;span&gt;...&lt;/span&gt;&lt;span&gt;corsHeaders, &lt;/span&gt;&lt;span&gt;&apos;Content-Type&apos;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&apos;application/json&apos;&lt;/span&gt;&lt;span&gt; }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    })&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  try&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    const&lt;/span&gt;&lt;span&gt; body&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; await&lt;/span&gt;&lt;span&gt; request.&lt;/span&gt;&lt;span&gt;json&lt;/span&gt;&lt;span&gt;()&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    const&lt;/span&gt;&lt;span&gt; postId&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; body.postId &lt;/span&gt;&lt;span&gt;||&lt;/span&gt;&lt;span&gt; body.post&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    if&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;!&lt;/span&gt;&lt;span&gt;postId) {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      return&lt;/span&gt;&lt;span&gt; new&lt;/span&gt;&lt;span&gt; Response&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;JSON&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;stringify&lt;/span&gt;&lt;span&gt;({ code: &lt;/span&gt;&lt;span&gt;1&lt;/span&gt;&lt;span&gt;, message: &lt;/span&gt;&lt;span&gt;&apos;Missing postId&apos;&lt;/span&gt;&lt;span&gt; }), {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        status: &lt;/span&gt;&lt;span&gt;400&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        headers: { &lt;/span&gt;&lt;span&gt;...&lt;/span&gt;&lt;span&gt;corsHeaders, &lt;/span&gt;&lt;span&gt;&apos;Content-Type&apos;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&apos;application/json&apos;&lt;/span&gt;&lt;span&gt; }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      })&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    // 获取用户标识（IP + User-Agent）&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    const&lt;/span&gt;&lt;span&gt; ip&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; request.headers.&lt;/span&gt;&lt;span&gt;get&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&apos;CF-Connecting-IP&apos;&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;span&gt;||&lt;/span&gt;&lt;span&gt; &apos;unknown&apos;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    const&lt;/span&gt;&lt;span&gt; userAgent&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; request.headers.&lt;/span&gt;&lt;span&gt;get&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&apos;User-Agent&apos;&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;span&gt;||&lt;/span&gt;&lt;span&gt; &apos;unknown&apos;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    const&lt;/span&gt;&lt;span&gt; userId&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; await&lt;/span&gt;&lt;span&gt; hashString&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;`${&lt;/span&gt;&lt;span&gt;ip&lt;/span&gt;&lt;span&gt;}-${&lt;/span&gt;&lt;span&gt;userAgent&lt;/span&gt;&lt;span&gt;}`&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    // 检查是否已点赞&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    const&lt;/span&gt;&lt;span&gt; recordKey&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; `like:${&lt;/span&gt;&lt;span&gt;postId&lt;/span&gt;&lt;span&gt;}:${&lt;/span&gt;&lt;span&gt;userId&lt;/span&gt;&lt;span&gt;}`&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    const&lt;/span&gt;&lt;span&gt; hasLiked&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; await&lt;/span&gt;&lt;span&gt; UPVOTE_RECORD&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;get&lt;/span&gt;&lt;span&gt;(recordKey)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    let&lt;/span&gt;&lt;span&gt; newCount&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    if&lt;/span&gt;&lt;span&gt; (hasLiked) {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      // 取消点赞&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      await&lt;/span&gt;&lt;span&gt; UPVOTE_RECORD&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;delete&lt;/span&gt;&lt;span&gt;(recordKey)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      const&lt;/span&gt;&lt;span&gt; countKey&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; `like:count:${&lt;/span&gt;&lt;span&gt;postId&lt;/span&gt;&lt;span&gt;}`&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      const&lt;/span&gt;&lt;span&gt; currentCount&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; parseInt&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;await&lt;/span&gt;&lt;span&gt; UPVOTE_COUNT&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;get&lt;/span&gt;&lt;span&gt;(countKey) &lt;/span&gt;&lt;span&gt;||&lt;/span&gt;&lt;span&gt; &apos;0&apos;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      newCount &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; Math.&lt;/span&gt;&lt;span&gt;max&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;0&lt;/span&gt;&lt;span&gt;, currentCount &lt;/span&gt;&lt;span&gt;-&lt;/span&gt;&lt;span&gt; 1&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      await&lt;/span&gt;&lt;span&gt; UPVOTE_COUNT&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;put&lt;/span&gt;&lt;span&gt;(countKey, newCount.&lt;/span&gt;&lt;span&gt;toString&lt;/span&gt;&lt;span&gt;())&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    } &lt;/span&gt;&lt;span&gt;else&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      // 点赞&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      await&lt;/span&gt;&lt;span&gt; UPVOTE_RECORD&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;put&lt;/span&gt;&lt;span&gt;(recordKey, &lt;/span&gt;&lt;span&gt;&apos;1&apos;&lt;/span&gt;&lt;span&gt;, { expirationTtl: &lt;/span&gt;&lt;span&gt;31536000&lt;/span&gt;&lt;span&gt; })&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      const&lt;/span&gt;&lt;span&gt; countKey&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; `like:count:${&lt;/span&gt;&lt;span&gt;postId&lt;/span&gt;&lt;span&gt;}`&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      const&lt;/span&gt;&lt;span&gt; currentCount&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; parseInt&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;await&lt;/span&gt;&lt;span&gt; UPVOTE_COUNT&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;get&lt;/span&gt;&lt;span&gt;(countKey) &lt;/span&gt;&lt;span&gt;||&lt;/span&gt;&lt;span&gt; &apos;0&apos;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      newCount &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; currentCount &lt;/span&gt;&lt;span&gt;+&lt;/span&gt;&lt;span&gt; 1&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      await&lt;/span&gt;&lt;span&gt; UPVOTE_COUNT&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;put&lt;/span&gt;&lt;span&gt;(countKey, newCount.&lt;/span&gt;&lt;span&gt;toString&lt;/span&gt;&lt;span&gt;())&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    return&lt;/span&gt;&lt;span&gt; new&lt;/span&gt;&lt;span&gt; Response&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;JSON&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;stringify&lt;/span&gt;&lt;span&gt;({&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      code: &lt;/span&gt;&lt;span&gt;0&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      data: {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        count: newCount,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        hasLiked: &lt;/span&gt;&lt;span&gt;!&lt;/span&gt;&lt;span&gt;hasLiked&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    }), {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      headers: { &lt;/span&gt;&lt;span&gt;...&lt;/span&gt;&lt;span&gt;corsHeaders, &lt;/span&gt;&lt;span&gt;&apos;Content-Type&apos;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&apos;application/json&apos;&lt;/span&gt;&lt;span&gt; }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    })&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  } &lt;/span&gt;&lt;span&gt;catch&lt;/span&gt;&lt;span&gt; (error) {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    return&lt;/span&gt;&lt;span&gt; new&lt;/span&gt;&lt;span&gt; Response&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;JSON&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;stringify&lt;/span&gt;&lt;span&gt;({ code: &lt;/span&gt;&lt;span&gt;1&lt;/span&gt;&lt;span&gt;, message: error.message }), {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      status: &lt;/span&gt;&lt;span&gt;500&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      headers: { &lt;/span&gt;&lt;span&gt;...&lt;/span&gt;&lt;span&gt;corsHeaders, &lt;/span&gt;&lt;span&gt;&apos;Content-Type&apos;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&apos;application/json&apos;&lt;/span&gt;&lt;span&gt; }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    })&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;// 获取点赞数&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;async&lt;/span&gt;&lt;span&gt; function&lt;/span&gt;&lt;span&gt; handleGetCount&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;request&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;corsHeaders&lt;/span&gt;&lt;span&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  const&lt;/span&gt;&lt;span&gt; url&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; new&lt;/span&gt;&lt;span&gt; URL&lt;/span&gt;&lt;span&gt;(request.url)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  const&lt;/span&gt;&lt;span&gt; postId&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; url.searchParams.&lt;/span&gt;&lt;span&gt;get&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&apos;post&apos;&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;span&gt;||&lt;/span&gt;&lt;span&gt; url.searchParams.&lt;/span&gt;&lt;span&gt;get&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&apos;postId&apos;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  if&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;!&lt;/span&gt;&lt;span&gt;postId) {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    return&lt;/span&gt;&lt;span&gt; new&lt;/span&gt;&lt;span&gt; Response&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;JSON&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;stringify&lt;/span&gt;&lt;span&gt;({ code: &lt;/span&gt;&lt;span&gt;1&lt;/span&gt;&lt;span&gt;, message: &lt;/span&gt;&lt;span&gt;&apos;Missing post parameter&apos;&lt;/span&gt;&lt;span&gt; }), {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      status: &lt;/span&gt;&lt;span&gt;400&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      headers: { &lt;/span&gt;&lt;span&gt;...&lt;/span&gt;&lt;span&gt;corsHeaders, &lt;/span&gt;&lt;span&gt;&apos;Content-Type&apos;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&apos;application/json&apos;&lt;/span&gt;&lt;span&gt; }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    })&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  try&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    const&lt;/span&gt;&lt;span&gt; countKey&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; `like:count:${&lt;/span&gt;&lt;span&gt;postId&lt;/span&gt;&lt;span&gt;}`&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    const&lt;/span&gt;&lt;span&gt; count&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; parseInt&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;await&lt;/span&gt;&lt;span&gt; UPVOTE_COUNT&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;get&lt;/span&gt;&lt;span&gt;(countKey) &lt;/span&gt;&lt;span&gt;||&lt;/span&gt;&lt;span&gt; &apos;0&apos;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    const&lt;/span&gt;&lt;span&gt; ip&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; request.headers.&lt;/span&gt;&lt;span&gt;get&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&apos;CF-Connecting-IP&apos;&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;span&gt;||&lt;/span&gt;&lt;span&gt; &apos;unknown&apos;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    const&lt;/span&gt;&lt;span&gt; userAgent&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; request.headers.&lt;/span&gt;&lt;span&gt;get&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&apos;User-Agent&apos;&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;span&gt;||&lt;/span&gt;&lt;span&gt; &apos;unknown&apos;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    const&lt;/span&gt;&lt;span&gt; userId&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; await&lt;/span&gt;&lt;span&gt; hashString&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;`${&lt;/span&gt;&lt;span&gt;ip&lt;/span&gt;&lt;span&gt;}-${&lt;/span&gt;&lt;span&gt;userAgent&lt;/span&gt;&lt;span&gt;}`&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    const&lt;/span&gt;&lt;span&gt; recordKey&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; `like:${&lt;/span&gt;&lt;span&gt;postId&lt;/span&gt;&lt;span&gt;}:${&lt;/span&gt;&lt;span&gt;userId&lt;/span&gt;&lt;span&gt;}`&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    const&lt;/span&gt;&lt;span&gt; hasLiked&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; !!&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;await&lt;/span&gt;&lt;span&gt; UPVOTE_RECORD&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;get&lt;/span&gt;&lt;span&gt;(recordKey))&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    return&lt;/span&gt;&lt;span&gt; new&lt;/span&gt;&lt;span&gt; Response&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;JSON&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;stringify&lt;/span&gt;&lt;span&gt;({&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      code: &lt;/span&gt;&lt;span&gt;0&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      data: { count, hasLiked }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    }), {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      headers: { &lt;/span&gt;&lt;span&gt;...&lt;/span&gt;&lt;span&gt;corsHeaders, &lt;/span&gt;&lt;span&gt;&apos;Content-Type&apos;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&apos;application/json&apos;&lt;/span&gt;&lt;span&gt; }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    })&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  } &lt;/span&gt;&lt;span&gt;catch&lt;/span&gt;&lt;span&gt; (error) {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    return&lt;/span&gt;&lt;span&gt; new&lt;/span&gt;&lt;span&gt; Response&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;JSON&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;stringify&lt;/span&gt;&lt;span&gt;({ code: &lt;/span&gt;&lt;span&gt;1&lt;/span&gt;&lt;span&gt;, message: error.message }), {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      status: &lt;/span&gt;&lt;span&gt;500&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      headers: { &lt;/span&gt;&lt;span&gt;...&lt;/span&gt;&lt;span&gt;corsHeaders, &lt;/span&gt;&lt;span&gt;&apos;Content-Type&apos;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&apos;application/json&apos;&lt;/span&gt;&lt;span&gt; }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    })&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;// 记录页面访问&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;async&lt;/span&gt;&lt;span&gt; function&lt;/span&gt;&lt;span&gt; handlePageView&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;request&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;corsHeaders&lt;/span&gt;&lt;span&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  if&lt;/span&gt;&lt;span&gt; (request.method &lt;/span&gt;&lt;span&gt;!==&lt;/span&gt;&lt;span&gt; &apos;POST&apos;&lt;/span&gt;&lt;span&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    return&lt;/span&gt;&lt;span&gt; new&lt;/span&gt;&lt;span&gt; Response&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;JSON&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;stringify&lt;/span&gt;&lt;span&gt;({ code: &lt;/span&gt;&lt;span&gt;1&lt;/span&gt;&lt;span&gt;, message: &lt;/span&gt;&lt;span&gt;&apos;Method not allowed&apos;&lt;/span&gt;&lt;span&gt; }), {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      status: &lt;/span&gt;&lt;span&gt;405&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      headers: { &lt;/span&gt;&lt;span&gt;...&lt;/span&gt;&lt;span&gt;corsHeaders, &lt;/span&gt;&lt;span&gt;&apos;Content-Type&apos;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&apos;application/json&apos;&lt;/span&gt;&lt;span&gt; }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    })&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  try&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    const&lt;/span&gt;&lt;span&gt; body&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; await&lt;/span&gt;&lt;span&gt; request.&lt;/span&gt;&lt;span&gt;json&lt;/span&gt;&lt;span&gt;()&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    const&lt;/span&gt;&lt;span&gt; postId&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; body.postId &lt;/span&gt;&lt;span&gt;||&lt;/span&gt;&lt;span&gt; body.post&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    if&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;!&lt;/span&gt;&lt;span&gt;postId) {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      return&lt;/span&gt;&lt;span&gt; new&lt;/span&gt;&lt;span&gt; Response&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;JSON&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;stringify&lt;/span&gt;&lt;span&gt;({ code: &lt;/span&gt;&lt;span&gt;1&lt;/span&gt;&lt;span&gt;, message: &lt;/span&gt;&lt;span&gt;&apos;Missing postId&apos;&lt;/span&gt;&lt;span&gt; }), {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        status: &lt;/span&gt;&lt;span&gt;400&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        headers: { &lt;/span&gt;&lt;span&gt;...&lt;/span&gt;&lt;span&gt;corsHeaders, &lt;/span&gt;&lt;span&gt;&apos;Content-Type&apos;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&apos;application/json&apos;&lt;/span&gt;&lt;span&gt; }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      })&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    const&lt;/span&gt;&lt;span&gt; ip&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; request.headers.&lt;/span&gt;&lt;span&gt;get&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&apos;CF-Connecting-IP&apos;&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;span&gt;||&lt;/span&gt;&lt;span&gt; &apos;unknown&apos;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    const&lt;/span&gt;&lt;span&gt; userAgent&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; request.headers.&lt;/span&gt;&lt;span&gt;get&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&apos;User-Agent&apos;&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;span&gt;||&lt;/span&gt;&lt;span&gt; &apos;unknown&apos;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    const&lt;/span&gt;&lt;span&gt; userId&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; await&lt;/span&gt;&lt;span&gt; hashString&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;`${&lt;/span&gt;&lt;span&gt;ip&lt;/span&gt;&lt;span&gt;}-${&lt;/span&gt;&lt;span&gt;userAgent&lt;/span&gt;&lt;span&gt;}`&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    // 检查是否在5分钟内重复访问（防刷）&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    const&lt;/span&gt;&lt;span&gt; viewRecordKey&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; `view:record:${&lt;/span&gt;&lt;span&gt;postId&lt;/span&gt;&lt;span&gt;}:${&lt;/span&gt;&lt;span&gt;userId&lt;/span&gt;&lt;span&gt;}`&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    const&lt;/span&gt;&lt;span&gt; recentView&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; await&lt;/span&gt;&lt;span&gt; UPVOTE_RECORD&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;get&lt;/span&gt;&lt;span&gt;(viewRecordKey)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    if&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;!&lt;/span&gt;&lt;span&gt;recentView) {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      // 记录访问（5分钟内不重复计数）&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      await&lt;/span&gt;&lt;span&gt; UPVOTE_RECORD&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;put&lt;/span&gt;&lt;span&gt;(viewRecordKey, &lt;/span&gt;&lt;span&gt;&apos;1&apos;&lt;/span&gt;&lt;span&gt;, { expirationTtl: &lt;/span&gt;&lt;span&gt;300&lt;/span&gt;&lt;span&gt; })&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      // 增加访问计数&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      const&lt;/span&gt;&lt;span&gt; countKey&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; `view:count:${&lt;/span&gt;&lt;span&gt;postId&lt;/span&gt;&lt;span&gt;}`&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      const&lt;/span&gt;&lt;span&gt; currentCount&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; parseInt&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;await&lt;/span&gt;&lt;span&gt; UPVOTE_COUNT&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;get&lt;/span&gt;&lt;span&gt;(countKey) &lt;/span&gt;&lt;span&gt;||&lt;/span&gt;&lt;span&gt; &apos;0&apos;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      const&lt;/span&gt;&lt;span&gt; newCount&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; currentCount &lt;/span&gt;&lt;span&gt;+&lt;/span&gt;&lt;span&gt; 1&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      await&lt;/span&gt;&lt;span&gt; UPVOTE_COUNT&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;put&lt;/span&gt;&lt;span&gt;(countKey, newCount.&lt;/span&gt;&lt;span&gt;toString&lt;/span&gt;&lt;span&gt;())&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      return&lt;/span&gt;&lt;span&gt; new&lt;/span&gt;&lt;span&gt; Response&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;JSON&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;stringify&lt;/span&gt;&lt;span&gt;({&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        code: &lt;/span&gt;&lt;span&gt;0&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        data: { views: newCount, counted: &lt;/span&gt;&lt;span&gt;true&lt;/span&gt;&lt;span&gt; }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      }), {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        headers: { &lt;/span&gt;&lt;span&gt;...&lt;/span&gt;&lt;span&gt;corsHeaders, &lt;/span&gt;&lt;span&gt;&apos;Content-Type&apos;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&apos;application/json&apos;&lt;/span&gt;&lt;span&gt; }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      })&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    } &lt;/span&gt;&lt;span&gt;else&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      const&lt;/span&gt;&lt;span&gt; countKey&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; `view:count:${&lt;/span&gt;&lt;span&gt;postId&lt;/span&gt;&lt;span&gt;}`&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      const&lt;/span&gt;&lt;span&gt; currentCount&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; parseInt&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;await&lt;/span&gt;&lt;span&gt; UPVOTE_COUNT&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;get&lt;/span&gt;&lt;span&gt;(countKey) &lt;/span&gt;&lt;span&gt;||&lt;/span&gt;&lt;span&gt; &apos;0&apos;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      return&lt;/span&gt;&lt;span&gt; new&lt;/span&gt;&lt;span&gt; Response&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;JSON&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;stringify&lt;/span&gt;&lt;span&gt;({&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        code: &lt;/span&gt;&lt;span&gt;0&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        data: { views: currentCount, counted: &lt;/span&gt;&lt;span&gt;false&lt;/span&gt;&lt;span&gt; }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      }), {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        headers: { &lt;/span&gt;&lt;span&gt;...&lt;/span&gt;&lt;span&gt;corsHeaders, &lt;/span&gt;&lt;span&gt;&apos;Content-Type&apos;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&apos;application/json&apos;&lt;/span&gt;&lt;span&gt; }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      })&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  } &lt;/span&gt;&lt;span&gt;catch&lt;/span&gt;&lt;span&gt; (error) {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    return&lt;/span&gt;&lt;span&gt; new&lt;/span&gt;&lt;span&gt; Response&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;JSON&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;stringify&lt;/span&gt;&lt;span&gt;({ code: &lt;/span&gt;&lt;span&gt;1&lt;/span&gt;&lt;span&gt;, message: error.message }), {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      status: &lt;/span&gt;&lt;span&gt;500&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      headers: { &lt;/span&gt;&lt;span&gt;...&lt;/span&gt;&lt;span&gt;corsHeaders, &lt;/span&gt;&lt;span&gt;&apos;Content-Type&apos;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&apos;application/json&apos;&lt;/span&gt;&lt;span&gt; }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    })&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;// 获取统计数据（点赞 + 访问）&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;async&lt;/span&gt;&lt;span&gt; function&lt;/span&gt;&lt;span&gt; handleGetStats&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;request&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;corsHeaders&lt;/span&gt;&lt;span&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  const&lt;/span&gt;&lt;span&gt; url&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; new&lt;/span&gt;&lt;span&gt; URL&lt;/span&gt;&lt;span&gt;(request.url)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  const&lt;/span&gt;&lt;span&gt; postId&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; url.searchParams.&lt;/span&gt;&lt;span&gt;get&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&apos;post&apos;&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;span&gt;||&lt;/span&gt;&lt;span&gt; url.searchParams.&lt;/span&gt;&lt;span&gt;get&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&apos;postId&apos;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  if&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;!&lt;/span&gt;&lt;span&gt;postId) {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    return&lt;/span&gt;&lt;span&gt; new&lt;/span&gt;&lt;span&gt; Response&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;JSON&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;stringify&lt;/span&gt;&lt;span&gt;({ code: &lt;/span&gt;&lt;span&gt;1&lt;/span&gt;&lt;span&gt;, message: &lt;/span&gt;&lt;span&gt;&apos;Missing post parameter&apos;&lt;/span&gt;&lt;span&gt; }), {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      status: &lt;/span&gt;&lt;span&gt;400&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      headers: { &lt;/span&gt;&lt;span&gt;...&lt;/span&gt;&lt;span&gt;corsHeaders, &lt;/span&gt;&lt;span&gt;&apos;Content-Type&apos;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&apos;application/json&apos;&lt;/span&gt;&lt;span&gt; }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    })&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  try&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    const&lt;/span&gt;&lt;span&gt; likeCountKey&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; `like:count:${&lt;/span&gt;&lt;span&gt;postId&lt;/span&gt;&lt;span&gt;}`&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    const&lt;/span&gt;&lt;span&gt; likeCount&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; parseInt&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;await&lt;/span&gt;&lt;span&gt; UPVOTE_COUNT&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;get&lt;/span&gt;&lt;span&gt;(likeCountKey) &lt;/span&gt;&lt;span&gt;||&lt;/span&gt;&lt;span&gt; &apos;0&apos;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    const&lt;/span&gt;&lt;span&gt; viewCountKey&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; `view:count:${&lt;/span&gt;&lt;span&gt;postId&lt;/span&gt;&lt;span&gt;}`&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    const&lt;/span&gt;&lt;span&gt; viewCount&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; parseInt&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;await&lt;/span&gt;&lt;span&gt; UPVOTE_COUNT&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;get&lt;/span&gt;&lt;span&gt;(viewCountKey) &lt;/span&gt;&lt;span&gt;||&lt;/span&gt;&lt;span&gt; &apos;0&apos;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    const&lt;/span&gt;&lt;span&gt; ip&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; request.headers.&lt;/span&gt;&lt;span&gt;get&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&apos;CF-Connecting-IP&apos;&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;span&gt;||&lt;/span&gt;&lt;span&gt; &apos;unknown&apos;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    const&lt;/span&gt;&lt;span&gt; userAgent&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; request.headers.&lt;/span&gt;&lt;span&gt;get&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&apos;User-Agent&apos;&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;span&gt;||&lt;/span&gt;&lt;span&gt; &apos;unknown&apos;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    const&lt;/span&gt;&lt;span&gt; userId&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; await&lt;/span&gt;&lt;span&gt; hashString&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;`${&lt;/span&gt;&lt;span&gt;ip&lt;/span&gt;&lt;span&gt;}-${&lt;/span&gt;&lt;span&gt;userAgent&lt;/span&gt;&lt;span&gt;}`&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    const&lt;/span&gt;&lt;span&gt; recordKey&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; `like:${&lt;/span&gt;&lt;span&gt;postId&lt;/span&gt;&lt;span&gt;}:${&lt;/span&gt;&lt;span&gt;userId&lt;/span&gt;&lt;span&gt;}`&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    const&lt;/span&gt;&lt;span&gt; hasLiked&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; !!&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;await&lt;/span&gt;&lt;span&gt; UPVOTE_RECORD&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;get&lt;/span&gt;&lt;span&gt;(recordKey))&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    return&lt;/span&gt;&lt;span&gt; new&lt;/span&gt;&lt;span&gt; Response&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;JSON&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;stringify&lt;/span&gt;&lt;span&gt;({&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      code: &lt;/span&gt;&lt;span&gt;0&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      data: { likes: likeCount, views: viewCount, hasLiked }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    }), {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      headers: { &lt;/span&gt;&lt;span&gt;...&lt;/span&gt;&lt;span&gt;corsHeaders, &lt;/span&gt;&lt;span&gt;&apos;Content-Type&apos;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&apos;application/json&apos;&lt;/span&gt;&lt;span&gt; }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    })&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  } &lt;/span&gt;&lt;span&gt;catch&lt;/span&gt;&lt;span&gt; (error) {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    return&lt;/span&gt;&lt;span&gt; new&lt;/span&gt;&lt;span&gt; Response&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;JSON&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;stringify&lt;/span&gt;&lt;span&gt;({ code: &lt;/span&gt;&lt;span&gt;1&lt;/span&gt;&lt;span&gt;, message: error.message }), {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      status: &lt;/span&gt;&lt;span&gt;500&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      headers: { &lt;/span&gt;&lt;span&gt;...&lt;/span&gt;&lt;span&gt;corsHeaders, &lt;/span&gt;&lt;span&gt;&apos;Content-Type&apos;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&apos;application/json&apos;&lt;/span&gt;&lt;span&gt; }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    })&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;// 哈希函数&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;async&lt;/span&gt;&lt;span&gt; function&lt;/span&gt;&lt;span&gt; hashString&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;str&lt;/span&gt;&lt;span&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  const&lt;/span&gt;&lt;span&gt; encoder&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; new&lt;/span&gt;&lt;span&gt; TextEncoder&lt;/span&gt;&lt;span&gt;()&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  const&lt;/span&gt;&lt;span&gt; data&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; encoder.&lt;/span&gt;&lt;span&gt;encode&lt;/span&gt;&lt;span&gt;(str)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  const&lt;/span&gt;&lt;span&gt; hashBuffer&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; await&lt;/span&gt;&lt;span&gt; crypto.subtle.&lt;/span&gt;&lt;span&gt;digest&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&apos;SHA-256&apos;&lt;/span&gt;&lt;span&gt;, data)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  const&lt;/span&gt;&lt;span&gt; hashArray&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; Array.&lt;/span&gt;&lt;span&gt;from&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;new&lt;/span&gt;&lt;span&gt; Uint8Array&lt;/span&gt;&lt;span&gt;(hashBuffer))&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  return&lt;/span&gt;&lt;span&gt; hashArray.&lt;/span&gt;&lt;span&gt;map&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;b&lt;/span&gt;&lt;span&gt; =&amp;gt;&lt;/span&gt;&lt;span&gt; b.&lt;/span&gt;&lt;span&gt;toString&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;16&lt;/span&gt;&lt;span&gt;).&lt;/span&gt;&lt;span&gt;padStart&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;2&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;&apos;0&apos;&lt;/span&gt;&lt;span&gt;)).&lt;/span&gt;&lt;span&gt;join&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&apos;&apos;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;API 接口说明&lt;/h3&gt;
&lt;p&gt;更新后的 API 提供以下接口：&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;1. 点赞/取消点赞&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;POST&lt;/span&gt;&lt;span&gt; /like&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;Content-Type:&lt;/span&gt;&lt;span&gt; application/json&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  &quot;postId&quot;&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; &quot;your-post-id&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;2. 获取点赞数&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;GET&lt;/span&gt;&lt;span&gt; /count?post=your-post-id&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;3. 记录页面访问（新增）&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;POST&lt;/span&gt;&lt;span&gt; /view&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;Content-Type:&lt;/span&gt;&lt;span&gt; application/json&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  &quot;postId&quot;&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; &quot;your-post-id&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;4. 获取完整统计（新增）&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;GET&lt;/span&gt;&lt;span&gt; /stats?post=your-post-id&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;返回：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  &quot;code&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;0&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  &quot;data&quot;&lt;/span&gt;&lt;span&gt;: {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    &quot;likes&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;10&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    &quot;views&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;123&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    &quot;hasLiked&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;false&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;数据存储说明&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;无需创建新的 KV namespace&lt;/strong&gt;，通过 Key 前缀区分数据类型：&lt;/p&gt;
&lt;p&gt;在 &lt;code&gt;UPVOTE_COUNT&lt;/code&gt; 中：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;like:count:{postId}&lt;/code&gt; - 点赞计数&lt;/li&gt;
&lt;li&gt;&lt;code&gt;view:count:{postId}&lt;/code&gt; - 访问计数&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;在 &lt;code&gt;UPVOTE_RECORD&lt;/code&gt; 中：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;like:{postId}:{userId}&lt;/code&gt; - 点赞记录（1年过期）&lt;/li&gt;
&lt;li&gt;&lt;code&gt;view:record:{postId}:{userId}&lt;/code&gt; - 访问记录（5分钟过期，防刷）&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;前端集成示例（Astro）&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;创建访问统计组件&lt;/strong&gt; &lt;code&gt;src/components/ViewCount.astro&lt;/code&gt;：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;---&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;import { siteConfig } from &apos;../config&apos;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;interface Props {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  postId: string;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;const { postId } = Astro.props;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;const apiUrl = siteConfig.like.apiUrl;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;---&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;lt;span class=&quot;view-count&quot; id=&quot;view-count-container&quot;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  &amp;lt;span id=&quot;view-count&quot;&amp;gt;--&amp;lt;/span&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  &amp;lt;span&amp;gt;次阅读&amp;lt;/span&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;lt;/span&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;lt;script is:inline define:vars={{ postId, apiUrl }}&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  (function() {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    let viewRecorded = false;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    function initViewCount() {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      fetchViewCount();&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      if (!viewRecorded) {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        setTimeout(() =&amp;gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;          recordPageView();&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;          viewRecorded = true;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        }, 2000);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    async function fetchViewCount() {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      try {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        const response = await fetch(`${apiUrl}/stats?post=${encodeURIComponent(postId)}`);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        const data = await response.json();&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        if (data.code === 0) {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;          document.getElementById(&apos;view-count&apos;).textContent = data.data.views || 0;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      } catch (error) {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        console.error(&apos;获取访问次数失败:&apos;, error);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    async function recordPageView() {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      try {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        await fetch(`${apiUrl}/view`, {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;          method: &apos;POST&apos;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;          headers: { &apos;Content-Type&apos;: &apos;application/json&apos; },&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;          body: JSON.stringify({ postId })&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        });&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      } catch (error) {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        console.error(&apos;记录访问失败:&apos;, error);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    if (document.readyState === &apos;loading&apos;) {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      document.addEventListener(&apos;DOMContentLoaded&apos;, initViewCount);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    } else {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      initViewCount();&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    document.addEventListener(&apos;astro:page-load&apos;, () =&amp;gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      viewRecorded = false;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      initViewCount();&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    });&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  })();&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;lt;/script&amp;gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;在文章页面使用：&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;---&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;import ViewCount from &apos;../../components/ViewCount.astro&apos;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;---&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;lt;div class=&quot;article-meta&quot;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  &amp;lt;time&amp;gt;{formatDate(post.data.date)}&amp;lt;/time&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  &amp;lt;span&amp;gt;|&amp;lt;/span&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  &amp;lt;span&amp;gt;{readingTime} 分钟&amp;lt;/span&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  &amp;lt;span&amp;gt;|&amp;lt;/span&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  &amp;lt;ViewCount postId={post.slug} /&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;功能特点&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;访问统计：&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;✅ 自动记录页面访问&lt;/li&gt;
&lt;li&gt;✅ 5分钟内同一用户不重复计数&lt;/li&gt;
&lt;li&gt;✅ 使用 SHA-256 哈希保护隐私&lt;/li&gt;
&lt;li&gt;✅ 页面加载2秒后记录（避免误计数）&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;性能优化：&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;✅ 异步记录，不阻塞页面&lt;/li&gt;
&lt;li&gt;✅ 复用现有 KV，零额外成本&lt;/li&gt;
&lt;li&gt;✅ 支持 Astro View Transitions&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;测试&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;测试访问统计：&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;# 记录访问&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;curl&lt;/span&gt;&lt;span&gt; -X&lt;/span&gt;&lt;span&gt; POST&lt;/span&gt;&lt;span&gt; &amp;lt;&lt;/span&gt;&lt;span&gt;https://your-worker.workers.dev/vie&lt;/span&gt;&lt;span&gt;w&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;span&gt; \\&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  -H&lt;/span&gt;&lt;span&gt; &quot;Content-Type: application/json&quot;&lt;/span&gt;&lt;span&gt; \\&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  -d&lt;/span&gt;&lt;span&gt; &apos;{&quot;postId&quot;: &quot;test-post&quot;}&apos;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;# 获取统计&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;curl&lt;/span&gt;&lt;span&gt; &quot;&amp;lt;https://your-worker.workers.dev/stats?post=test-post&amp;gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;注意事项&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;Cloudflare Workers 免费额度：&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;每天 100,000 次请求&lt;/li&gt;
&lt;li&gt;每天 100,000 次 KV 读取&lt;/li&gt;
&lt;li&gt;每天 1,000 次 KV 写入&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;对于个人博客完全够用。如果流量较大，建议：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;增加防刷时间（如10分钟）&lt;/li&gt;
&lt;li&gt;使用 Cloudflare Analytics 获取更详细数据&lt;/li&gt;
&lt;li&gt;升级到付费计划&lt;/li&gt;
&lt;/ol&gt;</content:encoded><category>tech</category><category>访问统计</category><category>cloudflare</category></item><item><title>self-hosted 应用总结</title><link>https://yourdomain.com/blog/self-hosted-apps-summary/</link><guid isPermaLink="true">https://yourdomain.com/blog/self-hosted-apps-summary/</guid><description>分类：tech | 标签：部署, docker</description><pubDate>Sun, 04 Jan 2026 12:49:00 GMT</pubDate><content:encoded>&lt;p&gt;Self-host，即「自托管」，是指在自己的服务器上部署和运行软件，而非依赖第三方 SaaS 服务。&lt;/p&gt;
&lt;p&gt;几年前，我对 self-hosted 近乎狂热。经常浏览 &lt;a href=&quot;http://awesome-selfhosted.net/&quot;&gt;awesome-selfhosted&lt;/a&gt; 寻找新项目，总想让服务器发挥更多价值。借助 Docker 和 Caddy 的便利，只需编写一个 compose.yml 文件，新应用就能快速上线，并通过 &lt;a href=&quot;http://suus.me/&quot;&gt;suus.me&lt;/a&gt; 的子域名对外访问。这种部署体验简单高效，让人上瘾。&lt;/p&gt;
&lt;p&gt;开源软件的精神值得尊敬，这些项目为互联网带来了宝贵的自由选择。但必须承认，许多开源产品更关注功能实现而非用户体验，界面设计往往差强人意。随着时间推移，我的想法发生了变化：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;工作后可支配时间大幅减少，不想在服务维护上投入过多精力&lt;/li&gt;
&lt;li&gt;深度使用 Apple 生态后，iCloud 配合系统原生应用已能满足大部分需求&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;这股 self-hosted 热情逐渐冷却了下来。&lt;/p&gt;
&lt;p&gt;趁着最近重构网站，我清理了服务器上那些不常用的自托管服务，只保留真正必需的几个。那些被下线的应用，都曾让我兴奋、折腾、投入过热情。这篇文章记录它们，算是一种告别。&lt;/p&gt;
&lt;h3&gt;很酷，但用不上&lt;/h3&gt;
&lt;p&gt;以下是已经下线的应用。它们曾经让我兴奋过，部署过，使用过一段时间，但最终因为各种原因退出了我的服务器。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;&lt;a href=&quot;https://supabase.com/docs/guides/self-hosting&quot;&gt;Supabase self-hosted&lt;/a&gt;&lt;/strong&gt;：Supabase 的自托管版本，提供完整的后端解决方案。**千万别自己部署，会变得不幸（尽管看起来很酷）。**使用 Supabase 的初衷就是将后端复杂度外包，自己部署完全违背了这个目的。光是官方示例的 compose.yml 就长达 500 多行，包含十几个组件：studio、kong、gotrue、postgrest、realtime、storage-api、imgproxy、postgres-meta、edge-runtime、logflare、postgres、vector、supavisor、minio……还是老老实实用在线服务吧。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;&lt;a href=&quot;https://github.com/dbeaver/cloudbeaver&quot;&gt;CloudBeaver&lt;/a&gt; / &lt;a href=&quot;https://www.phpmyadmin.net/&quot;&gt;phpMyAdmin&lt;/a&gt;&lt;/strong&gt;：数据库管理的 Web 界面，无需写 SQL 就能增删改查。我甚至一度把 CloudBeaver 当作博客后台，直接在里面发文章。很酷，但现在数据库都迁移到 Supabase 了，这些工具也就没必要了。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;&lt;a href=&quot;https://www.authelia.com/&quot;&gt;Authelia&lt;/a&gt; / &lt;a href=&quot;https://www.keycloak.org/&quot;&gt;Keycloak&lt;/a&gt;&lt;/strong&gt;：自建单点登录（SSO），为所有自托管应用提供统一的用户认证系统。很酷，但我不再需要单独的 SSO 了。Supabase 本身就提供 &lt;a href=&quot;https://supabase.com/docs/guides/auth/oauth-server/getting-started&quot;&gt;OAuth Server 功能&lt;/a&gt;，账户体系可以直接复用我的 &lt;a href=&quot;http://skywt.net/&quot;&gt;skywt.net&lt;/a&gt;。这更酷！&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;&lt;a href=&quot;https://freshrss.org/index.html&quot;&gt;FreshRSS&lt;/a&gt;&lt;/strong&gt;：简洁实用的 RSS 阅读器。很酷，但我现在用开源客户端 NetNewsWire，足够简洁，通过 iCloud 同步更方便，不再需要在浏览器里看 RSS 了。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;&lt;a href=&quot;https://github.com/dani-garcia/vaultwarden&quot;&gt;VaultWarden&lt;/a&gt;&lt;/strong&gt;：BitWarden 的开源服务端实现。很酷，但我已经完全切换到 Apple Passwords 近一年，没遇到任何问题。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;&lt;a href=&quot;https://nextcloud.com/&quot;&gt;NextCloud&lt;/a&gt;&lt;/strong&gt;：历史悠久、功能强大的网盘系统，拥有庞大的应用生态，足够折腾很久。很酷，但基于 PHP 的古老技术栈导致体验一言难尽。大多数功能属于「看起来很酷，但实际不会用」的类型（真的有人会在 NextCloud 里管理菜谱和购物清单吗？）。或许适合团队协作，但对个人来说，没必要。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;&lt;a href=&quot;https://cloudreve.org/&quot;&gt;Cloudreve&lt;/a&gt;&lt;/strong&gt;：功能强大的云盘系统。很酷，但我有 iCloud。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;&lt;a href=&quot;https://usememos.com/&quot;&gt;Memos&lt;/a&gt;&lt;/strong&gt;：最初是 flomo 浮墨笔记的开源替代品。很酷，但我的想法都发到社交媒体了，形式也很类似。另外吐槽一下，这个软件的 UI 改来改去，每隔几个版本就大改一次，真的难以适应。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;&lt;a href=&quot;https://gitea.com/&quot;&gt;Gitea&lt;/a&gt;&lt;/strong&gt;：类似 GitLab 的代码托管平台。很酷，但我想不到任何理由把项目放在自己的平台而不是 GitHub。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;&lt;a href=&quot;https://github.com/SnapDrop/snapdrop&quot;&gt;Snapdrop&lt;/a&gt;&lt;/strong&gt;：文件传输工具。很酷，但替代品太多了。Apple 生态内的 AirDrop 体验好太多。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;&lt;a href=&quot;https://github.com/coder/code-server&quot;&gt;Code-server&lt;/a&gt;&lt;/strong&gt;：运行在浏览器里的 VSCode。很酷，但我什么时候会放着本地 IDE 不用，跑到浏览器里受罪呢？&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;&lt;a href=&quot;https://github.com/janeczku/calibre-web&quot;&gt;Calibre-web&lt;/a&gt;&lt;/strong&gt;：为 Calibre 电子书库提供 Web 界面。很酷，但如果我想看有哪些书，直接打开本地的 Calibre 不是更方便吗？&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;&lt;a href=&quot;https://overleaf.com/&quot;&gt;Overleaf&lt;/a&gt;&lt;/strong&gt;：功能完善的在线 LaTeX 编辑器。很酷，但只有写正式论文才用得上，而且自己部署资源占用太大，体验还不如直接用官方的在线服务 &lt;a href=&quot;http://overleaf.com/&quot;&gt;overleaf.com&lt;/a&gt;。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;&lt;a href=&quot;https://getoutline.com/&quot;&gt;Outline&lt;/a&gt;&lt;/strong&gt;：支持团队协作的知识库系统。很酷，UI 漂亮，协作体验不错（有点像飞书文档），但部署非常麻烦，个人使用完全没必要。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;&lt;a href=&quot;https://github.com/cooderl/wewe-rss&quot;&gt;WeWeRSS&lt;/a&gt;&lt;/strong&gt;：将微信公众号转换为 RSS feed，利用的是（逆向得到的）微信读书接口。很酷，但账号失效太频繁，隔三差五就要重新扫码认证，维护成本太高。万恶的微信。&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;很酷，且离不开&lt;/h3&gt;
&lt;p&gt;服务器上依然保留着一些经常使用的自托管应用。它们通常必须依赖服务器运行，找不到合适的非自托管替代品，所以继续保留。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;&lt;a href=&quot;https://docs.rsshub.app/&quot;&gt;RSSHub&lt;/a&gt;&lt;/strong&gt;：将任何网站转换为 RSS feed。公共实例可能速度慢或不稳定，所以选择自己部署。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;&lt;a href=&quot;https://github.com/Finb/Bark&quot;&gt;Bark&lt;/a&gt;&lt;/strong&gt;：利用 APNs 向 iOS 推送通知。简单好用。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;&lt;a href=&quot;https://headscale.net/&quot;&gt;Headscale&lt;/a&gt;&lt;/strong&gt;：开源的 Tailscale 服务端，内置 DERP Server，非常实用。由于众所周知的原因，想在国内稳定使用 Tailscale 必须自己部署节点。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;&lt;a href=&quot;https://matomo.org/&quot;&gt;Matomo&lt;/a&gt;&lt;/strong&gt;：我用过的最强大的网站分析工具。试过 Plausible、Umami，功能都不如 Matomo 全面（虽然 Matomo 的使用体验比较一般）。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;&lt;a href=&quot;https://archivebox.io/&quot;&gt;ArchiveBox&lt;/a&gt;&lt;/strong&gt;：网页存档工具。在这个互联网快速崩塌的时代尤其重要。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;&lt;a href=&quot;https://www.min.io/&quot;&gt;MinIO&lt;/a&gt;&lt;/strong&gt;：兼容 S3 协议的对象存储。部署它是为了做国内图床镜像。虽然主要使用 Cloudflare R2 作为图床，但由于众所周知的原因，国内访问速度被刻意劣化（实测下载 3MB 图片需要二十多秒）。不得不在境内网络建立镜像。各大云厂商的 S3 服务都不便宜，不如在自己服务器上搭建，性价比最高。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;&lt;a href=&quot;https://caddyserver.com/&quot;&gt;Caddy&lt;/a&gt;&lt;/strong&gt;：能够自动申请 TLS 证书的 Web 服务器。配置简洁方便，一行配置即可反向代理启动一个网站。对个人来说，比 Nginx 好用太多。特别是配合 &lt;a href=&quot;https://github.com/lucaslorentz/caddy-docker-proxy&quot;&gt;caddy-docker-proxy&lt;/a&gt;，连 Caddy 的配置都可以直接写在对应服务的 compose.yml 中。自托管玩家必备。&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;hr /&gt;
&lt;p&gt;工作后，属于自己的时间变得更加宝贵，不再像以前那样能花一下午折腾某个细节问题。这让我更加理解 Serverless 这类「将复杂度外包」服务的价值。现在我的数据库放在 Supabase，文件存在 Cloudflare R2，前端（海外）部署在 Vercel。不用花太多精力维护，可以信任它们的稳定性，让我能把精力投入到更重要的事情上。&lt;/p&gt;</content:encoded><category>tech</category><category>部署</category><category>docker</category></item><item><title>2026 未来可期</title><link>https://yourdomain.com/blog/2026-future-expectations/</link><guid isPermaLink="true">https://yourdomain.com/blog/2026-future-expectations/</guid><description>分类：life | 标签：生活, 思考</description><pubDate>Tue, 30 Dec 2025 13:17:00 GMT</pubDate><content:encoded>&lt;p&gt;站在2026年的阳光下，看着镜子里鬓角隐约的白发，我不再像三十岁时那样急着去拔掉它。40岁出头的年纪，刚好是人生的中点，也是这一代人最需要“定力”的时刻。2026年，AI已经帮我们处理了大部分的机械劳动，这反而逼着我们去思考：那些AI做不到的、真正属于一个人的“内核”到底是什么？&lt;/p&gt;
&lt;p&gt;在这个“未来已来”的年份，我针对教育、职场和财富这三个中年人最扎心的命题，整理了一份属于自己的“复盘笔记”。&lt;/p&gt;
&lt;h3&gt;教育心得：在AI时代，守护一个“小学生”的好奇心&lt;/h3&gt;
&lt;p&gt;2026年，我家孩子正读小学。当他在平板电脑上熟练地用语音指令让AI帮他润色日记、生成科学课的实验草图时，我这个做家长的，心态经历了一场从“恐慌”到“淡定”的洗礼。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;我总结出：小学阶段的教育，不再是刷多少题，而是保护好他那点“不被算法吞噬”的灵气。&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;从“卷分数”转向“卷习惯”：&lt;/strong&gt; 既然基础计算和背诵已经可以被工具秒杀，我不再盯着他的试卷错了几处。我更在意他是否养成了独立思考的习惯，是否能在面对AI给出的答案时反问一句“为什么”。在2026年，&lt;strong&gt;“提问的能力”远比“回答的能力”更值钱。&lt;/strong&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;数字化时代的“视力”与“定力”：&lt;/strong&gt; 作为家长，我最大的战场变成了控制屏幕时长和筛选信息流。我带他去泥地里打滚，去森林里辨认昆虫，去体验那些没有屏幕、没有算法的真实触感。我希望他在小学阶段能明白：代码构建的世界再精彩，也比不上大自然的一缕风。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;放下“赢在起跑线”的执念：&lt;/strong&gt; 40岁以后我看开了，未来的职业形态可能我们现在根本想象不到。与其逼他学一个可能被淘汰的特长，不如陪他读读历史、练练体育。一个身体结实、心态阳光、有着底层人文素养的孩子，无论到了哪个时代，都有饭吃。&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;家庭财富：从“加杠杆”到“筑护城河”的回归&lt;/h3&gt;
&lt;p&gt;经历过市场的起起伏伏，到了2026年，我的理财观变得极其“保守且清醒”。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;我的财富总结：现金流是血液，健康是底金，保险是城墙。&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;稳健为王：&lt;/strong&gt; 孩子还在读小学，未来的路还长。我不再执着于那些暴富的投机，而是配置了更多的分红类和稳健类资产。对于“三明治”家庭，&lt;strong&gt;“不亏损”远比“高回报”重要。&lt;/strong&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;投资健康：&lt;/strong&gt; 这是我总结出的最划算的一笔理财。2026年，我把每年的全家深度体检和健身列为刚性支出。守住自己的身体，就是守住孩子和父母的天空。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;教育基金独立化：&lt;/strong&gt; 我已经为孩子建立了一个独立的教育信托。无论外界环境怎么变，这笔钱能确保他受完完整的教育，这是我作为父亲能给他的最后一道屏障。&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;不惑之年，未来大有可为&lt;/h3&gt;
&lt;p&gt;“未来可期”对20岁的青年来说，是星辰大海；对40多岁的我来说，是&lt;strong&gt;一蔬一饭的踏实，是父母晚年的安稳，是孩子眼里的光亮，更是自己内心那份不再随波逐流的淡定。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;2026年，我不去羡慕那些少年成名的神话，我只看重此时此刻我对手头生活的掌控感。人到中年，真正的自由不是想做什么就做什么，而是&lt;strong&gt;有能力拒绝那些不属于自己的喧嚣，守住自己的一亩三分地，活得通透、活得体面。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;2026，不仅是时代的未来，更是我们这代中年人重新出发、向下扎根、向上开花的黄金时代。&lt;/p&gt;</content:encoded><category>life</category><category>生活</category><category>思考</category></item><item><title>为静态站点添加点赞和访问统计功能</title><link>https://yourdomain.com/blog/add-like-feature-to-static-site/</link><guid isPermaLink="true">https://yourdomain.com/blog/add-like-feature-to-static-site/</guid><description>分类：life | 标签：Cloudflare, 部署, 技术</description><pubDate>Mon, 29 Dec 2025 14:28:00 GMT</pubDate><content:encoded>&lt;p&gt;本文介绍如何使用 Cloudflare Workers + KV 为静态网站添加点赞和访问统计功能。基于 hugo-bearneo 的 Upvote 功能扩展而来，支持：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;✅ &lt;strong&gt;点赞功能&lt;/strong&gt;：用户对文章点赞，统计点赞数，记录点赞状态&lt;/li&gt;
&lt;li&gt;✅ &lt;strong&gt;访问统计&lt;/strong&gt;：自动记录文章访问次数，防刷机制&lt;/li&gt;
&lt;li&gt;✅ &lt;strong&gt;完全免费&lt;/strong&gt;：使用 Cloudflare 免费额度&lt;/li&gt;
&lt;li&gt;✅ &lt;strong&gt;隐私保护&lt;/strong&gt;：使用哈希保护用户身份&lt;/li&gt;
&lt;li&gt;✅ &lt;strong&gt;易于部署&lt;/strong&gt;：无需数据库，几分钟完成部署&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;部署指南&lt;/h2&gt;
&lt;h3&gt;部署 Worker&lt;/h3&gt;
&lt;p&gt;注册/登录 Cloudflare 后台，前往 Workers 模块后点击 Create（下图 2 处）。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://rokcso.com/p/post-upvote-api/img/create-worker-1.webp&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;点击 Create Worker（下图 3 处）。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://rokcso.com/p/post-upvote-api/img/create-worker-2.webp&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;随便输入一个名称（比如 post-upvote）后点击 Deploy（下图 5 处）。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://rokcso.com/p/post-upvote-api/img/create-worker-3.webp&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;然后点击 Edit code（下图 6 处）。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://rokcso.com/p/post-upvote-api/img/edit-worker-code-1.webp&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;删除代码编辑器（下图 7 处）中原有的代码，将本项目 &lt;a href=&quot;https://github.com/rokcso/post-upvote-api/blob/main/worker.js&quot;&gt;worker.js&lt;/a&gt; 中的代码完全复制粘贴到代码编辑器中，点击 Deploy（下图 8 处）。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://rokcso.com/p/post-upvote-api/img/edit-worker-code-2.webp&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;h3&gt;创建 KV namespace&lt;/h3&gt;
&lt;p&gt;注册/登录 Cloudflare 后台，前往 KV 模块后点击 Create（下图 10 处）。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://rokcso.com/p/post-upvote-api/img/create-kv-1.webp&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;随便输入一个名称（比如 upvote-count）后点击 Add（下图 12 处）。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://rokcso.com/p/post-upvote-api/img/create-kv-2.webp&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;用相同的步骤再创建一个 KV namespace，依然可以随便命名（比如 upvote-record）。&lt;/p&gt;
&lt;h3&gt;为 Worker 绑定 KV namespace&lt;/h3&gt;
&lt;p&gt;注册/登录 Cloudflare 后台，前往 Workers 模块后点击进入刚刚创建的 Worker（如本案例中下图 14 处的 post-upvote）。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://rokcso.com/p/post-upvote-api/img/binding-kv-1.webp&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;前往该 Worker 中的 Settings -&amp;gt; Bindings，点击 Add（下图 17 处）。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://rokcso.com/p/post-upvote-api/img/binding-kv-2.webp&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;选择 KV namespace 后输入 Variable name 为 &lt;code&gt;UPVOTE_COUNT&lt;/code&gt;，然后选择一个刚刚创建的 KV namespace（比如 upvote-count），随后点击 Save（下图 20 处）。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://rokcso.com/p/post-upvote-api/img/binding-kv-3.webp&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;用相同的步骤再创建一个 Variable name 为 &lt;code&gt;UPVOTE_RECORD&lt;/code&gt;，选择刚刚创建的另一个 KV namespace（比如 upvote-record），随后点击 Save。&lt;/p&gt;
&lt;p&gt;正确的配置应如下图 21 处，Variable name（即 &lt;code&gt;UPVOTE_COUNT&lt;/code&gt; 和 &lt;code&gt;UPVOTE_RECORD&lt;/code&gt;）一定不能错。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://rokcso.com/p/post-upvote-api/img/binding-kv-4.webp&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;h3&gt;测试&lt;/h3&gt;
&lt;p&gt;注册/登录 Cloudflare 后台，前往 Workers 模块后点击进入刚刚创建的 Worker，进入该 Worker 中的 Settings -&amp;gt; Domains &amp;amp; Routes，此处默认启用的 workers.dev 域名后对应的 Value（下图 22 处）即为该 Worker 的域名。&lt;/p&gt;
&lt;p&gt;或者直接点击下图 23 处访问该 Worker 的域名。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://rokcso.com/p/post-upvote-api/img/test-api-1.webp&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;通过浏览器访问该 Worker 的域名后如果能看到如下图提示即为部署成功。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://rokcso.com/p/post-upvote-api/img/test-api-2.webp&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;h3&gt;注意事项&lt;/h3&gt;
&lt;p&gt;中国境内可能无法顺畅访问 Cloudflare Workers 的 workers.dev 域名，可以通过为该 Worker 添加一个自定义域名解决。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://rokcso.com/p/post-upvote-api/img/custom-domain-1.webp&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;h2&gt;通用集成方法&lt;/h2&gt;
&lt;p&gt;部署完成后，你可以在任何静态网站中集成这个点赞功能。以下是通用的前端集成方法：&lt;/p&gt;
&lt;h3&gt;API 接口说明&lt;/h3&gt;
&lt;p&gt;假设你的 Worker 域名为 &lt;code&gt;https://your-worker.workers.dev&lt;/code&gt;，API 提供以下接口：&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;1. 获取文章点赞数&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;GET https://your-worker.workers.dev/upvote?postId=your-post-id&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;2. 点赞/取消点赞&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;POST https://your-worker.workers.dev/upvote&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;Content-Type: application/json&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  &quot;postId&quot;: &quot;your-post-id&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;前端实现示例&lt;/h3&gt;
&lt;p&gt;以下是一个简单的 HTML + JavaScript 实现示例：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;&amp;lt;&lt;/span&gt;&lt;span&gt;div&lt;/span&gt;&lt;span&gt; class&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;upvote-container&quot;&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  &amp;lt;&lt;/span&gt;&lt;span&gt;button&lt;/span&gt;&lt;span&gt; id&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;upvote-btn&quot;&lt;/span&gt;&lt;span&gt; class&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;upvote-button&quot;&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    &amp;lt;&lt;/span&gt;&lt;span&gt;span&lt;/span&gt;&lt;span&gt; class&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;icon&quot;&lt;/span&gt;&lt;span&gt;&amp;gt;👍&amp;lt;/&lt;/span&gt;&lt;span&gt;span&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    &amp;lt;&lt;/span&gt;&lt;span&gt;span&lt;/span&gt;&lt;span&gt; id&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;upvote-count&quot;&lt;/span&gt;&lt;span&gt;&amp;gt;0&amp;lt;/&lt;/span&gt;&lt;span&gt;span&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  &amp;lt;/&lt;/span&gt;&lt;span&gt;button&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;lt;/&lt;/span&gt;&lt;span&gt;div&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;lt;&lt;/span&gt;&lt;span&gt;script&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;// 配置你的 Worker 域名&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; API_URL&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; &apos;https://your-worker.workers.dev/upvote&apos;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;// 使用文章的唯一标识作为 postId（如 slug、路径等）&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; POST_ID&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; window.location.pathname;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;// 获取点赞数&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;async&lt;/span&gt;&lt;span&gt; function&lt;/span&gt;&lt;span&gt; getUpvoteCount&lt;/span&gt;&lt;span&gt;() {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  try&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    const&lt;/span&gt;&lt;span&gt; response&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; await&lt;/span&gt;&lt;span&gt; fetch&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;`${&lt;/span&gt;&lt;span&gt;API_URL&lt;/span&gt;&lt;span&gt;}?postId=${&lt;/span&gt;&lt;span&gt;encodeURIComponent&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;POST_ID&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;}`&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    const&lt;/span&gt;&lt;span&gt; data&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; await&lt;/span&gt;&lt;span&gt; response.&lt;/span&gt;&lt;span&gt;json&lt;/span&gt;&lt;span&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    document.&lt;/span&gt;&lt;span&gt;getElementById&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&apos;upvote-count&apos;&lt;/span&gt;&lt;span&gt;).textContent &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; data.count &lt;/span&gt;&lt;span&gt;||&lt;/span&gt;&lt;span&gt; 0&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    // 根据用户是否已点赞更新按钮状态&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    const&lt;/span&gt;&lt;span&gt; button&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; document.&lt;/span&gt;&lt;span&gt;getElementById&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&apos;upvote-btn&apos;&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    if&lt;/span&gt;&lt;span&gt; (data.upvoted) {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      button.classList.&lt;/span&gt;&lt;span&gt;add&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&apos;upvoted&apos;&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  } &lt;/span&gt;&lt;span&gt;catch&lt;/span&gt;&lt;span&gt; (error) {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    console.&lt;/span&gt;&lt;span&gt;error&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&apos;获取点赞数失败:&apos;&lt;/span&gt;&lt;span&gt;, error);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;// 点赞/取消点赞&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;async&lt;/span&gt;&lt;span&gt; function&lt;/span&gt;&lt;span&gt; toggleUpvote&lt;/span&gt;&lt;span&gt;() {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  const&lt;/span&gt;&lt;span&gt; button&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; document.&lt;/span&gt;&lt;span&gt;getElementById&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&apos;upvote-btn&apos;&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  button.disabled &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; true&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  try&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    const&lt;/span&gt;&lt;span&gt; response&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; await&lt;/span&gt;&lt;span&gt; fetch&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;API_URL&lt;/span&gt;&lt;span&gt;, {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      method: &lt;/span&gt;&lt;span&gt;&apos;POST&apos;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      headers: {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        &apos;Content-Type&apos;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&apos;application/json&apos;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      },&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      body: &lt;/span&gt;&lt;span&gt;JSON&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;stringify&lt;/span&gt;&lt;span&gt;({ postId: &lt;/span&gt;&lt;span&gt;POST_ID&lt;/span&gt;&lt;span&gt; })&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    });&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    const&lt;/span&gt;&lt;span&gt; data&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; await&lt;/span&gt;&lt;span&gt; response.&lt;/span&gt;&lt;span&gt;json&lt;/span&gt;&lt;span&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    // 更新点赞数&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    document.&lt;/span&gt;&lt;span&gt;getElementById&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&apos;upvote-count&apos;&lt;/span&gt;&lt;span&gt;).textContent &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; data.count &lt;/span&gt;&lt;span&gt;||&lt;/span&gt;&lt;span&gt; 0&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    // 更新按钮状态&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    if&lt;/span&gt;&lt;span&gt; (data.upvoted) {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      button.classList.&lt;/span&gt;&lt;span&gt;add&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&apos;upvoted&apos;&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    } &lt;/span&gt;&lt;span&gt;else&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      button.classList.&lt;/span&gt;&lt;span&gt;remove&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&apos;upvoted&apos;&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  } &lt;/span&gt;&lt;span&gt;catch&lt;/span&gt;&lt;span&gt; (error) {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    console.&lt;/span&gt;&lt;span&gt;error&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&apos;点赞操作失败:&apos;&lt;/span&gt;&lt;span&gt;, error);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  } &lt;/span&gt;&lt;span&gt;finally&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    button.disabled &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; false&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;// 页面加载时获取点赞数&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;getUpvoteCount&lt;/span&gt;&lt;span&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;// 绑定点击事件&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;document.&lt;/span&gt;&lt;span&gt;getElementById&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&apos;upvote-btn&apos;&lt;/span&gt;&lt;span&gt;).&lt;/span&gt;&lt;span&gt;addEventListener&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&apos;click&apos;&lt;/span&gt;&lt;span&gt;, toggleUpvote);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;lt;/&lt;/span&gt;&lt;span&gt;script&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;lt;&lt;/span&gt;&lt;span&gt;style&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;.upvote-button&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  display&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;inline-flex&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  align-items&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;center&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  gap&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;8&lt;/span&gt;&lt;span&gt;px&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  padding&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;8&lt;/span&gt;&lt;span&gt;px&lt;/span&gt;&lt;span&gt; 16&lt;/span&gt;&lt;span&gt;px&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  border&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;2&lt;/span&gt;&lt;span&gt;px&lt;/span&gt;&lt;span&gt; solid&lt;/span&gt;&lt;span&gt; #e5e7eb&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  background&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;white&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  border-radius&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;8&lt;/span&gt;&lt;span&gt;px&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  cursor&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;pointer&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  transition&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;all&lt;/span&gt;&lt;span&gt; 0.2&lt;/span&gt;&lt;span&gt;s&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  font-size&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;16&lt;/span&gt;&lt;span&gt;px&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;.upvote-button:hover&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  border-color&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;#3b82f6&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  background&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;#eff6ff&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;.upvote-button.upvoted&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  border-color&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;#3b82f6&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  background&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;#3b82f6&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  color&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;white&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;.upvote-button:disabled&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  opacity&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;0.6&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  cursor&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;not-allowed&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;lt;/&lt;/span&gt;&lt;span&gt;style&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;框架集成示例&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;React / Next.js&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;import&lt;/span&gt;&lt;span&gt; { useState, useEffect } &lt;/span&gt;&lt;span&gt;from&lt;/span&gt;&lt;span&gt; &apos;react&apos;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;export&lt;/span&gt;&lt;span&gt; default&lt;/span&gt;&lt;span&gt; function&lt;/span&gt;&lt;span&gt; UpvoteButton&lt;/span&gt;&lt;span&gt;({ &lt;/span&gt;&lt;span&gt;postId&lt;/span&gt;&lt;span&gt; }) {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  const&lt;/span&gt;&lt;span&gt; [&lt;/span&gt;&lt;span&gt;count&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;setCount&lt;/span&gt;&lt;span&gt;] &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; useState&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;0&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  const&lt;/span&gt;&lt;span&gt; [&lt;/span&gt;&lt;span&gt;upvoted&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;setUpvoted&lt;/span&gt;&lt;span&gt;] &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; useState&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;false&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  const&lt;/span&gt;&lt;span&gt; [&lt;/span&gt;&lt;span&gt;loading&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;setLoading&lt;/span&gt;&lt;span&gt;] &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; useState&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;false&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  const&lt;/span&gt;&lt;span&gt; API_URL&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; &apos;https://your-worker.workers.dev/upvote&apos;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  useEffect&lt;/span&gt;&lt;span&gt;(() &lt;/span&gt;&lt;span&gt;=&amp;gt;&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    fetchUpvoteData&lt;/span&gt;&lt;span&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  }, [postId]);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  async&lt;/span&gt;&lt;span&gt; function&lt;/span&gt;&lt;span&gt; fetchUpvoteData&lt;/span&gt;&lt;span&gt;() {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    try&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      const&lt;/span&gt;&lt;span&gt; response&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; await&lt;/span&gt;&lt;span&gt; fetch&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;`${&lt;/span&gt;&lt;span&gt;API_URL&lt;/span&gt;&lt;span&gt;}?postId=${&lt;/span&gt;&lt;span&gt;encodeURIComponent&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;postId&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;}`&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      const&lt;/span&gt;&lt;span&gt; data&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; await&lt;/span&gt;&lt;span&gt; response.&lt;/span&gt;&lt;span&gt;json&lt;/span&gt;&lt;span&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      setCount&lt;/span&gt;&lt;span&gt;(data.count &lt;/span&gt;&lt;span&gt;||&lt;/span&gt;&lt;span&gt; 0&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      setUpvoted&lt;/span&gt;&lt;span&gt;(data.upvoted &lt;/span&gt;&lt;span&gt;||&lt;/span&gt;&lt;span&gt; false&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    } &lt;/span&gt;&lt;span&gt;catch&lt;/span&gt;&lt;span&gt; (error) {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      console.&lt;/span&gt;&lt;span&gt;error&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&apos;获取点赞数失败:&apos;&lt;/span&gt;&lt;span&gt;, error);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  async&lt;/span&gt;&lt;span&gt; function&lt;/span&gt;&lt;span&gt; handleUpvote&lt;/span&gt;&lt;span&gt;() {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    setLoading&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;true&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    try&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      const&lt;/span&gt;&lt;span&gt; response&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; await&lt;/span&gt;&lt;span&gt; fetch&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;API_URL&lt;/span&gt;&lt;span&gt;, {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        method: &lt;/span&gt;&lt;span&gt;&apos;POST&apos;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        headers: { &lt;/span&gt;&lt;span&gt;&apos;Content-Type&apos;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&apos;application/json&apos;&lt;/span&gt;&lt;span&gt; },&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        body: &lt;/span&gt;&lt;span&gt;JSON&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;stringify&lt;/span&gt;&lt;span&gt;({ postId })&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      });&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      const&lt;/span&gt;&lt;span&gt; data&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; await&lt;/span&gt;&lt;span&gt; response.&lt;/span&gt;&lt;span&gt;json&lt;/span&gt;&lt;span&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      setCount&lt;/span&gt;&lt;span&gt;(data.count &lt;/span&gt;&lt;span&gt;||&lt;/span&gt;&lt;span&gt; 0&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      setUpvoted&lt;/span&gt;&lt;span&gt;(data.upvoted &lt;/span&gt;&lt;span&gt;||&lt;/span&gt;&lt;span&gt; false&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    } &lt;/span&gt;&lt;span&gt;catch&lt;/span&gt;&lt;span&gt; (error) {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      console.&lt;/span&gt;&lt;span&gt;error&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&apos;点赞操作失败:&apos;&lt;/span&gt;&lt;span&gt;, error);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    } &lt;/span&gt;&lt;span&gt;finally&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      setLoading&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;false&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  return&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    &amp;lt;&lt;/span&gt;&lt;span&gt;button&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      onClick&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;{handleUpvote} &lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      disabled&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;{loading}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      className&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;{upvoted &lt;/span&gt;&lt;span&gt;?&lt;/span&gt;&lt;span&gt; &apos;upvoted&apos;&lt;/span&gt;&lt;span&gt; :&lt;/span&gt;&lt;span&gt; &apos;&apos;&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    &amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      &amp;lt;&lt;/span&gt;&lt;span&gt;span&lt;/span&gt;&lt;span&gt;&amp;gt;👍&amp;lt;/&lt;/span&gt;&lt;span&gt;span&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      &amp;lt;&lt;/span&gt;&lt;span&gt;span&lt;/span&gt;&lt;span&gt;&amp;gt;{count}&amp;lt;/&lt;/span&gt;&lt;span&gt;span&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    &amp;lt;/&lt;/span&gt;&lt;span&gt;button&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  );&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;Vue / Nuxt&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;&amp;lt;&lt;/span&gt;&lt;span&gt;template&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  &amp;lt;&lt;/span&gt;&lt;span&gt;button&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    @click&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;toggleUpvote&quot;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    :disabled&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;loading&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    :class&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;{ upvoted }&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    class&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;upvote-button&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  &amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    &amp;lt;&lt;/span&gt;&lt;span&gt;span&lt;/span&gt;&lt;span&gt;&amp;gt;👍&amp;lt;/&lt;/span&gt;&lt;span&gt;span&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    &amp;lt;&lt;/span&gt;&lt;span&gt;span&lt;/span&gt;&lt;span&gt;&amp;gt;{{ count }}&amp;lt;/&lt;/span&gt;&lt;span&gt;span&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  &amp;lt;/&lt;/span&gt;&lt;span&gt;button&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;lt;/&lt;/span&gt;&lt;span&gt;template&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;lt;&lt;/span&gt;&lt;span&gt;script&lt;/span&gt;&lt;span&gt; setup&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;import&lt;/span&gt;&lt;span&gt; { ref, onMounted } &lt;/span&gt;&lt;span&gt;from&lt;/span&gt;&lt;span&gt; &apos;vue&apos;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; props&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; defineProps&lt;/span&gt;&lt;span&gt;({&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  postId: String&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;});&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; API_URL&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; &apos;https://your-worker.workers.dev/upvote&apos;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; count&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; ref&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;0&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; upvoted&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; ref&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;false&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; loading&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; ref&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;false&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;async&lt;/span&gt;&lt;span&gt; function&lt;/span&gt;&lt;span&gt; fetchUpvoteData&lt;/span&gt;&lt;span&gt;() {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  try&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    const&lt;/span&gt;&lt;span&gt; response&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; await&lt;/span&gt;&lt;span&gt; fetch&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;`${&lt;/span&gt;&lt;span&gt;API_URL&lt;/span&gt;&lt;span&gt;}?postId=${&lt;/span&gt;&lt;span&gt;encodeURIComponent&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;props&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;postId&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;}`&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    const&lt;/span&gt;&lt;span&gt; data&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; await&lt;/span&gt;&lt;span&gt; response.&lt;/span&gt;&lt;span&gt;json&lt;/span&gt;&lt;span&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    count.value &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; data.count &lt;/span&gt;&lt;span&gt;||&lt;/span&gt;&lt;span&gt; 0&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    upvoted.value &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; data.upvoted &lt;/span&gt;&lt;span&gt;||&lt;/span&gt;&lt;span&gt; false&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  } &lt;/span&gt;&lt;span&gt;catch&lt;/span&gt;&lt;span&gt; (error) {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    console.&lt;/span&gt;&lt;span&gt;error&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&apos;获取点赞数失败:&apos;&lt;/span&gt;&lt;span&gt;, error);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;async&lt;/span&gt;&lt;span&gt; function&lt;/span&gt;&lt;span&gt; toggleUpvote&lt;/span&gt;&lt;span&gt;() {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  loading.value &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; true&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  try&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    const&lt;/span&gt;&lt;span&gt; response&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; await&lt;/span&gt;&lt;span&gt; fetch&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;API_URL&lt;/span&gt;&lt;span&gt;, {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      method: &lt;/span&gt;&lt;span&gt;&apos;POST&apos;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      headers: { &lt;/span&gt;&lt;span&gt;&apos;Content-Type&apos;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&apos;application/json&apos;&lt;/span&gt;&lt;span&gt; },&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      body: &lt;/span&gt;&lt;span&gt;JSON&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;stringify&lt;/span&gt;&lt;span&gt;({ postId: props.postId })&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    });&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    const&lt;/span&gt;&lt;span&gt; data&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; await&lt;/span&gt;&lt;span&gt; response.&lt;/span&gt;&lt;span&gt;json&lt;/span&gt;&lt;span&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    count.value &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; data.count &lt;/span&gt;&lt;span&gt;||&lt;/span&gt;&lt;span&gt; 0&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    upvoted.value &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; data.upvoted &lt;/span&gt;&lt;span&gt;||&lt;/span&gt;&lt;span&gt; false&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  } &lt;/span&gt;&lt;span&gt;catch&lt;/span&gt;&lt;span&gt; (error) {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    console.&lt;/span&gt;&lt;span&gt;error&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&apos;点赞操作失败:&apos;&lt;/span&gt;&lt;span&gt;, error);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  } &lt;/span&gt;&lt;span&gt;finally&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    loading.value &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; false&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;onMounted&lt;/span&gt;&lt;span&gt;(() &lt;/span&gt;&lt;span&gt;=&amp;gt;&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  fetchUpvoteData&lt;/span&gt;&lt;span&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;});&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;lt;/&lt;/span&gt;&lt;span&gt;script&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;如何在 hugo 中启用 Upvote 功能？&lt;/h2&gt;
&lt;p&gt;详见 hugo-bearneo 提供的 &lt;a href=&quot;https://github.com/rokcso/hugo-bearneo/&quot;&gt;使用指南&lt;/a&gt;。&lt;/p&gt;
&lt;h3&gt;自定义域名配置&lt;/h3&gt;
&lt;p&gt;如果你想使用自定义域名（推荐），可以在 Cloudflare Workers 的 Settings -&amp;gt; Domains &amp;amp; Routes 中添加自定义域名，这样可以避免 workers.dev 域名在国内访问不畅的问题。&lt;/p&gt;
&lt;h2&gt;扩展功能：添加访问统计&lt;/h2&gt;
&lt;p&gt;在点赞功能的基础上，我们可以轻松添加访问统计功能，无需创建新的 KV namespace。&lt;/p&gt;
&lt;h3&gt;更新 Worker 代码&lt;/h3&gt;
&lt;p&gt;将以下完整代码替换到你的 Worker 中（包含点赞 + 访问统计）：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;// Cloudflare Worker - 点赞 + 访问统计&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;addEventListener&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&apos;fetch&apos;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;event&lt;/span&gt;&lt;span&gt; =&amp;gt;&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  event.&lt;/span&gt;&lt;span&gt;respondWith&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;handleRequest&lt;/span&gt;&lt;span&gt;(event.request))&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;})&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;async&lt;/span&gt;&lt;span&gt; function&lt;/span&gt;&lt;span&gt; handleRequest&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;request&lt;/span&gt;&lt;span&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  const&lt;/span&gt;&lt;span&gt; url&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; new&lt;/span&gt;&lt;span&gt; URL&lt;/span&gt;&lt;span&gt;(request.url)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  const&lt;/span&gt;&lt;span&gt; path&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; url.pathname&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  // CORS 头&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  const&lt;/span&gt;&lt;span&gt; corsHeaders&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    &apos;Access-Control-Allow-Origin&apos;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&apos;*&apos;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    &apos;Access-Control-Allow-Methods&apos;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&apos;GET, POST, OPTIONS&apos;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    &apos;Access-Control-Allow-Headers&apos;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&apos;Content-Type&apos;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  // 处理 OPTIONS 请求&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  if&lt;/span&gt;&lt;span&gt; (request.method &lt;/span&gt;&lt;span&gt;===&lt;/span&gt;&lt;span&gt; &apos;OPTIONS&apos;&lt;/span&gt;&lt;span&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    return&lt;/span&gt;&lt;span&gt; new&lt;/span&gt;&lt;span&gt; Response&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;null&lt;/span&gt;&lt;span&gt;, { headers: corsHeaders })&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  // 路由处理&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  if&lt;/span&gt;&lt;span&gt; (path &lt;/span&gt;&lt;span&gt;===&lt;/span&gt;&lt;span&gt; &apos;/like&apos;&lt;/span&gt;&lt;span&gt; ||&lt;/span&gt;&lt;span&gt; path &lt;/span&gt;&lt;span&gt;===&lt;/span&gt;&lt;span&gt; &apos;/upvote&apos;&lt;/span&gt;&lt;span&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    return&lt;/span&gt;&lt;span&gt; handleLike&lt;/span&gt;&lt;span&gt;(request, corsHeaders)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  } &lt;/span&gt;&lt;span&gt;else&lt;/span&gt;&lt;span&gt; if&lt;/span&gt;&lt;span&gt; (path &lt;/span&gt;&lt;span&gt;===&lt;/span&gt;&lt;span&gt; &apos;/count&apos;&lt;/span&gt;&lt;span&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    return&lt;/span&gt;&lt;span&gt; handleGetCount&lt;/span&gt;&lt;span&gt;(request, corsHeaders)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  } &lt;/span&gt;&lt;span&gt;else&lt;/span&gt;&lt;span&gt; if&lt;/span&gt;&lt;span&gt; (path &lt;/span&gt;&lt;span&gt;===&lt;/span&gt;&lt;span&gt; &apos;/view&apos;&lt;/span&gt;&lt;span&gt; ||&lt;/span&gt;&lt;span&gt; path &lt;/span&gt;&lt;span&gt;===&lt;/span&gt;&lt;span&gt; &apos;/pageview&apos;&lt;/span&gt;&lt;span&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    return&lt;/span&gt;&lt;span&gt; handlePageView&lt;/span&gt;&lt;span&gt;(request, corsHeaders)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  } &lt;/span&gt;&lt;span&gt;else&lt;/span&gt;&lt;span&gt; if&lt;/span&gt;&lt;span&gt; (path &lt;/span&gt;&lt;span&gt;===&lt;/span&gt;&lt;span&gt; &apos;/stats&apos;&lt;/span&gt;&lt;span&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    return&lt;/span&gt;&lt;span&gt; handleGetStats&lt;/span&gt;&lt;span&gt;(request, corsHeaders)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  } &lt;/span&gt;&lt;span&gt;else&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    return&lt;/span&gt;&lt;span&gt; new&lt;/span&gt;&lt;span&gt; Response&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;JSON&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;stringify&lt;/span&gt;&lt;span&gt;({&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      code: &lt;/span&gt;&lt;span&gt;0&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      message: &lt;/span&gt;&lt;span&gt;&apos;API is running&apos;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      endpoints: {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        like: &lt;/span&gt;&lt;span&gt;&apos;POST /like - 点赞/取消点赞&apos;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        count: &lt;/span&gt;&lt;span&gt;&apos;GET /count?post=xxx - 获取点赞数&apos;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        view: &lt;/span&gt;&lt;span&gt;&apos;POST /view - 记录访问&apos;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        stats: &lt;/span&gt;&lt;span&gt;&apos;GET /stats?post=xxx - 获取统计数据（点赞+访问）&apos;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    }), {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      headers: { &lt;/span&gt;&lt;span&gt;...&lt;/span&gt;&lt;span&gt;corsHeaders, &lt;/span&gt;&lt;span&gt;&apos;Content-Type&apos;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&apos;application/json&apos;&lt;/span&gt;&lt;span&gt; }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    })&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;// 处理点赞&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;async&lt;/span&gt;&lt;span&gt; function&lt;/span&gt;&lt;span&gt; handleLike&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;request&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;corsHeaders&lt;/span&gt;&lt;span&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  if&lt;/span&gt;&lt;span&gt; (request.method &lt;/span&gt;&lt;span&gt;!==&lt;/span&gt;&lt;span&gt; &apos;POST&apos;&lt;/span&gt;&lt;span&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    return&lt;/span&gt;&lt;span&gt; new&lt;/span&gt;&lt;span&gt; Response&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;JSON&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;stringify&lt;/span&gt;&lt;span&gt;({ code: &lt;/span&gt;&lt;span&gt;1&lt;/span&gt;&lt;span&gt;, message: &lt;/span&gt;&lt;span&gt;&apos;Method not allowed&apos;&lt;/span&gt;&lt;span&gt; }), {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      status: &lt;/span&gt;&lt;span&gt;405&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      headers: { &lt;/span&gt;&lt;span&gt;...&lt;/span&gt;&lt;span&gt;corsHeaders, &lt;/span&gt;&lt;span&gt;&apos;Content-Type&apos;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&apos;application/json&apos;&lt;/span&gt;&lt;span&gt; }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    })&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  try&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    const&lt;/span&gt;&lt;span&gt; body&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; await&lt;/span&gt;&lt;span&gt; request.&lt;/span&gt;&lt;span&gt;json&lt;/span&gt;&lt;span&gt;()&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    const&lt;/span&gt;&lt;span&gt; postId&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; body.postId &lt;/span&gt;&lt;span&gt;||&lt;/span&gt;&lt;span&gt; body.post&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    if&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;!&lt;/span&gt;&lt;span&gt;postId) {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      return&lt;/span&gt;&lt;span&gt; new&lt;/span&gt;&lt;span&gt; Response&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;JSON&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;stringify&lt;/span&gt;&lt;span&gt;({ code: &lt;/span&gt;&lt;span&gt;1&lt;/span&gt;&lt;span&gt;, message: &lt;/span&gt;&lt;span&gt;&apos;Missing postId&apos;&lt;/span&gt;&lt;span&gt; }), {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        status: &lt;/span&gt;&lt;span&gt;400&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        headers: { &lt;/span&gt;&lt;span&gt;...&lt;/span&gt;&lt;span&gt;corsHeaders, &lt;/span&gt;&lt;span&gt;&apos;Content-Type&apos;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&apos;application/json&apos;&lt;/span&gt;&lt;span&gt; }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      })&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    // 获取用户标识（IP + User-Agent）&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    const&lt;/span&gt;&lt;span&gt; ip&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; request.headers.&lt;/span&gt;&lt;span&gt;get&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&apos;CF-Connecting-IP&apos;&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;span&gt;||&lt;/span&gt;&lt;span&gt; &apos;unknown&apos;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    const&lt;/span&gt;&lt;span&gt; userAgent&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; request.headers.&lt;/span&gt;&lt;span&gt;get&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&apos;User-Agent&apos;&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;span&gt;||&lt;/span&gt;&lt;span&gt; &apos;unknown&apos;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    const&lt;/span&gt;&lt;span&gt; userId&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; await&lt;/span&gt;&lt;span&gt; hashString&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;`${&lt;/span&gt;&lt;span&gt;ip&lt;/span&gt;&lt;span&gt;}-${&lt;/span&gt;&lt;span&gt;userAgent&lt;/span&gt;&lt;span&gt;}`&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    // 检查是否已点赞&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    const&lt;/span&gt;&lt;span&gt; recordKey&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; `like:${&lt;/span&gt;&lt;span&gt;postId&lt;/span&gt;&lt;span&gt;}:${&lt;/span&gt;&lt;span&gt;userId&lt;/span&gt;&lt;span&gt;}`&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    const&lt;/span&gt;&lt;span&gt; hasLiked&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; await&lt;/span&gt;&lt;span&gt; UPVOTE_RECORD&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;get&lt;/span&gt;&lt;span&gt;(recordKey)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    let&lt;/span&gt;&lt;span&gt; newCount&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    if&lt;/span&gt;&lt;span&gt; (hasLiked) {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      // 取消点赞&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      await&lt;/span&gt;&lt;span&gt; UPVOTE_RECORD&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;delete&lt;/span&gt;&lt;span&gt;(recordKey)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      const&lt;/span&gt;&lt;span&gt; countKey&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; `like:count:${&lt;/span&gt;&lt;span&gt;postId&lt;/span&gt;&lt;span&gt;}`&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      const&lt;/span&gt;&lt;span&gt; currentCount&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; parseInt&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;await&lt;/span&gt;&lt;span&gt; UPVOTE_COUNT&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;get&lt;/span&gt;&lt;span&gt;(countKey) &lt;/span&gt;&lt;span&gt;||&lt;/span&gt;&lt;span&gt; &apos;0&apos;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      newCount &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; Math.&lt;/span&gt;&lt;span&gt;max&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;0&lt;/span&gt;&lt;span&gt;, currentCount &lt;/span&gt;&lt;span&gt;-&lt;/span&gt;&lt;span&gt; 1&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      await&lt;/span&gt;&lt;span&gt; UPVOTE_COUNT&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;put&lt;/span&gt;&lt;span&gt;(countKey, newCount.&lt;/span&gt;&lt;span&gt;toString&lt;/span&gt;&lt;span&gt;())&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    } &lt;/span&gt;&lt;span&gt;else&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      // 点赞&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      await&lt;/span&gt;&lt;span&gt; UPVOTE_RECORD&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;put&lt;/span&gt;&lt;span&gt;(recordKey, &lt;/span&gt;&lt;span&gt;&apos;1&apos;&lt;/span&gt;&lt;span&gt;, { expirationTtl: &lt;/span&gt;&lt;span&gt;31536000&lt;/span&gt;&lt;span&gt; })&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      const&lt;/span&gt;&lt;span&gt; countKey&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; `like:count:${&lt;/span&gt;&lt;span&gt;postId&lt;/span&gt;&lt;span&gt;}`&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      const&lt;/span&gt;&lt;span&gt; currentCount&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; parseInt&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;await&lt;/span&gt;&lt;span&gt; UPVOTE_COUNT&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;get&lt;/span&gt;&lt;span&gt;(countKey) &lt;/span&gt;&lt;span&gt;||&lt;/span&gt;&lt;span&gt; &apos;0&apos;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      newCount &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; currentCount &lt;/span&gt;&lt;span&gt;+&lt;/span&gt;&lt;span&gt; 1&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      await&lt;/span&gt;&lt;span&gt; UPVOTE_COUNT&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;put&lt;/span&gt;&lt;span&gt;(countKey, newCount.&lt;/span&gt;&lt;span&gt;toString&lt;/span&gt;&lt;span&gt;())&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    return&lt;/span&gt;&lt;span&gt; new&lt;/span&gt;&lt;span&gt; Response&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;JSON&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;stringify&lt;/span&gt;&lt;span&gt;({&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      code: &lt;/span&gt;&lt;span&gt;0&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      data: {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        count: newCount,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        hasLiked: &lt;/span&gt;&lt;span&gt;!&lt;/span&gt;&lt;span&gt;hasLiked&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    }), {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      headers: { &lt;/span&gt;&lt;span&gt;...&lt;/span&gt;&lt;span&gt;corsHeaders, &lt;/span&gt;&lt;span&gt;&apos;Content-Type&apos;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&apos;application/json&apos;&lt;/span&gt;&lt;span&gt; }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    })&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  } &lt;/span&gt;&lt;span&gt;catch&lt;/span&gt;&lt;span&gt; (error) {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    return&lt;/span&gt;&lt;span&gt; new&lt;/span&gt;&lt;span&gt; Response&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;JSON&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;stringify&lt;/span&gt;&lt;span&gt;({ code: &lt;/span&gt;&lt;span&gt;1&lt;/span&gt;&lt;span&gt;, message: error.message }), {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      status: &lt;/span&gt;&lt;span&gt;500&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      headers: { &lt;/span&gt;&lt;span&gt;...&lt;/span&gt;&lt;span&gt;corsHeaders, &lt;/span&gt;&lt;span&gt;&apos;Content-Type&apos;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&apos;application/json&apos;&lt;/span&gt;&lt;span&gt; }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    })&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;// 获取点赞数&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;async&lt;/span&gt;&lt;span&gt; function&lt;/span&gt;&lt;span&gt; handleGetCount&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;request&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;corsHeaders&lt;/span&gt;&lt;span&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  const&lt;/span&gt;&lt;span&gt; url&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; new&lt;/span&gt;&lt;span&gt; URL&lt;/span&gt;&lt;span&gt;(request.url)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  const&lt;/span&gt;&lt;span&gt; postId&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; url.searchParams.&lt;/span&gt;&lt;span&gt;get&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&apos;post&apos;&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;span&gt;||&lt;/span&gt;&lt;span&gt; url.searchParams.&lt;/span&gt;&lt;span&gt;get&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&apos;postId&apos;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  if&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;!&lt;/span&gt;&lt;span&gt;postId) {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    return&lt;/span&gt;&lt;span&gt; new&lt;/span&gt;&lt;span&gt; Response&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;JSON&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;stringify&lt;/span&gt;&lt;span&gt;({ code: &lt;/span&gt;&lt;span&gt;1&lt;/span&gt;&lt;span&gt;, message: &lt;/span&gt;&lt;span&gt;&apos;Missing post parameter&apos;&lt;/span&gt;&lt;span&gt; }), {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      status: &lt;/span&gt;&lt;span&gt;400&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      headers: { &lt;/span&gt;&lt;span&gt;...&lt;/span&gt;&lt;span&gt;corsHeaders, &lt;/span&gt;&lt;span&gt;&apos;Content-Type&apos;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&apos;application/json&apos;&lt;/span&gt;&lt;span&gt; }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    })&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  try&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    const&lt;/span&gt;&lt;span&gt; countKey&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; `like:count:${&lt;/span&gt;&lt;span&gt;postId&lt;/span&gt;&lt;span&gt;}`&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    const&lt;/span&gt;&lt;span&gt; count&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; parseInt&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;await&lt;/span&gt;&lt;span&gt; UPVOTE_COUNT&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;get&lt;/span&gt;&lt;span&gt;(countKey) &lt;/span&gt;&lt;span&gt;||&lt;/span&gt;&lt;span&gt; &apos;0&apos;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    const&lt;/span&gt;&lt;span&gt; ip&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; request.headers.&lt;/span&gt;&lt;span&gt;get&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&apos;CF-Connecting-IP&apos;&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;span&gt;||&lt;/span&gt;&lt;span&gt; &apos;unknown&apos;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    const&lt;/span&gt;&lt;span&gt; userAgent&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; request.headers.&lt;/span&gt;&lt;span&gt;get&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&apos;User-Agent&apos;&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;span&gt;||&lt;/span&gt;&lt;span&gt; &apos;unknown&apos;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    const&lt;/span&gt;&lt;span&gt; userId&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; await&lt;/span&gt;&lt;span&gt; hashString&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;`${&lt;/span&gt;&lt;span&gt;ip&lt;/span&gt;&lt;span&gt;}-${&lt;/span&gt;&lt;span&gt;userAgent&lt;/span&gt;&lt;span&gt;}`&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    const&lt;/span&gt;&lt;span&gt; recordKey&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; `like:${&lt;/span&gt;&lt;span&gt;postId&lt;/span&gt;&lt;span&gt;}:${&lt;/span&gt;&lt;span&gt;userId&lt;/span&gt;&lt;span&gt;}`&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    const&lt;/span&gt;&lt;span&gt; hasLiked&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; !!&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;await&lt;/span&gt;&lt;span&gt; UPVOTE_RECORD&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;get&lt;/span&gt;&lt;span&gt;(recordKey))&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    return&lt;/span&gt;&lt;span&gt; new&lt;/span&gt;&lt;span&gt; Response&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;JSON&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;stringify&lt;/span&gt;&lt;span&gt;({&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      code: &lt;/span&gt;&lt;span&gt;0&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      data: { count, hasLiked }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    }), {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      headers: { &lt;/span&gt;&lt;span&gt;...&lt;/span&gt;&lt;span&gt;corsHeaders, &lt;/span&gt;&lt;span&gt;&apos;Content-Type&apos;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&apos;application/json&apos;&lt;/span&gt;&lt;span&gt; }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    })&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  } &lt;/span&gt;&lt;span&gt;catch&lt;/span&gt;&lt;span&gt; (error) {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    return&lt;/span&gt;&lt;span&gt; new&lt;/span&gt;&lt;span&gt; Response&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;JSON&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;stringify&lt;/span&gt;&lt;span&gt;({ code: &lt;/span&gt;&lt;span&gt;1&lt;/span&gt;&lt;span&gt;, message: error.message }), {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      status: &lt;/span&gt;&lt;span&gt;500&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      headers: { &lt;/span&gt;&lt;span&gt;...&lt;/span&gt;&lt;span&gt;corsHeaders, &lt;/span&gt;&lt;span&gt;&apos;Content-Type&apos;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&apos;application/json&apos;&lt;/span&gt;&lt;span&gt; }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    })&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;// 记录页面访问&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;async&lt;/span&gt;&lt;span&gt; function&lt;/span&gt;&lt;span&gt; handlePageView&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;request&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;corsHeaders&lt;/span&gt;&lt;span&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  if&lt;/span&gt;&lt;span&gt; (request.method &lt;/span&gt;&lt;span&gt;!==&lt;/span&gt;&lt;span&gt; &apos;POST&apos;&lt;/span&gt;&lt;span&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    return&lt;/span&gt;&lt;span&gt; new&lt;/span&gt;&lt;span&gt; Response&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;JSON&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;stringify&lt;/span&gt;&lt;span&gt;({ code: &lt;/span&gt;&lt;span&gt;1&lt;/span&gt;&lt;span&gt;, message: &lt;/span&gt;&lt;span&gt;&apos;Method not allowed&apos;&lt;/span&gt;&lt;span&gt; }), {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      status: &lt;/span&gt;&lt;span&gt;405&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      headers: { &lt;/span&gt;&lt;span&gt;...&lt;/span&gt;&lt;span&gt;corsHeaders, &lt;/span&gt;&lt;span&gt;&apos;Content-Type&apos;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&apos;application/json&apos;&lt;/span&gt;&lt;span&gt; }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    })&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  try&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    const&lt;/span&gt;&lt;span&gt; body&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; await&lt;/span&gt;&lt;span&gt; request.&lt;/span&gt;&lt;span&gt;json&lt;/span&gt;&lt;span&gt;()&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    const&lt;/span&gt;&lt;span&gt; postId&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; body.postId &lt;/span&gt;&lt;span&gt;||&lt;/span&gt;&lt;span&gt; body.post&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    if&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;!&lt;/span&gt;&lt;span&gt;postId) {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      return&lt;/span&gt;&lt;span&gt; new&lt;/span&gt;&lt;span&gt; Response&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;JSON&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;stringify&lt;/span&gt;&lt;span&gt;({ code: &lt;/span&gt;&lt;span&gt;1&lt;/span&gt;&lt;span&gt;, message: &lt;/span&gt;&lt;span&gt;&apos;Missing postId&apos;&lt;/span&gt;&lt;span&gt; }), {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        status: &lt;/span&gt;&lt;span&gt;400&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        headers: { &lt;/span&gt;&lt;span&gt;...&lt;/span&gt;&lt;span&gt;corsHeaders, &lt;/span&gt;&lt;span&gt;&apos;Content-Type&apos;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&apos;application/json&apos;&lt;/span&gt;&lt;span&gt; }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      })&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    const&lt;/span&gt;&lt;span&gt; ip&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; request.headers.&lt;/span&gt;&lt;span&gt;get&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&apos;CF-Connecting-IP&apos;&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;span&gt;||&lt;/span&gt;&lt;span&gt; &apos;unknown&apos;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    const&lt;/span&gt;&lt;span&gt; userAgent&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; request.headers.&lt;/span&gt;&lt;span&gt;get&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&apos;User-Agent&apos;&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;span&gt;||&lt;/span&gt;&lt;span&gt; &apos;unknown&apos;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    const&lt;/span&gt;&lt;span&gt; userId&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; await&lt;/span&gt;&lt;span&gt; hashString&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;`${&lt;/span&gt;&lt;span&gt;ip&lt;/span&gt;&lt;span&gt;}-${&lt;/span&gt;&lt;span&gt;userAgent&lt;/span&gt;&lt;span&gt;}`&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    // 检查是否在5分钟内重复访问（防刷）&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    const&lt;/span&gt;&lt;span&gt; viewRecordKey&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; `view:record:${&lt;/span&gt;&lt;span&gt;postId&lt;/span&gt;&lt;span&gt;}:${&lt;/span&gt;&lt;span&gt;userId&lt;/span&gt;&lt;span&gt;}`&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    const&lt;/span&gt;&lt;span&gt; recentView&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; await&lt;/span&gt;&lt;span&gt; UPVOTE_RECORD&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;get&lt;/span&gt;&lt;span&gt;(viewRecordKey)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    if&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;!&lt;/span&gt;&lt;span&gt;recentView) {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      // 记录访问（5分钟内不重复计数）&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      await&lt;/span&gt;&lt;span&gt; UPVOTE_RECORD&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;put&lt;/span&gt;&lt;span&gt;(viewRecordKey, &lt;/span&gt;&lt;span&gt;&apos;1&apos;&lt;/span&gt;&lt;span&gt;, { expirationTtl: &lt;/span&gt;&lt;span&gt;300&lt;/span&gt;&lt;span&gt; })&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      &lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      // 增加访问计数&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      const&lt;/span&gt;&lt;span&gt; countKey&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; `view:count:${&lt;/span&gt;&lt;span&gt;postId&lt;/span&gt;&lt;span&gt;}`&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      const&lt;/span&gt;&lt;span&gt; currentCount&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; parseInt&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;await&lt;/span&gt;&lt;span&gt; UPVOTE_COUNT&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;get&lt;/span&gt;&lt;span&gt;(countKey) &lt;/span&gt;&lt;span&gt;||&lt;/span&gt;&lt;span&gt; &apos;0&apos;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      const&lt;/span&gt;&lt;span&gt; newCount&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; currentCount &lt;/span&gt;&lt;span&gt;+&lt;/span&gt;&lt;span&gt; 1&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      await&lt;/span&gt;&lt;span&gt; UPVOTE_COUNT&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;put&lt;/span&gt;&lt;span&gt;(countKey, newCount.&lt;/span&gt;&lt;span&gt;toString&lt;/span&gt;&lt;span&gt;())&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      return&lt;/span&gt;&lt;span&gt; new&lt;/span&gt;&lt;span&gt; Response&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;JSON&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;stringify&lt;/span&gt;&lt;span&gt;({&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        code: &lt;/span&gt;&lt;span&gt;0&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        data: { views: newCount, counted: &lt;/span&gt;&lt;span&gt;true&lt;/span&gt;&lt;span&gt; }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      }), {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        headers: { &lt;/span&gt;&lt;span&gt;...&lt;/span&gt;&lt;span&gt;corsHeaders, &lt;/span&gt;&lt;span&gt;&apos;Content-Type&apos;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&apos;application/json&apos;&lt;/span&gt;&lt;span&gt; }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      })&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    } &lt;/span&gt;&lt;span&gt;else&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      const&lt;/span&gt;&lt;span&gt; countKey&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; `view:count:${&lt;/span&gt;&lt;span&gt;postId&lt;/span&gt;&lt;span&gt;}`&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      const&lt;/span&gt;&lt;span&gt; currentCount&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; parseInt&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;await&lt;/span&gt;&lt;span&gt; UPVOTE_COUNT&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;get&lt;/span&gt;&lt;span&gt;(countKey) &lt;/span&gt;&lt;span&gt;||&lt;/span&gt;&lt;span&gt; &apos;0&apos;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      &lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      return&lt;/span&gt;&lt;span&gt; new&lt;/span&gt;&lt;span&gt; Response&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;JSON&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;stringify&lt;/span&gt;&lt;span&gt;({&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        code: &lt;/span&gt;&lt;span&gt;0&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        data: { views: currentCount, counted: &lt;/span&gt;&lt;span&gt;false&lt;/span&gt;&lt;span&gt; }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      }), {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        headers: { &lt;/span&gt;&lt;span&gt;...&lt;/span&gt;&lt;span&gt;corsHeaders, &lt;/span&gt;&lt;span&gt;&apos;Content-Type&apos;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&apos;application/json&apos;&lt;/span&gt;&lt;span&gt; }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      })&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  } &lt;/span&gt;&lt;span&gt;catch&lt;/span&gt;&lt;span&gt; (error) {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    return&lt;/span&gt;&lt;span&gt; new&lt;/span&gt;&lt;span&gt; Response&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;JSON&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;stringify&lt;/span&gt;&lt;span&gt;({ code: &lt;/span&gt;&lt;span&gt;1&lt;/span&gt;&lt;span&gt;, message: error.message }), {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      status: &lt;/span&gt;&lt;span&gt;500&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      headers: { &lt;/span&gt;&lt;span&gt;...&lt;/span&gt;&lt;span&gt;corsHeaders, &lt;/span&gt;&lt;span&gt;&apos;Content-Type&apos;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&apos;application/json&apos;&lt;/span&gt;&lt;span&gt; }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    })&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;// 获取统计数据（点赞 + 访问）&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;async&lt;/span&gt;&lt;span&gt; function&lt;/span&gt;&lt;span&gt; handleGetStats&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;request&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;corsHeaders&lt;/span&gt;&lt;span&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  const&lt;/span&gt;&lt;span&gt; url&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; new&lt;/span&gt;&lt;span&gt; URL&lt;/span&gt;&lt;span&gt;(request.url)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  const&lt;/span&gt;&lt;span&gt; postId&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; url.searchParams.&lt;/span&gt;&lt;span&gt;get&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&apos;post&apos;&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;span&gt;||&lt;/span&gt;&lt;span&gt; url.searchParams.&lt;/span&gt;&lt;span&gt;get&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&apos;postId&apos;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  if&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;!&lt;/span&gt;&lt;span&gt;postId) {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    return&lt;/span&gt;&lt;span&gt; new&lt;/span&gt;&lt;span&gt; Response&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;JSON&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;stringify&lt;/span&gt;&lt;span&gt;({ code: &lt;/span&gt;&lt;span&gt;1&lt;/span&gt;&lt;span&gt;, message: &lt;/span&gt;&lt;span&gt;&apos;Missing post parameter&apos;&lt;/span&gt;&lt;span&gt; }), {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      status: &lt;/span&gt;&lt;span&gt;400&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      headers: { &lt;/span&gt;&lt;span&gt;...&lt;/span&gt;&lt;span&gt;corsHeaders, &lt;/span&gt;&lt;span&gt;&apos;Content-Type&apos;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&apos;application/json&apos;&lt;/span&gt;&lt;span&gt; }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    })&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  try&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    const&lt;/span&gt;&lt;span&gt; likeCountKey&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; `like:count:${&lt;/span&gt;&lt;span&gt;postId&lt;/span&gt;&lt;span&gt;}`&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    const&lt;/span&gt;&lt;span&gt; likeCount&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; parseInt&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;await&lt;/span&gt;&lt;span&gt; UPVOTE_COUNT&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;get&lt;/span&gt;&lt;span&gt;(likeCountKey) &lt;/span&gt;&lt;span&gt;||&lt;/span&gt;&lt;span&gt; &apos;0&apos;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    const&lt;/span&gt;&lt;span&gt; viewCountKey&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; `view:count:${&lt;/span&gt;&lt;span&gt;postId&lt;/span&gt;&lt;span&gt;}`&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    const&lt;/span&gt;&lt;span&gt; viewCount&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; parseInt&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;await&lt;/span&gt;&lt;span&gt; UPVOTE_COUNT&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;get&lt;/span&gt;&lt;span&gt;(viewCountKey) &lt;/span&gt;&lt;span&gt;||&lt;/span&gt;&lt;span&gt; &apos;0&apos;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    const&lt;/span&gt;&lt;span&gt; ip&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; request.headers.&lt;/span&gt;&lt;span&gt;get&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&apos;CF-Connecting-IP&apos;&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;span&gt;||&lt;/span&gt;&lt;span&gt; &apos;unknown&apos;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    const&lt;/span&gt;&lt;span&gt; userAgent&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; request.headers.&lt;/span&gt;&lt;span&gt;get&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&apos;User-Agent&apos;&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;span&gt;||&lt;/span&gt;&lt;span&gt; &apos;unknown&apos;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    const&lt;/span&gt;&lt;span&gt; userId&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; await&lt;/span&gt;&lt;span&gt; hashString&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;`${&lt;/span&gt;&lt;span&gt;ip&lt;/span&gt;&lt;span&gt;}-${&lt;/span&gt;&lt;span&gt;userAgent&lt;/span&gt;&lt;span&gt;}`&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    const&lt;/span&gt;&lt;span&gt; recordKey&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; `like:${&lt;/span&gt;&lt;span&gt;postId&lt;/span&gt;&lt;span&gt;}:${&lt;/span&gt;&lt;span&gt;userId&lt;/span&gt;&lt;span&gt;}`&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    const&lt;/span&gt;&lt;span&gt; hasLiked&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; !!&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;await&lt;/span&gt;&lt;span&gt; UPVOTE_RECORD&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;get&lt;/span&gt;&lt;span&gt;(recordKey))&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    return&lt;/span&gt;&lt;span&gt; new&lt;/span&gt;&lt;span&gt; Response&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;JSON&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;stringify&lt;/span&gt;&lt;span&gt;({&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      code: &lt;/span&gt;&lt;span&gt;0&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      data: { likes: likeCount, views: viewCount, hasLiked }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    }), {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      headers: { &lt;/span&gt;&lt;span&gt;...&lt;/span&gt;&lt;span&gt;corsHeaders, &lt;/span&gt;&lt;span&gt;&apos;Content-Type&apos;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&apos;application/json&apos;&lt;/span&gt;&lt;span&gt; }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    })&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  } &lt;/span&gt;&lt;span&gt;catch&lt;/span&gt;&lt;span&gt; (error) {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    return&lt;/span&gt;&lt;span&gt; new&lt;/span&gt;&lt;span&gt; Response&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;JSON&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;stringify&lt;/span&gt;&lt;span&gt;({ code: &lt;/span&gt;&lt;span&gt;1&lt;/span&gt;&lt;span&gt;, message: error.message }), {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      status: &lt;/span&gt;&lt;span&gt;500&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      headers: { &lt;/span&gt;&lt;span&gt;...&lt;/span&gt;&lt;span&gt;corsHeaders, &lt;/span&gt;&lt;span&gt;&apos;Content-Type&apos;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&apos;application/json&apos;&lt;/span&gt;&lt;span&gt; }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    })&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;// 哈希函数&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;async&lt;/span&gt;&lt;span&gt; function&lt;/span&gt;&lt;span&gt; hashString&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;str&lt;/span&gt;&lt;span&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  const&lt;/span&gt;&lt;span&gt; encoder&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; new&lt;/span&gt;&lt;span&gt; TextEncoder&lt;/span&gt;&lt;span&gt;()&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  const&lt;/span&gt;&lt;span&gt; data&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; encoder.&lt;/span&gt;&lt;span&gt;encode&lt;/span&gt;&lt;span&gt;(str)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  const&lt;/span&gt;&lt;span&gt; hashBuffer&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; await&lt;/span&gt;&lt;span&gt; crypto.subtle.&lt;/span&gt;&lt;span&gt;digest&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&apos;SHA-256&apos;&lt;/span&gt;&lt;span&gt;, data)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  const&lt;/span&gt;&lt;span&gt; hashArray&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; Array.&lt;/span&gt;&lt;span&gt;from&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;new&lt;/span&gt;&lt;span&gt; Uint8Array&lt;/span&gt;&lt;span&gt;(hashBuffer))&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  return&lt;/span&gt;&lt;span&gt; hashArray.&lt;/span&gt;&lt;span&gt;map&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;b&lt;/span&gt;&lt;span&gt; =&amp;gt;&lt;/span&gt;&lt;span&gt; b.&lt;/span&gt;&lt;span&gt;toString&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;16&lt;/span&gt;&lt;span&gt;).&lt;/span&gt;&lt;span&gt;padStart&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;2&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;&apos;0&apos;&lt;/span&gt;&lt;span&gt;)).&lt;/span&gt;&lt;span&gt;join&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&apos;&apos;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;API 接口说明&lt;/h3&gt;
&lt;p&gt;更新后的 API 提供以下接口：&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;1. 点赞/取消点赞&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;POST&lt;/span&gt;&lt;span&gt; /like&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;Content-Type:&lt;/span&gt;&lt;span&gt; application/json&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  &quot;postId&quot;&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; &quot;your-post-id&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;2. 获取点赞数&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;GET&lt;/span&gt;&lt;span&gt; /count?post=your-post-id&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;3. 记录页面访问（新增）&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;POST&lt;/span&gt;&lt;span&gt; /view&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;Content-Type:&lt;/span&gt;&lt;span&gt; application/json&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  &quot;postId&quot;&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; &quot;your-post-id&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;4. 获取完整统计（新增）&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;GET&lt;/span&gt;&lt;span&gt; /stats?post=your-post-id&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;返回：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  &quot;code&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;0&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  &quot;data&quot;&lt;/span&gt;&lt;span&gt;: {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    &quot;likes&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;10&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    &quot;views&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;123&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    &quot;hasLiked&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;false&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;数据存储说明&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;无需创建新的 KV namespace&lt;/strong&gt;，通过 Key 前缀区分数据类型：&lt;/p&gt;
&lt;p&gt;在 &lt;code&gt;UPVOTE_COUNT&lt;/code&gt; 中：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;like:count:{postId}&lt;/code&gt; - 点赞计数&lt;/li&gt;
&lt;li&gt;&lt;code&gt;view:count:{postId}&lt;/code&gt; - 访问计数&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;在 &lt;code&gt;UPVOTE_RECORD&lt;/code&gt; 中：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;like:{postId}:{userId}&lt;/code&gt; - 点赞记录（1年过期）&lt;/li&gt;
&lt;li&gt;&lt;code&gt;view:record:{postId}:{userId}&lt;/code&gt; - 访问记录（5分钟过期，防刷）&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;前端集成示例（Astro）&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;创建访问统计组件&lt;/strong&gt; &lt;code&gt;src/components/ViewCount.astro&lt;/code&gt;：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;---&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;import&lt;/span&gt;&lt;span&gt; { siteConfig } &lt;/span&gt;&lt;span&gt;from&lt;/span&gt;&lt;span&gt; &apos;../config&apos;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;interface&lt;/span&gt;&lt;span&gt; Props&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  postId&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; string&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; { &lt;/span&gt;&lt;span&gt;postId&lt;/span&gt;&lt;span&gt; } &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; Astro.props;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; apiUrl&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; siteConfig.like.apiUrl;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;---&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;lt;&lt;/span&gt;&lt;span&gt;span&lt;/span&gt;&lt;span&gt; class&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;view-count&quot;&lt;/span&gt;&lt;span&gt; id&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;view-count-container&quot;&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  &amp;lt;&lt;/span&gt;&lt;span&gt;span&lt;/span&gt;&lt;span&gt; id&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;view-count&quot;&lt;/span&gt;&lt;span&gt;&amp;gt;--&amp;lt;/&lt;/span&gt;&lt;span&gt;span&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  &amp;lt;&lt;/span&gt;&lt;span&gt;span&lt;/span&gt;&lt;span&gt;&amp;gt;次阅读&amp;lt;/&lt;/span&gt;&lt;span&gt;span&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;lt;/&lt;/span&gt;&lt;span&gt;span&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;lt;&lt;/span&gt;&lt;span&gt;script&lt;/span&gt;&lt;span&gt; is:inline&lt;/span&gt;&lt;span&gt; define:vars&lt;/span&gt;&lt;span&gt;={{ postId, apiUrl }}&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  (&lt;/span&gt;&lt;span&gt;function&lt;/span&gt;&lt;span&gt;() {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    let&lt;/span&gt;&lt;span&gt; viewRecorded &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; false&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    function&lt;/span&gt;&lt;span&gt; initViewCount&lt;/span&gt;&lt;span&gt;() {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      fetchViewCount&lt;/span&gt;&lt;span&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      &lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      if&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;!&lt;/span&gt;&lt;span&gt;viewRecorded) {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        setTimeout&lt;/span&gt;&lt;span&gt;(() &lt;/span&gt;&lt;span&gt;=&amp;gt;&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;          recordPageView&lt;/span&gt;&lt;span&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;          viewRecorded &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; true&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        }, &lt;/span&gt;&lt;span&gt;2000&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    async&lt;/span&gt;&lt;span&gt; function&lt;/span&gt;&lt;span&gt; fetchViewCount&lt;/span&gt;&lt;span&gt;() {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      try&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        const&lt;/span&gt;&lt;span&gt; response&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; await&lt;/span&gt;&lt;span&gt; fetch&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;`${&lt;/span&gt;&lt;span&gt;apiUrl&lt;/span&gt;&lt;span&gt;}/stats?post=${&lt;/span&gt;&lt;span&gt;encodeURIComponent&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;postId&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;}`&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        const&lt;/span&gt;&lt;span&gt; data&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; await&lt;/span&gt;&lt;span&gt; response.&lt;/span&gt;&lt;span&gt;json&lt;/span&gt;&lt;span&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        &lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        if&lt;/span&gt;&lt;span&gt; (data.code &lt;/span&gt;&lt;span&gt;===&lt;/span&gt;&lt;span&gt; 0&lt;/span&gt;&lt;span&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;          document.&lt;/span&gt;&lt;span&gt;getElementById&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&apos;view-count&apos;&lt;/span&gt;&lt;span&gt;).textContent &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; data.data.views &lt;/span&gt;&lt;span&gt;||&lt;/span&gt;&lt;span&gt; 0&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      } &lt;/span&gt;&lt;span&gt;catch&lt;/span&gt;&lt;span&gt; (error) {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        console.&lt;/span&gt;&lt;span&gt;error&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&apos;获取访问次数失败:&apos;&lt;/span&gt;&lt;span&gt;, error);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    async&lt;/span&gt;&lt;span&gt; function&lt;/span&gt;&lt;span&gt; recordPageView&lt;/span&gt;&lt;span&gt;() {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      try&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        await&lt;/span&gt;&lt;span&gt; fetch&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;`${&lt;/span&gt;&lt;span&gt;apiUrl&lt;/span&gt;&lt;span&gt;}/view`&lt;/span&gt;&lt;span&gt;, {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;          method: &lt;/span&gt;&lt;span&gt;&apos;POST&apos;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;          headers: { &lt;/span&gt;&lt;span&gt;&apos;Content-Type&apos;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&apos;application/json&apos;&lt;/span&gt;&lt;span&gt; },&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;          body: &lt;/span&gt;&lt;span&gt;JSON&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;stringify&lt;/span&gt;&lt;span&gt;({ postId })&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        });&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      } &lt;/span&gt;&lt;span&gt;catch&lt;/span&gt;&lt;span&gt; (error) {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        console.&lt;/span&gt;&lt;span&gt;error&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&apos;记录访问失败:&apos;&lt;/span&gt;&lt;span&gt;, error);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    if&lt;/span&gt;&lt;span&gt; (document.readyState &lt;/span&gt;&lt;span&gt;===&lt;/span&gt;&lt;span&gt; &apos;loading&apos;&lt;/span&gt;&lt;span&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      document.&lt;/span&gt;&lt;span&gt;addEventListener&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&apos;DOMContentLoaded&apos;&lt;/span&gt;&lt;span&gt;, initViewCount);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    } &lt;/span&gt;&lt;span&gt;else&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      initViewCount&lt;/span&gt;&lt;span&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    document.&lt;/span&gt;&lt;span&gt;addEventListener&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&apos;astro:page-load&apos;&lt;/span&gt;&lt;span&gt;, () &lt;/span&gt;&lt;span&gt;=&amp;gt;&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      viewRecorded &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; false&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      initViewCount&lt;/span&gt;&lt;span&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    });&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  })();&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;lt;/&lt;/span&gt;&lt;span&gt;script&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;在文章页面使用：&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;---&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;import&lt;/span&gt;&lt;span&gt; ViewCount &lt;/span&gt;&lt;span&gt;from&lt;/span&gt;&lt;span&gt; &apos;../../components/ViewCount.astro&apos;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;---&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;lt;&lt;/span&gt;&lt;span&gt;div&lt;/span&gt;&lt;span&gt; class&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;article-meta&quot;&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  &amp;lt;&lt;/span&gt;&lt;span&gt;time&lt;/span&gt;&lt;span&gt;&amp;gt;{&lt;/span&gt;&lt;span&gt;formatDate&lt;/span&gt;&lt;span&gt;(post.data.date)}&amp;lt;/&lt;/span&gt;&lt;span&gt;time&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  &amp;lt;&lt;/span&gt;&lt;span&gt;span&lt;/span&gt;&lt;span&gt;&amp;gt;|&amp;lt;/&lt;/span&gt;&lt;span&gt;span&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  &amp;lt;&lt;/span&gt;&lt;span&gt;span&lt;/span&gt;&lt;span&gt;&amp;gt;{readingTime} 分钟&amp;lt;/&lt;/span&gt;&lt;span&gt;span&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  &amp;lt;&lt;/span&gt;&lt;span&gt;span&lt;/span&gt;&lt;span&gt;&amp;gt;|&amp;lt;/&lt;/span&gt;&lt;span&gt;span&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  &amp;lt;&lt;/span&gt;&lt;span&gt;ViewCount&lt;/span&gt;&lt;span&gt; postId&lt;/span&gt;&lt;span&gt;={post.slug} /&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;lt;/&lt;/span&gt;&lt;span&gt;div&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;功能特点&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;访问统计：&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;✅ 自动记录页面访问&lt;/li&gt;
&lt;li&gt;✅ 5分钟内同一用户不重复计数&lt;/li&gt;
&lt;li&gt;✅ 使用 SHA-256 哈希保护隐私&lt;/li&gt;
&lt;li&gt;✅ 页面加载2秒后记录（避免误计数）&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;性能优化：&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;✅ 异步记录，不阻塞页面&lt;/li&gt;
&lt;li&gt;✅ 复用现有 KV，零额外成本&lt;/li&gt;
&lt;li&gt;✅ 支持 Astro View Transitions&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;测试&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;测试访问统计：&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;# 记录访问&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;curl&lt;/span&gt;&lt;span&gt; -X&lt;/span&gt;&lt;span&gt; POST&lt;/span&gt;&lt;span&gt; https://your-worker.workers.dev/view&lt;/span&gt;&lt;span&gt; \&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  -H&lt;/span&gt;&lt;span&gt; &quot;Content-Type: application/json&quot;&lt;/span&gt;&lt;span&gt; \&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  -d&lt;/span&gt;&lt;span&gt; &apos;{&quot;postId&quot;: &quot;test-post&quot;}&apos;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;# 获取统计&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;curl&lt;/span&gt;&lt;span&gt; &quot;https://your-worker.workers.dev/stats?post=test-post&quot;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;注意事项&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;Cloudflare Workers 免费额度：&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;每天 100,000 次请求&lt;/li&gt;
&lt;li&gt;每天 100,000 次 KV 读取&lt;/li&gt;
&lt;li&gt;每天 1,000 次 KV 写入&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;对于个人博客完全够用。如果流量较大，建议：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;增加防刷时间（如10分钟）&lt;/li&gt;
&lt;li&gt;使用 Cloudflare Analytics 获取更详细数据&lt;/li&gt;
&lt;li&gt;升级到付费计划&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;鸣谢&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;感谢 &lt;a href=&quot;https://github.com/HermanMartinus/bearblog&quot;&gt;bearblog&lt;/a&gt; 创造了 Bear Blog，感谢 &lt;a href=&quot;https://github.com/janraasch/hugo-bearblog&quot;&gt;hugo-bearblog&lt;/a&gt; 将 Bear Blog 带到了 &lt;a href=&quot;https://gohugo.io/&quot;&gt;Hugo&lt;/a&gt;，更感谢 &lt;a href=&quot;https://rokcso.com/&quot;&gt;Rokcso&lt;/a&gt; 丰富了 &lt;a href=&quot;https://github.com/rokcso/hugo-bearneo&quot;&gt;Hugo Bearblog&lt;/a&gt;。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;感谢 &lt;a href=&quot;https://www.cloudflare.com/&quot;&gt;Cloudflare&lt;/a&gt; 提供了本项目得以实现的所有功能和资源。&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;</content:encoded><category>life</category><category>Cloudflare</category><category>部署</category><category>技术</category></item><item><title>解决 Google Antigravity 无法登录问题</title><link>https://yourdomain.com/blog/fix-google-antigravity-login-issue/</link><guid isPermaLink="true">https://yourdomain.com/blog/fix-google-antigravity-login-issue/</guid><description>分类：life | 标签：Google, 技术</description><pubDate>Sun, 28 Dec 2025 03:32:00 GMT</pubDate><content:encoded>&lt;p&gt;&lt;strong&gt;Google Antigravity&lt;/strong&gt; 推出有一段时间了，依然有很多人无法解决登录问题，特别是 Windows 系统上。&lt;/p&gt;
&lt;p&gt;我因为 Mac 上能正常使用，所以也没想过在 Win 上彻底解决，今天详细研究了一下，其实非常简单。&lt;/p&gt;
&lt;p&gt;网上目前流传的各种解决方案，都是一个抄一个，完全不管是不是能真的解决问题，抓两个典型：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;修改账号所在地：这是最荒唐滑稽的解决方案，只要不是明确禁用的国家/地区都能使用，从新加坡修改到美国有个屁用；&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;修改电脑语言到 US：这也是一个完全不负责的胡说八道，秀逗。&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;我的 Mac 装上就能用，就一直很纳闷，所有环境都是相同的，唯独 Win 上不行，总是卡在登录那里：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;网页登录后无法调起&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;调起后 Antigravity 无法通过授权&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;根本问题其实还是 TUN 模式的问题，说出来都搞笑，Win 上想要 TUN 模式生效，需要使用管理模式启动魔法。&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;简单说一说 TUN 模式，其实就是在操作系统层面创建一个虚拟网卡，​强制流量全部都走虚拟网卡过一遍，这比操作系统自带的系统代理覆盖面更广。&lt;/p&gt;
&lt;p&gt;而启用 TUN 模式默认就需要&lt;mark&gt;管理员权限&lt;/mark&gt;，日常使用中很多没有使用管理员模式，所以即便开启了 TUN 开关，也没有生效，就以为解决方案不对。&lt;/p&gt;</content:encoded><category>life</category><category>Google</category><category>技术</category></item><item><title>2025之我的日常应用清单</title><link>https://yourdomain.com/blog/202512231037/</link><guid isPermaLink="true">https://yourdomain.com/blog/202512231037/</guid><description>分类：life | 标签：app</description><pubDate>Tue, 23 Dec 2025 02:37:14 GMT</pubDate><content:encoded>&lt;p&gt;看到 @1900 写了 &lt;a href=&quot;https://1900.live/my-app-defaults-2025/&quot;&gt;My App Defaults 2025&lt;/a&gt;，我也效仿着写一下我的日常App使用清单，其实2025年，我基本除了微信其他App基本都用不到了，所以这个清单可能比较简单，索性格式也就按照 @1900 文章的格式来~&lt;/p&gt;
&lt;p&gt;📨 Mail Client: 用的企业微信自带的邮件功能。&lt;/p&gt;
&lt;p&gt;📮 Mail Server: 如上。&lt;/p&gt;
&lt;p&gt;📝 Notes: 开始学习Obsidian，同时还有Notion。&lt;/p&gt;
&lt;p&gt;📋 To-Do List: 使用Todoist，同时搭配Gotosocial。&lt;/p&gt;
&lt;p&gt;📷 iPhone Android Photo Shooting: 使用 iPhone 系统自带相机。&lt;/p&gt;
&lt;p&gt;🟦 Photo Management: OneDrive 和飞牛相册。&lt;/p&gt;
&lt;p&gt;📆 Calendar: 企业微信日历，同时同步到手机端。&lt;/p&gt;
&lt;p&gt;📁 Cloud File Storage: 使用OneDrive，Windows上的文件同步使用OneDrive，手机使用iPhone自带的iCloud以及飞牛。&lt;/p&gt;
&lt;p&gt;📖 RSS: 使用 &lt;a href=&quot;https://lilog.cn/&quot;&gt;李的日志&lt;/a&gt; 提供的聚合平台。&lt;/p&gt;
&lt;p&gt;🙍🏻‍♂️ Contacts: 使用系统自带联系人应用。&lt;/p&gt;
&lt;p&gt;🌐 Browser: PC 端 Chrome，手机端同样。&lt;/p&gt;
&lt;p&gt;💬 Chat: 微信、Telegram 。&lt;/p&gt;
&lt;p&gt;🔖 Bookmarks: 使用Chrome自带同步，且同步到Raindrop。&lt;/p&gt;
&lt;p&gt;📜 Word Processing: VScode为主，同时正在学习Obsidian。&lt;/p&gt;
&lt;p&gt;📈 Spreadsheets: Office2024。&lt;/p&gt;
&lt;p&gt;🛒 Shopping Lists: 一般直接加到购物车里。&lt;/p&gt;
&lt;p&gt;🍴 Meal Planning: 美食邪修，喜欢根据口味、营养自我搭配。&lt;/p&gt;
&lt;p&gt;💰 Budgeting and Personal Finance: 因本年度没工作，基本没有什么开支。平时就是用 随手记App。&lt;/p&gt;
&lt;p&gt;📰 News:  新闻聚合，TG频道。&lt;/p&gt;
&lt;p&gt;🎵 Music: Apple Music。&lt;/p&gt;
&lt;p&gt;🎞 Movie：Emby、飞牛影视。&lt;/p&gt;
&lt;p&gt;🎤 Podcasts: 微信读书。&lt;/p&gt;
&lt;p&gt;🔐 Password Management: vaultwarden。&lt;/p&gt;
&lt;p&gt;💖Social：宅夫基本没有~&lt;/p&gt;
&lt;p&gt;一眨眼的功夫，2025年也接近尾声了，荒废了一年的光阴，也没有什么大的收获，唯一值得庆幸的是，可以有更多的时间来陪伴孩子，在育儿这方面，我还是比较满意的，虽然没有什么大的成就，但是也算是一个小小的进步吧。&lt;/p&gt;
&lt;p&gt;这不算年终总结，只是简单记录~顺便水一篇~~&lt;/p&gt;</content:encoded><category>life</category><category>app</category></item><item><title>童年的&quot;大&quot;与成长的&quot;小&quot;</title><link>https://yourdomain.com/blog/202512210700/</link><guid isPermaLink="true">https://yourdomain.com/blog/202512210700/</guid><description>分类：life | 标签：童年, 成长</description><pubDate>Sun, 21 Dec 2025 07:00:00 GMT</pubDate><content:encoded>&lt;p&gt;小时候，老家的院子是我心中最辽阔的天地。石板路从门口延伸到井边,凹凸不平的石块在阳光下闪着微光。&lt;/p&gt;
&lt;p&gt;我总觉得那是一条漫长的旅程，每次从门口走到井边，都要小心翼翼地跨过石缝，仿佛在完成一场探险。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;如今再回去，只需两步就能走完，石板路依旧在，却不再显得遥远。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;小学的操场也是如此。那时的操场像是一片无边的草原，跑一圈都要气喘吁吁。秋千在操场的一角，摇摆之间，仿佛能把我带到天空。&lt;/p&gt;
&lt;p&gt;如今再站在操场上，才发现它不过是一块普通的空地，秋千也矮得出奇，轻轻一跃就能触到顶端的横梁。&lt;/p&gt;
&lt;p&gt;还有家门前的小巷。小时候，巷子深邃而神秘，走在里面总觉得要花很久才能到尽头。两边的墙壁高耸，仿佛守护着一个秘密的世界。&lt;/p&gt;
&lt;p&gt;如今再走进去，才发现不过是短短几步，墙壁也早已斑驳低矮，不再有昔日的威严。&lt;/p&gt;
&lt;p&gt;这些地方没有改变，改变的是我们。成长让脚步更快，视野更广，却也让曾经的辽阔缩小成了寻常。&lt;/p&gt;
&lt;p&gt;小时候的”大”，是因为我们心中充满了好奇与想象；长大后的”小”，是因为我们已经习惯了掌控与理解。&lt;/p&gt;
&lt;p&gt;然而，真正的大并不在于空间的尺度，而在于心灵的感受。那些曾经觉得很大的地方，依然承载着童年的笑声、奔跑的影子和无数的幻想。&lt;/p&gt;
&lt;p&gt;即使如今两步就能走完，它们仍是记忆里最广阔的天地。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;成长让我们跨越了空间的限制，却提醒我们不要忘记心中的辽阔。或许，保持一颗童心，就是让世界永远”大”的秘密。&lt;/strong&gt;&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;&lt;strong&gt;《缩小的世界，不变的辽阔》&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;石缝间的探险，缩短为两步的距离，&lt;/p&gt;
&lt;p&gt;高耸的院墙，褪去了昔日的神秘。&lt;/p&gt;
&lt;p&gt;秋千不再摇向云端，小巷已能一眼望穿，&lt;/p&gt;
&lt;p&gt;成长让我们脚步轻快，却让世界变得寻常。&lt;/p&gt;
&lt;p&gt;并非天地在萎缩，而是我们在俯瞰，&lt;/p&gt;
&lt;p&gt;当好奇化作习惯，宏大便成了平凡。&lt;/p&gt;
&lt;p&gt;但若心存一抹童真，记忆便永无边际，&lt;/p&gt;
&lt;p&gt;最辽阔的天地，始终藏在你的眼底。&lt;/p&gt;</content:encoded><category>life</category><category>童年</category><category>成长</category></item><item><title>《思考，快与慢》读书笔记</title><link>https://yourdomain.com/blog/202512190100/</link><guid isPermaLink="true">https://yourdomain.com/blog/202512190100/</guid><description>分类：thoughts | 标签：认知偏见, 决策方法</description><pubDate>Fri, 19 Dec 2025 01:00:00 GMT</pubDate><content:encoded>&lt;p&gt;《思考，快与慢》又是一本很久以前看过，但是值得看很多遍的书，常看常新。初看的时候是2013年，最近又看的时候发现很多事情有了更多的视角去思考。&lt;/p&gt;
&lt;h3&gt;诺贝尔经济学奖获得者的思考&lt;/h3&gt;
&lt;p&gt;《思考，快与慢》是以色列经济学家和贝尔经济学奖得主丹尼尔·卡尼曼的一本畅销书籍，于2011年出版。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://s3.suus.me/posts/202512190100/2026/01/ya0osfh1.webp&quot; alt=&quot;image&quot; /&gt;&lt;/p&gt;
&lt;p&gt;这本书受到推崇的原因很大程度上是因为作者的背景，丹尼尔·卡尼曼几乎是行为经济学的奠基人之一。他自己的研究领域主要在经济学、心理学和决策科学，所以这本书可以说是他自己研究的一个简要总结。而且和学术论文不同，这本书写得条理清晰、深入浅出，甚至可以当成半学术作品来读。&lt;/p&gt;
&lt;h3&gt;1.两种思维系统&lt;/h3&gt;
&lt;p&gt;《思考，快与慢》这本书的结构很清晰：卡尼曼先介绍了一下&lt;strong&gt;两种思维方式&lt;/strong&gt; 。系统1和系统2最早是由心理学家基思·斯坦诺维奇和理查德·韦斯特提出的。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;系统1的运行是无意识且快速的，不怎么费脑力，没有感觉，完全处于自主控制状态。&lt;/p&gt;
&lt;p&gt;系统2将注意力转移到需要费脑力的大脑活动上来，例如复杂的运算。系统2的运行通常与行为，选择和专注等主观体验相关联。&lt;/p&gt;
&lt;p&gt;——丹尼尔·卡尼曼&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;总的来说，就是系统1简单直接且更接近第一反应，系统2与深度思考和重大决策相关。&lt;/p&gt;
&lt;p&gt;卡尼曼指出，我们在日常生活中经常依赖于系统1的思维方式，因为它能够帮助我们快速做出决策并应对各种情境，这就像是动物在丛林里觅食和活动，更多靠的其实是自然反应。&lt;/p&gt;
&lt;p&gt;从这个角度来讲，系统1也容易受到各种认知偏见的影响，比如过度自信、确认偏见和代表性偏见等。这些偏见导致我们在决策和判断中犯下错误，甚至影响了我们的投资、商业决策和政策制定等重要领域。&lt;/p&gt;
&lt;p&gt;而系统2是一种需要深度、需要时间和逻辑思维的思考系统。由于系统2的思维方式本来就是更加复杂的，需要收集更多的信息，更多的逻辑训练等等。所以事实上门槛更高。这本书后边的内容都在讲系统一思维方式可能的问题和如何尽量用系统2去做决策。&lt;/p&gt;
&lt;h3&gt;2.偏见谬误和决策&lt;/h3&gt;
&lt;p&gt;在这一部分里，卡尼曼讨论了一些我们日常会出现的一些认知偏差。&lt;/p&gt;
&lt;p&gt;比如，他介绍了“暴力直觉”（violence of intuitions）的概念，指的是我们对于某些问题的直觉判断可能是错误的，需要经过深思熟虑和分析才能得出正确的结论。还有“替代效应”（substitution effect）的概念，即在面对复杂问题时，我们常常倾向于回答一个相对简单的问题，从而产生了判断的偏差。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://s3.suus.me/posts/202512190100/2026/01/go1ipizi.webp&quot; alt=&quot;image&quot; /&gt;&lt;/p&gt;
&lt;p&gt;我觉得在日常生活里比较多出现的是“过度自信”和“代表性偏见”。这两种认知偏差主要因为陷于自己有限的偏差和比较狭窄的逻辑分析体系。具体体现形式除了刚愎自用，就是我们之前提到的“标签化思维”。&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;http://mp.weixin.qq.com/s?__biz=MzkzNjYxNzEyMg%3D%3D&amp;amp;mid=2247483861&amp;amp;idx=1&amp;amp;sn=ab7efb6f4fdcde08c1a62fa69605208a&amp;amp;chksm=c29ab418f5ed3d0e7ed0392a88774d6b682b563ba20a41da6869ebf218da4e5edc9c7fee23d6&amp;amp;scene=21#wechat_redirect&quot;&gt;标签化思维—我的脑子给自己上了个锁&lt;/a&gt;&lt;/p&gt;
&lt;h3&gt;3.有什么方法？&lt;/h3&gt;
&lt;p&gt;那么如何解决一些认知谬误和习惯呢？注意，如果我们接受了思维方式是有区分前后和级别的，那么也就代表着我们认同了这种思维方式快慢深浅的分析逻辑。&lt;/p&gt;
&lt;p&gt;在这个基础上，卡尼曼提出了在面对问题时可以从四个角度去思考、分析并做出决策。我个人理解，每一项背后其实是一个决策要素的代表(&lt;strong&gt;蓝字部分&lt;/strong&gt;)：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;区分长期和短期利益，识别二者的冲突(&lt;strong&gt;周期&lt;/strong&gt;)&lt;/li&gt;
&lt;li&gt;对风险的态度(&lt;strong&gt;风险&lt;/strong&gt;)&lt;/li&gt;
&lt;li&gt;对幸福感的评估(&lt;strong&gt;状态&lt;/strong&gt;)&lt;/li&gt;
&lt;li&gt;经济学视角(&lt;strong&gt;投入产出比&lt;/strong&gt;)&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;所以在需要做决策的时候，除了一些经典的思考模式，也可以从这几个角度去分析，当前面对的问题到底是一个怎样的问题，以及可能有怎样的解法。&lt;/p&gt;
&lt;p&gt;其实除了这些方法，还有一些其他的方法可以用来解决类似的问题。在《刻意练习》这本书里提到，可以通过刻意练习的方式去让自己对某一种习惯或者思维方式形成自动化的反应。就像围棋的定式；编程的算法；数学的公式；物理的定理；都是&lt;strong&gt;知识模型框架&lt;/strong&gt;。构建框架—形成框架——固化框架的过程就相当于给大脑装了一套新的系统或者软件，然后不断升级，给软件打升级包来修正认知谬误。&lt;/p&gt;
&lt;p&gt;但是自己对自己的思维方式和认知的看法还是容易出问题从而陷入另一个暴力直觉和过度自信的陷阱，对于这种情况，达里奥的《原则》里给出的一个解决方案是“保持极度开放透明”。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;和可信、愿意表达分歧的人一起审视你的观点。&lt;/p&gt;
&lt;p&gt;经常利用痛苦来引导自己进行高质量的思考&lt;/p&gt;
&lt;p&gt;——《原则》 达里奥&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;看上去好像挺痛苦并且会产生不少思维冲突的，但是只要你习惯了这种方式，你会觉得思维的碰撞比很多相对低维度的娱乐，有趣多了。&lt;/p&gt;</content:encoded><category>thoughts</category><category>认知偏见</category><category>决策方法</category></item><item><title>明信片里的温柔时光</title><link>https://yourdomain.com/blog/202512122203/</link><guid isPermaLink="true">https://yourdomain.com/blog/202512122203/</guid><description>分类：life | 标签：明信片</description><pubDate>Fri, 12 Dec 2025 14:03:57 GMT</pubDate><content:encoded>&lt;p&gt;在这个科技发达的时代，&lt;strong&gt;写信或写明信片&lt;/strong&gt;似乎已成为过去。通信可以在几秒钟内传遍世界各地。一切都是如此瞬间，以至于我们忘记了欣赏简单的事物。然而，一张来自博友的明信片，却让我重新体会到纸张与笔墨的温度。&lt;/p&gt;
&lt;p&gt;前几日，博友 &lt;a href=&quot;https://www.laomuzhu.cn/&quot;&gt;@木竹&lt;/a&gt; 在群聊时说起要给友链的博友发明信片，借此机缘，有幸收到了久违的明信片，很是激动~&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://i.p-i.vip/89/20251218-6943c369b4dda.webp&quot; alt=&quot;&quot; /&gt;
&lt;img src=&quot;https://i.p-i.vip/89/20251218-6943c2c20f72a.webp&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;h3&gt;那一刻的惊喜&lt;/h3&gt;
&lt;p&gt;看到苍劲有力的墨迹与精美的图案时，心中涌起一种久违的感动。不同于冰冷的电子邮件，明信片承载着寄件人亲手挑选的心意。那一刻，我仿佛穿越了时空，与博友在字里行间相遇。卡片上写下的几句问候，简短却真挚。字迹或许并不工整，却带着独特的个性与情感。每一个字都像是跨越千里而来的微笑，让我在冬日的夜晚感受到温暖。&lt;/p&gt;
&lt;h3&gt;纸上的旅行&lt;/h3&gt;
&lt;p&gt;明信片上的风景画，记录着博友所在城市的独特气息。那是一种视觉上的旅行，让我在未曾踏足的地方，感受到陌生却亲切的风景。纸面上的色彩与图案，仿佛在诉说着另一端的故事。&lt;/p&gt;
&lt;h3&gt;友情的印记&lt;/h3&gt;
&lt;p&gt;这张明信片不仅是一份礼物，更是一种情感的见证。它提醒我，网络上的交流并非虚拟，而是真实的心灵碰撞。博友的心意通过久违的信件传递，让友情有了可以触摸的形态。&lt;/p&gt;
&lt;p&gt;在快节奏的生活里，这样的片刻显得格外珍贵。它让我相信，文字依旧可以温柔，纸张依旧可以承载深情。&lt;/p&gt;</content:encoded><category>life</category><category>明信片</category></item><item><title>时至不惑：人生的新半场</title><link>https://yourdomain.com/blog/202512072000/</link><guid isPermaLink="true">https://yourdomain.com/blog/202512072000/</guid><description>分类：life | 标签：成长</description><pubDate>Sun, 07 Dec 2025 12:00:38 GMT</pubDate><content:encoded>&lt;p&gt;我最近常常思考“不可撤销性”这个词。它是一个冷峻的事实：20 岁的我，终究会抵达 40 岁，但 40 岁的我，却永远无法以当下的心境和阅历，回到 20 岁。&lt;span&gt;这种单向、不可逆的特性，是生命这条河流给予我的最根本的敬畏。&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;但有趣的是，过了 40 岁这个重要的分水岭，这种不安感反而开始减弱，被一种前所未有的&lt;strong&gt;富足感&lt;/strong&gt;取代。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;告别躁动：40 岁后的心态平稳&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;40 岁前的生活，常常是带着一种对“追赶”的焦躁感。追赶他人的成就，追赶时代的浪潮，追赶那个理想中遥远的自己。但踏入 40 岁之后，世界像是突然静了下来。这种静，并非消极的躺平，而是一种深度的心性平稳。&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://moretothat.com/on-turning-40/&quot;&gt;Lawrence Yeo&lt;/a&gt; 说：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;当你来到 40 岁，已历经相当多的艰难。也许你尚未遭遇此生最尖锐的苦难，但你一定明白人们为何说「生活不易」。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;我终于开始明白，许多年轻时急于求成的“聪明”和“机灵”，其实是缺乏耐心的表现。我不再渴求短期的胜利，而是将目光放到了更长的周期。这种转变，正是源于对 &lt;strong&gt;“生活不易”&lt;/strong&gt; 的深刻理解。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;经验的复利：对抗生活难题的底气&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;正如所说，当你来到 40 岁，你已经历了相当多的艰难。虽然可能尚未遭遇此生最尖锐的苦难，但你已深知“生活不易”的份量。好处在于，这四十年的历练，让我真正收获了 &lt;strong&gt;“经验的复利效应”&lt;/strong&gt; 。&lt;/p&gt;
&lt;p&gt;我发现，每跨过一道障碍，我获取的教训和策略并不会随着时间被遗忘，而是像投资的复利一样，被整合到我的 &lt;strong&gt;“人生应对系统”&lt;/strong&gt; 中。&lt;/p&gt;
&lt;p&gt;在 40 岁前，失败是打击；在 40 岁后，失败是&lt;strong&gt;校准&lt;/strong&gt;。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;20 岁遇到变故，我可能全盘慌乱，不知所措。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;40 岁遇到变故，我能迅速从过往相似的经验中，调取应对情绪、分析风险、制定方案的整体框架。&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;这种心性的变化，让我拥有了一种从容的底气。我想要的不是消除难题，而是少些慌乱，而 40 岁前的所有挣扎，都是为了现在这份从容积攒的资本。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;追求智慧：不可撤销的成长目标&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;如果说 20 岁追求的是速度，那么 40 岁追求的则是&lt;strong&gt;智慧、平和与从容&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;这种不可撤销的年龄增长，让我意识到，时间并不是在掠夺，而是在精炼。它磨掉了我身上的浮躁和尖锐，留下的是对人际关系更深的理解、对情绪波动更大的掌控力，以及对人生无限游戏的敬畏。&lt;/p&gt;
&lt;p&gt;我不再需要去证明什么，我更专注于去成为什么。成为那个能“日拱一卒，不急不躁”的人，成为那个在风暴来临时，能为自己和身边人提供稳定锚点的人。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;四十不惑，不是说再没有疑问，而是说我拥有了与疑问共处的能力。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;这些能力及经验的迁移，让我获得了“智慧”。这种智慧，不是聪明，而是对复杂世界的一种化繁为简的能力。&lt;/p&gt;
&lt;p&gt;我期待未来的岁月，因为它将继续以复利的形式，丰富我的智慧账户；我期待未来的生命篇章，因为它将继续以复利的形式，给我馈赠更多的平和与从容。让我&lt;mark&gt;从从容容游刃有余，不要匆匆忙忙连滚带爬&lt;/mark&gt;。&lt;/p&gt;</content:encoded><category>life</category><category>成长</category></item><item><title>所谓生活：一场「做自己」的英雄之旅</title><link>https://yourdomain.com/blog/202511262116/</link><guid isPermaLink="true">https://yourdomain.com/blog/202511262116/</guid><description>分类：life | 标签：生活, 思考</description><pubDate>Wed, 26 Nov 2025 13:16:37 GMT</pubDate><content:encoded>&lt;p&gt;我们常陷入一种误区，认为爱是牺牲，努力是受苦。但真相往往相反：只有你自己活得丰盛，你周围的世界才会变得丰盛。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;1. 关于育儿：做灯塔，而不是做蜡烛&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;传统的育儿观念像「蜡烛」，燃烧自己，照亮孩子，最后只剩下灰烬和对孩子的道德绑架。 高段位的育儿应当像 &lt;strong&gt;「灯塔」&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;重格局，轻时长&lt;/strong&gt;： 陪伴的质量取决于你灵魂的厚度。把那个盯着孩子写作业的时间，用来阅读、思考、提升自己的认知。当你的眼界开阔了，在孩子人生的关键路口，你的一句指点，胜过平时一万句唠叨。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;去掌控，重独立&lt;/strong&gt;： 知识会过时，但「独立自主」和「让自己快乐的能力」永不过时。相信孩子的人品和本自具足的生命力。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;最好的教育是「感染」&lt;/strong&gt;： 无论你希望孩子成为什么样的人，你自己先成为那样的人。你活出了光彩，孩子自然会追光而来。&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;2. 关于自我：让自己满意，是最高级的责任&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;在这个充满了噪音的世界里，讨好所有人是通往崩溃的捷径。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;情绪的源头&lt;/strong&gt;： 委屈自己换不来他人的满意。相反，一个压抑、委屈的父母，养不出松弛、自信的孩子。把自己哄开心了，是家庭幸福的第一生产力。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;拒绝苦难叙事&lt;/strong&gt;： 警惕那些「为了未来好，所以现在必须受罪」的论调。此刻的开心和舒心，本身就是巨大的能量。不要把自己的怯懦和懒惰，包装成对家庭的责任和牺牲。&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;3. 关于事业：乘势而上，顺流而行&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;在 AI 席卷的时代，闷头苦干是战术上的勤奋，战略上的懒惰。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;做超级个体&lt;/strong&gt;： 无论世界怎么变，打磨出让人愉悦的产品、提供独特的情绪价值，永远有市场。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;热爱即财富&lt;/strong&gt;： 找到你真正热爱的事物，全身心投入，金钱只是随之而来的副产品（Money follows passion）。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;借力打力&lt;/strong&gt;： 抓住大势（如 AI），比对抗大势要聪明得多。与其用肉身撞墙，不如造船出海。&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;4. 关于命运：尽人事，安天命&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;人生的大框架或许早已注定，但这不代表我们无能为力。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;解的艺术&lt;/strong&gt;： 我们努力的意义，不是为了逆天改命，而是为了把那条注定的波幅震荡到最高点。承认运气的作用，承认阶层的固化，然后放过自己。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;超越摇摆&lt;/strong&gt;： 叔本华说人生在「痛苦」和「无聊」间摇摆。打破这个钟摆的唯一方式，是创造。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;活在当下&lt;/strong&gt;： 不为未实现的理想焦虑，也不为实现后的空虚迷茫。专注于当下的每一次呼吸，每一顿饭，每一个与家人欢笑的瞬间。&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;所谓「赢」，不是战胜了别人，而是活出了自己。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;当你不再执着于教导孩子，而是专注提升自己时，孩子反而学得更好了； 当你不再执着于讨好世界，而是专注取悦自己时，世界反而对你和颜悦色了； 当你不再执着于苦苦追求金钱，而是专注做热爱之事时，财富反而流向你了。&lt;/p&gt;</content:encoded><category>life</category><category>生活</category><category>思考</category></item><item><title>读《人这一辈子什么最重要》：终究是爱与不悔</title><link>https://yourdomain.com/blog/202511232218/</link><guid isPermaLink="true">https://yourdomain.com/blog/202511232218/</guid><description>分类：life | 标签：生活</description><pubDate>Sun, 23 Nov 2025 10:14:13 GMT</pubDate><content:encoded>&lt;p&gt;读完作者夏重意难平的文章，心中久久不能平静，一份感悟涌上心头，看到“人这一辈子什么最重要”这个题目时，心里其实就有了答案，但还是忍不住想知道作者“夏重意难平”会给出怎样一个令人心服口服的回答。&lt;/p&gt;
&lt;p&gt;人这一生，总是在不停地追逐。年轻时，我们追逐&lt;strong&gt;远方和梦想&lt;/strong&gt;，觉得功成名就是最重要的；中年时，我们追逐&lt;strong&gt;稳定和财富&lt;/strong&gt;，觉得能给家人一个遮风避雨的家才是最重要的；等到白发苍苍，尘埃落定，才发现原来那些曾被我们忽略的，才是生命中最柔软、最珍贵的部分。&lt;/p&gt;
&lt;p&gt;文章最触动我的，可能就是那份 &lt;strong&gt;“意难平”&lt;/strong&gt;。这世上哪有真正圆满的人生？我们总是在拥有的时候不懂珍惜，在失去后才懂得怀念。&lt;/p&gt;
&lt;p&gt;也许是给父母打的那个总说 &lt;strong&gt;“我很忙”&lt;/strong&gt; 的电话，匆匆挂断后，电话那头老人的叹息。直到某一天，那个号码再也打不通，我们才猛然意识到，原来这辈子最重要的事，是多花一分钟听听他们的唠叨。&lt;/p&gt;
&lt;p&gt;也许是和爱人争执时，脱口而出的那句&lt;strong&gt;伤人又刻薄的话&lt;/strong&gt;。我们总以为爱是永恒的靠山，可以肆无忌惮地挥霍。直到有一天，身边人成了旧人，回头看，最遗憾的不是没有轰轰烈烈的爱情，而是没有好好珍惜那些&lt;strong&gt;平淡日常里的温暖&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;又或许是和孩子相处的&lt;strong&gt;短短几年光阴&lt;/strong&gt;。我们忙着给他创造更好的物质条件，却错过了他第一次喊“爸爸/妈妈”的惊喜、第一次跌倒后的无助。&lt;/p&gt;
&lt;p&gt;就像作者所说：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;这一辈子最重要的，不是我们最终获得了什么，而是我们好好爱过谁，又是否对得起这份爱。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;平实的文字，总有最戳人的力量。真正的泪点不是那些戏剧性的悲伤，而是那些日常的遗憾。当我们发现，自己这一路走来，错过了太多本该紧紧抓住的瞬间，辜负了太多本该好好珍惜的人，那种无声的悔意，才是最让人心酸的。&lt;/p&gt;
&lt;p&gt;人到最后，躺在床上回顾一生，真正能让我们感到安慰的，不是银行卡的数字，也不是头顶上的光环，而是能坦然地说一句：“虽然有遗憾，但那些爱我的人，我都尽力去爱了。”&lt;/p&gt;
&lt;p&gt;这篇文章提醒我们，最重要的，其实是现在。是此刻身边的人，是当下所拥有的健康，是那份不计回报的真心。&lt;/p&gt;
&lt;p&gt;“我们总以为，拥有了更多，就能拥有一切。” 但其实，真正拥有一切的，是那些&lt;strong&gt;真正用心去爱的人&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;如果人生能重来一次，我们一定会把时间花在&lt;strong&gt;陪伴、拥抱、倾听和原谅上&lt;/strong&gt;。 而既然不能，那就从现在开始，把遗憾降到最低吧。&lt;/p&gt;</content:encoded><category>life</category><category>生活</category></item><item><title>使用 TinaCMS 更优雅的管理 Hugo 网站</title><link>https://yourdomain.com/blog/202511142353/</link><guid isPermaLink="true">https://yourdomain.com/blog/202511142353/</guid><description>分类：tech | 标签：TinaCMS, CMS, Hugo</description><pubDate>Fri, 14 Nov 2025 15:53:23 GMT</pubDate><content:encoded>&lt;p&gt;最近一直在摸索如何在 Hugo 网站上撰写文章，因为我想要一个更方便的写作环境。看到 &lt;a href=&quot;https://lawtee.com/&quot;&gt;@佬T博客&lt;/a&gt; 也一直在折腾如何为 hugo 实现一个方便且快捷的方式来管理文章。我也来了兴趣，于是就想着自己也来试试。&lt;/p&gt;
&lt;p&gt;目前我的网站是在本地撰写 markdown ，再推送到 Github ，最后由 Cloudflare 完成线上部署的。这样要撰写文章不方便，因为我得坐在电脑前才能编辑 markdown ，不能随时随地线上编辑修改、发布文章。&lt;/p&gt;
&lt;p&gt;有没有我能够直接线上编辑 Github 的 Git 储存库的方式呢？ &lt;code&gt;「github.dev」&lt;/code&gt; 是可以做到，不过我既要又要。像用 WordPress 、 Typecho 等动态网站管理工具一样，直接打开一个网页，登录后台，想改哪篇点哪篇，改完点个「保存」就自动发布，那该多好？&lt;/p&gt;
&lt;p&gt;前几天折腾了Netlify CMS ，后台看起来总觉得简陋，本想着将就用吧，但最终还是放弃了，又开始折腾&lt;a href=&quot;/202511062203&quot;&gt; Front Matter CMS &lt;/a&gt;，随即又发现 Front Matter 的功能只能本地，还不够优雅，终于，找到了一个能实现这个梦想的工具 —— TinaCMS！&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://s0.wp.com/mshots/v1/https://demo.tina.io/admin/index.html#/~/posts/june/learning-about-tinacloud&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;h3&gt;啥是 TinaCMS？它能干啥？&lt;/h3&gt;
&lt;p&gt;简单来说，&lt;a href=&quot;https://tina.io/zh&quot;&gt;TinaCMS&lt;/a&gt; 是一个开源免费的「无头 CMS」（Headless CMS）。&lt;/p&gt;
&lt;p&gt;别被这个名字吓到，「无头」的意思就是它只提供一个内容管理的后台，但不管你的网站「前台」长啥样。这正好跟 Hugo 这种静态网站生成器是天生一对！&lt;/p&gt;
&lt;p&gt;装上 TinaCMS 之后，它能帮你在你自己的 Hugo 网站上变出一个管理后台（就像 &lt;code&gt;http://你的网域/admin 这样&lt;/code&gt;）。在这个后台里，你可以：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;所见即所得地写作：&lt;/strong&gt; 就像用在线文档一样编辑你的 Markdown 文章。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;管理图片：&lt;/strong&gt; 有个简易的媒体库，上传图片啥的方便多了。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;直接改、直接存：&lt;/strong&gt; 你在后台点「保存」，TinaCMS 就会自动帮你把修改的内容 &lt;code&gt;commit&lt;/code&gt; 并 &lt;code&gt;push&lt;/code&gt; 到你的 &lt;code&gt;GitHub/GitLab&lt;/code&gt; 仓库，触发网站的自动部署。&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;是不是听起来就很香？&lt;/p&gt;
&lt;h3&gt;方案一：先在「本地」试试水 (自架模式)&lt;/h3&gt;
&lt;p&gt;如果你暂时还不想折腾「线上编辑」，只是想在自己电脑上有个更舒服的写作后台（不想天天对着 Markdown ），那可以先试试它的「本地模式」。这个模式下，所有的修改都会直接保存在你电脑上的 Git 仓库里。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;开搞步骤：&lt;/strong&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;老规矩使用 IDE 进到你 Hugo 站点文件夹的根目录。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;用 npx 安装： （你需要先安装好 NodeJS 和 npm）&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;npx&lt;/span&gt;&lt;span&gt; @tinacms/cli@latest&lt;/span&gt;&lt;span&gt; init&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;安装过程中，它会问你一些问题，记得在「框架」那里选择 &lt;code&gt;Hugo&lt;/code&gt;。&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;改 &lt;code&gt;package.json&lt;/code&gt; 文件： 安装完后，它会生成一个 &lt;code&gt;package.json&lt;/code&gt;。打开它，找到 &lt;code&gt;&quot;scripts&quot;&lt;/code&gt; 部分，改成这样：&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;&quot;scripts&quot;&lt;/span&gt;&lt;span&gt;: {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  &quot;dev&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;tinacms dev -c &lt;/span&gt;&lt;span&gt;\&quot;&lt;/span&gt;&lt;span&gt;hugo server&lt;/span&gt;&lt;span&gt;\&quot;&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  &quot;build&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;tinacms build &amp;amp;&amp;amp; hugo&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;},&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这一步是告诉 TinaCMS，在启动开发模式 &lt;code&gt;npm run dev&lt;/code&gt; 时，顺便也把 Hugo 的本地预览 &lt;code&gt;hugo server&lt;/code&gt; 一并跑起来。&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;配置 TinaCMS：&lt;/strong&gt; 这一步最关键。找到 &lt;code&gt;tina/config.ts&lt;/code&gt; 这个文件，它会告诉 TinaCMS 你的文章都放在哪个文件夹里。 比如，我的文章放在 &lt;code&gt;content/posts&lt;/code&gt; 文件夹里，那我就要改成这样：&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;// tina/config.ts&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;import&lt;/span&gt;&lt;span&gt; { defineConfig } &lt;/span&gt;&lt;span&gt;from&lt;/span&gt;&lt;span&gt; &quot;tinacms&quot;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;export&lt;/span&gt;&lt;span&gt; default&lt;/span&gt;&lt;span&gt; defineConfig&lt;/span&gt;&lt;span&gt;({&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  // ... 其他配置 ...&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  schema: {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    collections: [&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        name: &lt;/span&gt;&lt;span&gt;&quot;post&quot;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;// 类型叫 &apos;post&apos;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        label: &lt;/span&gt;&lt;span&gt;&quot;文章&quot;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;// 后台显示的名称&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        path: &lt;/span&gt;&lt;span&gt;&quot;content/posts&quot;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;// 你的 Markdown 文件夹路径！&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        fields: [&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;          {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;            type: &lt;/span&gt;&lt;span&gt;&quot;string&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;            name: &lt;/span&gt;&lt;span&gt;&quot;title&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;            label: &lt;/span&gt;&lt;span&gt;&quot;标题&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;            isTitle: &lt;/span&gt;&lt;span&gt;true&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;            required: &lt;/span&gt;&lt;span&gt;true&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;          },&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;          {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;            type: &lt;/span&gt;&lt;span&gt;&quot;rich-text&quot;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;// 用富文本编辑器&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;            name: &lt;/span&gt;&lt;span&gt;&quot;body&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;            label: &lt;/span&gt;&lt;span&gt;&quot;正文&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;            isBody: &lt;/span&gt;&lt;span&gt;true&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;          },&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        ],&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      },&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    ],&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  },&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;});&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;注意： 这里的 path 一定要改成你自己放文章的目录！&lt;code&gt;fields&lt;/code&gt; 里的 &lt;code&gt;name&lt;/code&gt; 和 &lt;code&gt;label&lt;/code&gt; 是你在后台看到的字段名称，&lt;code&gt;isTitle&lt;/code&gt; 和 &lt;code&gt;isBody&lt;/code&gt; 是告诉 TinaCMS 这个字段是标题和正文。&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;运行：&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;npm&lt;/span&gt;&lt;span&gt; run&lt;/span&gt;&lt;span&gt; dev&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;等它跑完，打开浏览器，访问 &lt;a href=&quot;http://localhost:1313/admin/index.html%EF%BC%8C%E4%BD%A0%E5%B0%B1%E8%83%BD%E7%9C%8B%E5%88%B0&quot;&gt;http://localhost:1313/admin/index.html，你就能看到&lt;/a&gt; TinaCMS 的登录界面了！在本地模式下，你在这个后台做的所有修改，都会实时保存在你本地的文件里。恭喜你，你已经成功在本地为 Hugo 搭建了一个无头 CMS！&lt;/p&gt;
&lt;h3&gt;方案二：终极形态！连上 Tina Cloud&lt;/h3&gt;
&lt;p&gt;本地玩得差不多了，就该上「重头戏」了——让它连上云（Tina Cloud），实现随时随地线上编辑！&lt;/p&gt;
&lt;p&gt;Tina Cloud 是官方提供的一个服务，它充当一个「桥樑」，让你能在网页后台安全地操作你远在 GitHub 上的仓库。&lt;/p&gt;
&lt;p&gt;开搞步骤：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;注册 Tina Cloud 帐号：&lt;/strong&gt; 去 TinaCMS 官网注册一个帐号。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;授权 GitHub：&lt;/strong&gt; 登录后，它会引导你连接到你的 GitHub (或 GitLab) 帐号，然后选择你要管理的那个 Hugo 网站仓库。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;获取 API Key：&lt;/strong&gt; 在 Tina Cloud 的项目设置里，你会拿到一串密钥 &lt;code&gt;Client ID&lt;/code&gt; 和 &lt;code&gt;Client Secret&lt;/code&gt; 。把这些密钥按照&lt;a href=&quot;https://tina.io/zh/docs/tinacloud/overview&quot;&gt;官方文档&lt;/a&gt;的指示，复制粘贴到你本地的 &lt;code&gt;tina/config.ts&lt;/code&gt; 配置文件里。&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;提示： 这些是敏感信息，最好通过 &lt;code&gt;Vercel/Netlify&lt;/code&gt; 的「环境变量」来设置，而不是直接写死在代码里再推送到 &lt;code&gt;GitHub&lt;/code&gt; 上。&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;修改「网站部署」指令：&lt;/strong&gt; 这一步很重要！以前我们部署 Hugo 网站（比如在 Vercel 或 Netlify 上），部署指令可能就是简单的 hugo。 现在，因为我们加入了 TinaCMS，所以部署指令要改成 &lt;code&gt;npm run build&lt;/code&gt; (就是我们在 &lt;code&gt;package.json&lt;/code&gt; 里定义的那个 &lt;code&gt;tinacms build &amp;amp;&amp;amp; hugo&lt;/code&gt;)。 这样，部署平台在打包你的网站时，才会先把 TinaCMS 的后台管理界面也一起打包进去。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;推送代码，触发部署：&lt;/strong&gt; 把你本地修改过的 &lt;code&gt;tina/config.ts&lt;/code&gt;、p&lt;code&gt;ackage.json&lt;/code&gt; 等文件 &lt;code&gt;git push&lt;/code&gt; 到 GitHub。&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;大功告成！ 等网站重新部署成功后，你就可以在任何地方，通过访问 &lt;a href=&quot;https://%E4%BD%A0%E7%9A%84%E5%9F%9F%E5%90%8D/admin/index.html&quot;&gt;https://你的域名/admin/index.html&lt;/a&gt; 来登录你的网站后台了！&lt;/p&gt;
&lt;p&gt;现在试试看，在后台修改一篇文章并保存。你会发现，过一会儿，你的 GitHub 仓库就多了一个新的 commit，网站也自动开始重新构建了。完美！&lt;/p&gt;
&lt;h3&gt;实话实说：它有啥缺点？&lt;/h3&gt;
&lt;p&gt;结合我的经验，TinaCMS 虽好，但目前（特别是对于 Hugo 用户）还有些不完美的地方：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;对 Hugo 不算「亲儿子」：&lt;/strong&gt; 感觉官方的演示和功能重心都在 React 框架 (如 Next.js) 上，对 Hugo 的支持相对弱一些。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;编辑器功能一般：&lt;/strong&gt; 相比一些专门的 Markdown 编辑器，它的后台编辑功能只能算「够用」，不算强大。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;本地图片加载：&lt;/strong&gt; 在本地模式下，加载 Hugo 的本地图片资源可能会有点问题。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;搜索功能弱：&lt;/strong&gt; 后台的文章搜索功能比较拉胯。&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;总结&lt;/h3&gt;
&lt;p&gt;总而言之，TinaCMS 给我们这些 Hugo 静态网站的站长提供了一个非常棒的「折中方案」。&lt;/p&gt;
&lt;p&gt;它让我们既能享受到静态网站的「快和稳」，又能在一定程度上拥有了像 WordPress 那样「随时随地在线编辑」的便利性。&lt;/p&gt;
&lt;p&gt;如果你也受够了繁琐的本地编辑流程，或者想让不懂技术的运营伙伴也能帮你更新网站内容，那么 TinaCMS 绝对值得你花点时间折腾一下！&lt;/p&gt;</content:encoded><category>tech</category><category>TinaCMS</category><category>CMS</category><category>Hugo</category></item><item><title>告别繁琐！用 Front Matter CMS 优雅管理你的静态网站</title><link>https://yourdomain.com/blog/202511062203/</link><guid isPermaLink="true">https://yourdomain.com/blog/202511062203/</guid><description>分类：tech | 标签：Hugo, VSCode, Frontmatter</description><pubDate>Thu, 06 Nov 2025 22:03:35 GMT</pubDate><content:encoded>&lt;p&gt;你是否也觉得静态网站（如 Hugo , Hexo）的文章难以管理？ 是否也希望有一个漂亮的所见即所得编辑器来辅助写作？&lt;/p&gt;
&lt;p&gt;那么，你一定要试试 &lt;strong&gt;&lt;a href=&quot;https://frontmatter.codes/&quot;&gt;「Front Matter」&lt;/a&gt;&lt;/strong&gt; 来优雅地管理你的静态网站。&lt;/p&gt;
&lt;p&gt;本文将记录我配置 vscode-front-matter 的全过程，并分享配置完成后的“丝滑”发文流程，展示它如何简化操作，提升效率。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://shawnsandy.dev/images/front-matter-dashboard.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;h3&gt;Front Matter 的特色功能&lt;/h3&gt;
&lt;p&gt;「Front Matter」是由 Elio Struyf 开发的一款专为静态网站设计的 CMS (内容管理系统)，它以 Visual Studio Code 扩展的形式提供。&lt;/p&gt;
&lt;p&gt;Front Matter 的设计非常灵活，支持多种主流的静态网站生成器：Hugo、Jekyll、Hexo、NextJS、Gatsby、Astro 等。&lt;/p&gt;
&lt;p&gt;本文将以 Hugo 为例。我们知道，静态网站的源文件都是由目录和文件组成的。用户常常需要通过命令行，或者直接编辑 Markdown 文件来写作。这对于Geek来说可能不算什么，但对于习惯了 CMS 界面（例如 WordPress）的用户来说，难免会觉得繁琐。&lt;/p&gt;
&lt;p&gt;如果能有一款 GUI 工具，或是一个前端界面来编辑 Hugo 网站就好了。&lt;/p&gt;
&lt;p&gt;此时，只要装上 Front Matter，你的静态网站写作体验将会轻松许多。Front Matter 让你的 VS Code 仿佛“安装”了一个类似 WordPress 的后台管理界面。结合 VS Code 超强的文字编辑功能，你只需点击几个按钮，就能完成新增文章、预览网站、甚至部署网站等操作。&lt;/p&gt;
&lt;p&gt;Front Matter 的核心特色就是图形化。它会自动读取你现有网站的文章、图片、标签、分类，并清晰地罗列在专属面板上。用户可以使用所见即所得的 Markdown 编辑器写作，并通过漂亮的界面来批量管理分类和标签。&lt;/p&gt;
&lt;p&gt;Front Matter 甚至还会尝试给出一些 SEO 建议，或者让你调用 AI 来辅助写作。&lt;/p&gt;
&lt;p&gt;小小的吐槽： “Front Matter”，作者真是取了个“好”名字。如果搜索时不特别注明，搜到的几乎全是关于 Markdown 语法中 “front matter”（头部元数据）的资料。(黑暗執行緒 也认为这个命名不利于 SEO)。&lt;/p&gt;
&lt;p&gt;为了清楚地指代，我更喜欢称这个工具为 vscode-front-matter。&lt;/p&gt;
&lt;p&gt;当我提到 Front Matter (首字母大写) 时，指的是 vscode-front-matter 这款扩展。&lt;/p&gt;
&lt;p&gt;当我提到 front matter (全小写) 时，指的是 Markdown 文件头部的元数据。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://camo.githubusercontent.com/0ed85343369a000a88f3b54e3c913e01242e4f722b48965742f9ae958d20ee74/68747470733a2f2f626574612e66726f6e746d61747465722e636f6465732f72656c65617365732f7631302e302e302f6d756c74696c696e6775616c2d636f6e74656e742e706e67&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;h3&gt;为什么你应该使用 Front Matter CMS？&lt;/h3&gt;
&lt;p&gt;虽然 Hugo 是静态网站框架，但它也可以通过加装外部 CMS，使其拥有后台管理界面。&lt;/p&gt;
&lt;p&gt;我这篇文章主要讲的是本地开发的使用情景：即在自己的电脑上安装 VS Code，搭配 Front Matter CMS 来编辑 Hugo 网站的文章。&lt;/p&gt;
&lt;p&gt;那么，如果想远程开发呢？如果我们能将网页版的 VS Code (code-server) 部署到 VPS，再在里面安装 Front Matter CMS，理论上就能拥有一个类似 WordPress 后台的在线编辑器。你可以随时随地通过浏览器访问来撰写文章，再也不用盯着一堆 Markdown 文件发愁。&lt;/p&gt;
&lt;p&gt;我个人使用 Front Matter 的理由：&lt;/p&gt;
&lt;p&gt;我自己想要使用 CMS 的原因是 Hugo 没有像 WordPress 、Typecho 这样的 CMS一样的管理后台。可以很方便地管理分类、标签、图片、文件。&lt;/p&gt;
&lt;p&gt;现在，随着文章的增加，难以管理。所以我需要一个更轻松的图形化界面。起码，它可以减少我手动新建文件和目录的步骤，让我能更专注于写作本身。&lt;/p&gt;
&lt;p&gt;{{&amp;lt; alert “tip” “友情提示” &amp;gt;}}
你最好是已经深入使用过 Hugo (或其他静态框架) 一段时间后，再来使用 Front Matter。因为没人比你更清楚你网站的主题结构和工作流。Front Matter 虽然尽力做到了“开箱即用”，但你仍必须按照自己的需求进行调整，才能让它完美契合你的网站。
{{&amp;lt; /alert &amp;gt;}}&lt;/p&gt;
&lt;h3&gt;安装 vscode-front-matter&lt;/h3&gt;
&lt;p&gt;安装 VS Code，安装 Hugo 和 Git，并确保你的 Hugo 网站项目已建立。&lt;/p&gt;
&lt;p&gt;打开 VS Code，点击“文件” → “打开文件夹”，选择你 Hugo 网站的根目录。&lt;/p&gt;
&lt;p&gt;在 VS Code 的扩展市场中，搜索并安装 vscode-front-matter。&lt;/p&gt;
&lt;p&gt;安装完成后，点击 VS Code 左侧活动栏的 FM 图标，打开 Front Matter 面板，点击 &lt;code&gt;INITIALIZE PROJECT&lt;/code&gt; (初始化项目)。&lt;/p&gt;
&lt;p&gt;在 &lt;code&gt;FRAMEWORK PRESETS&lt;/code&gt; (框架预设) 中选取 hugo。&lt;/p&gt;
&lt;p&gt;在 &lt;code&gt;REGISTER CONTENT FOLDERS&lt;/code&gt; (注册内容文件夹) 中，选择你存放文章的目录，例如我的是 &lt;code&gt;content&lt;/code&gt; 。&lt;/p&gt;
&lt;p&gt;点击 &lt;code&gt;IMPORT ALL TAGS AND CATEGORIES&lt;/code&gt; (导入所有标签和分类)。&lt;/p&gt;
&lt;h3&gt;建立 Front Matter 配置文件&lt;/h3&gt;
&lt;p&gt;请参考 &lt;a href=&quot;https://frontmatter.codes/docs&quot;&gt;&lt;strong&gt;官方文档&lt;/strong&gt;&lt;/a&gt; 进行配置。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;注意：&lt;/strong&gt; 每个 Hugo 主题的设计都不同，你必须根据你的个人情况进行调整。在撰写本文时，我使用的是自己瞎改的主题。&lt;/p&gt;
&lt;p&gt;初始化项目后，Front Matter 会在你的网站根目录建立 &lt;code&gt;.vscode&lt;/code&gt; 目录和 &lt;code&gt;.frontmatter&lt;/code&gt; 目录，以及一个 &lt;code&gt;frontmatter.json&lt;/code&gt; 配置文件。&lt;/p&gt;
&lt;p&gt;我们只修改 &lt;code&gt;frontmatter.json&lt;/code&gt;，不要去动 VS Code 的全局 &lt;code&gt;setting.json&lt;/code&gt;。这样，Front Matter 的配置就只在打开这个 Hugo 网站目录时生效。&lt;/p&gt;
&lt;p&gt;打开 &lt;code&gt;frontmatter.json&lt;/code&gt;，填入以下内容（根据你的情况修改）：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;基础预览设置 设置网站本地预览地址和 Hugo server 的启动命令。&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  &quot;frontMatter.preview.host&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;http://localhost:1313&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  &quot;frontMatter.site.baseURL&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;https://suus.me/&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  &quot;frontMatter.framework.startCommand&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;hugo server&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;内容类型&lt;code&gt;(Content Types)&lt;/code&gt;和字段 我的文章都是以 Page Bundle 形式发布的。我在 frontMatter.taxonomy.contentTypes 中设置新文章默认包含的 &lt;code&gt;front matter&lt;/code&gt; 字段。&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  &quot;frontMatter.taxonomy.contentTypes&quot;&lt;/span&gt;&lt;span&gt;: [&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      &quot;name&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;default&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      &quot;previewPath&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;null&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      // 启用 Page Bundle&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      &quot;pageBundle&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;true&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      // 默认新文章目录名，留空，目录就会直接使用文章标题&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      &quot;filePrefix&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      &quot;fields&quot;&lt;/span&gt;&lt;span&gt;: [&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;          &quot;title&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;标题&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;          &quot;name&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;title&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;          &quot;type&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;string&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        },&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;          &quot;title&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;发布日期&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;          &quot;name&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;date&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;          &quot;type&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;datetime&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;          &quot;default&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;{{now}}&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;          &quot;isPublishDate&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;true&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        },&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;          &quot;title&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;是否为草稿&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;          &quot;name&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;draft&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;          &quot;type&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;draft&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        },&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;          &quot;title&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;分类&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;          &quot;name&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;categories&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;          &quot;type&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;categories&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        },&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;          &quot;title&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;标签&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;          &quot;name&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;tags&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;          &quot;type&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;tags&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        },&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;          &quot;title&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;预览图&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;          &quot;name&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;preview&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;          &quot;type&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;image&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      ]&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  ]&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Front Matter 和日期格式 指定文章 front matter 采用 YAML 格式，并设定时间格式。&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  &quot;frontMatter.taxonomy.frontMatterType&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;YAML&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  &quot;frontMatter.taxonomy.dateFormat&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;yyyy-MM-dd&apos;T&apos;HH:mm:ssxxx&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;注册特定的内容文件夹 (Page Folders) 我的 content 目录下还有不同的 section（如 posts 和 artworks），我使用 frontMatter.content.pageFolders 分别指定它们。&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  &quot;frontMatter.content.pageFolders&quot;&lt;/span&gt;&lt;span&gt;: [&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      &quot;title&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;posts&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      &quot;path&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;[[workspace]]/content/posts&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    },&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      &quot;title&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;artworks&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      &quot;path&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;[[workspace]]/content/artworks&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  ]&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;将 Shortcode 转换成 Snippet (代码片段)&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;我在文章中粘贴 Youtube 视频时，为了避免排版错乱，都用了自定义的 Shortcode 来处理 CSS，但这个代码有点长。&lt;/p&gt;
&lt;p&gt;Front Matter 允许你把 &lt;code&gt;Shortcode&lt;/code&gt; 短代码变成图形化菜单，以后就可以通过点击按钮来插入。&lt;/p&gt;
&lt;p&gt;例如，我有一个 Shortcode 位于 Hugo网站根目录&lt;code&gt;/layouts/shortcodes/embedded-youtube.html&lt;/code&gt;。&lt;/p&gt;
&lt;p&gt;在文章中插入的语法是： &lt;code&gt;\{\{&amp;lt; embedded-youtube &quot;视频网址&quot;&amp;gt; \}\}&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;于是，我在 &lt;code&gt;frontmatter.json&lt;/code&gt; 中添加以下片段：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  // Shortcode 代码片段&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  &quot;frontMatter.content.snippets&quot;&lt;/span&gt;&lt;span&gt;: {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    // &quot;embedded-youtube&quot; 按钮&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    &quot;embedded-youtube&quot;&lt;/span&gt;&lt;span&gt;: {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      &quot;description&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;插入 Youtube 视频&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      &quot;body&quot;&lt;/span&gt;&lt;span&gt;: [&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;         &quot;{&amp;lt; embedded-youtube [[videourl]] &amp;gt;}&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      ],&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      &quot;fields&quot;&lt;/span&gt;&lt;span&gt;: [&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;          &quot;name&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;videourl&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;          &quot;title&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;视频网址&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;          &quot;type&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;string&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;          &quot;default&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      ]&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  },&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  // 关闭 Snippet 的默认注释&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  &quot;frontMatter.snippets.wrapper.enabled&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;false&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;配置完成后，Front Matter 的 Dashboard (仪表盘) 上的 Snippets 页面就会出现一个 &lt;code&gt;embedded-youtube&lt;/code&gt; 按钮。点击它，填入视频网址，Shortcode 就会自动插入到文章光标处。&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;一切就绪后的发文流程&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;配置完成后，我现在的发文流程被极大简化了：&lt;/p&gt;
&lt;p&gt;打开 VS Code，打开 站点 根目录文件夹。切换到 Front Matter (FM) 页面，点击 &lt;code&gt;Create Content&lt;/code&gt; (创建内容)。&lt;/p&gt;
&lt;p&gt;选择要发布的 section (例如 posts)，输入文章标题。Front Matter 会自动创建好 &lt;code&gt;Page Bundle&lt;/code&gt; 目录和 &lt;code&gt;index.md&lt;/code&gt; 文件。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;插入图片：&lt;/strong&gt; 点击编辑器右上角的“插入图片”按钮，Front Matter 会自动定位到当前 &lt;code&gt;Page Bundle&lt;/code&gt; 所在的目录，我只需选择图片素材即可插入。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;优化写作体验：&lt;/strong&gt; 如果觉得默认编辑器界面不够优雅，可以点击左下角的 &lt;code&gt;Enable writing settings&lt;/code&gt; (启用写作设置)，这会临时调整行距和字体，让写作更舒适。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;设置缩略图：&lt;/strong&gt; Front Matter 列表的缩略图需要手动指定才会显示。我依据自己主题的习惯，将 &lt;code&gt;Page Bundle&lt;/code&gt; 目录下、与 &lt;code&gt;index.md&lt;/code&gt; 同级的 &lt;code&gt;feature.webp&lt;/code&gt; 文件设置为文章缩略图 (在 &lt;code&gt;front matter&lt;/code&gt; 中指定 &lt;code&gt;preview&lt;/code&gt; 字段)。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;预览：&lt;/strong&gt; 在 Dashboard (仪表盘) 点击 &lt;code&gt;Start Server&lt;/code&gt; 启动 Hugo 服务器，然后打开浏览器实时预览。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;部署：&lt;/strong&gt; 关于部署，因为我自己准备了一个用于 Git 推送的脚本，所以我没有启用 Front Matter 自带的 Git 集成功能。&lt;/p&gt;
&lt;h3&gt;关于远程使用 Front Matter&lt;/h3&gt;
&lt;p&gt;Front Matter 的一个局限在于，它主要还是为本地运行而设计的，对远程开发的支持度不佳，更不用说部署到云平台进行在线编辑了。&lt;/p&gt;
&lt;p&gt;如何通过浏览器使用 Front Matter？&lt;/p&gt;
&lt;p&gt;如果使用 code-server (网页版 VS Code)，Front Matter 扩展目前无法在浏览器环境中正常运作。&lt;/p&gt;
&lt;p&gt;目前可行的方案，也许是连接到电脑的远程桌面，在远程桌面上执行 VS Code 桌面版主程序，这样才能正常使用 Front Matter 的全部功能。&lt;/p&gt;</content:encoded><category>tech</category><category>Hugo</category><category>VSCode</category><category>Frontmatter</category></item><item><title>我为何越来越抵触`发声`这个词？</title><link>https://yourdomain.com/blog/why-i-resist-the-word-speaking-out/</link><guid isPermaLink="true">https://yourdomain.com/blog/why-i-resist-the-word-speaking-out/</guid><description>分类：life | 标签：思考</description><pubDate>Sun, 28 Sep 2025 15:08:00 GMT</pubDate><content:encoded>&lt;p&gt;不知道什么时候开始，&lt;strong&gt;“发声”&lt;/strong&gt; 这个词频繁出现在新闻标题上。&lt;/p&gt;
&lt;p&gt;刚开始没觉得有什么，随着时间推移，越发频繁的出现这个词，让我产生了一种本能的抵触，有一种异样的感觉。&lt;/p&gt;
&lt;p&gt;思考很久，我没有找到答案。于是我抱着试一试的心态，问了下 ChatGPT，竟然得到了让我满意的回答。如下：&lt;/p&gt;
&lt;p&gt;你觉得“发声”这个词怪怪的，可能是因为它在新闻标题中的使用有些特殊，带有一定的修辞效果。以下是几个可能的原因：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;词义延展&lt;/strong&gt;：“发声”这个词原本是指通过声带发出声音，即生理上的发声，但在这些新闻标题中，它被延伸为“表达意见”或“公开表态”。这种扩展用法虽然在语境中能理解，但从直觉上可能给人一种不自然或刻意修饰的感觉。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;媒体用词倾向&lt;/strong&gt;：媒体通常喜欢使用简洁、有冲击力的词汇来吸引眼球，而“发声”相比于“发言”或“表态”显得更具动态感或情绪性。它带有一种“打破沉默”的意味，似乎暗示了受害者或家属此前被压抑或没有表达的机会。这种隐含的情感可能会让你觉得不自然。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;词语的正面与负面联想&lt;/strong&gt;：在很多场合中，发声常常与“正义”或“维权”相关联。因此，在悲剧新闻中使用“发声”一词，可能让人觉得有些格格不入，尤其是在一些复杂或敏感的案件中，这种词语的正面联想可能会产生违和感。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;缺乏个性化&lt;/strong&gt;：频繁使用“受害者家属发声”这样结构化的标题，会让人觉得媒体在某种程度上忽略了个体的独特性，反而使用一种模板化的方式去表达。这种格式化的语言让新闻显得机械，缺少情感上的深度。&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;mark&gt;你可能也会觉得这个词过于机械或者煽情，特别是在新闻中反复使用的时候，产生了疲劳感。&lt;/mark&gt;&lt;/p&gt;</content:encoded><category>life</category><category>思考</category></item><item><title>我们，都是浪浪山的小妖怪。</title><link>https://yourdomain.com/blog/202508111954/</link><guid isPermaLink="true">https://yourdomain.com/blog/202508111954/</guid><description>分类：life | 标签：电影</description><pubDate>Mon, 11 Aug 2025 11:54:47 GMT</pubDate><content:encoded>&lt;p&gt;很少有一部动画短片，能如此精准又温柔地击中我们内心最柔软、最无奈的那一寸地方。《中国奇谭》中的《浪浪山的小妖怪》就是这样一部作品。它没有齐天大圣的意气风发，也没有神仙妖怪的惊天对决，它的主角，只是一只在浪浪山里默默无闻、为了获得一撮“奖励的鬃毛”而努力工作的小猪妖。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;而我们，在这只小猪妖身上，看到了无数个平凡的自己。&lt;/strong&gt;&lt;/p&gt;
&lt;h3&gt;↯ · 小人物的“打工魂”与朴素梦想&lt;/h3&gt;
&lt;p&gt;故事开始，小猪妖的生活就是当代“打工魂”的真实写照。他在庞大而等级森严的“浪浪山集团”里，只是一个最底层的员工。他的工作是擦亮兵器，每天重复着枯燥的劳动，他的梦想朴素又卑微——因为箭擦得好，能得到大王奖励的一撮头上的鬃毛，这对他而言，已是莫大的荣耀。&lt;/p&gt;
&lt;p&gt;他认真地计算着“唐僧肉”能分到自己几杯羹汤，小心翼翼地把妈妈做的、带着家乡味道的馅饼揣在怀里，计划着在庆功宴上分享。这份对工作的投入，这份对未来的微小期盼，这份对家人的深深眷恋，真实得让人心疼。我们谁又不是在自己的“浪浪山”里，为了那份小小的“绩效”和“奖金”，为了让家人过得更好一点，而日复一日地奔波忙碌呢？&lt;/p&gt;
&lt;h3&gt;↯ · 当“他者”不再是标签，而是鲜活的生命&lt;/h3&gt;
&lt;p&gt;影片的转折点，在于小猪妖对“敌人”的认知发生了改变。在人类老者的口中，他第一次听说，原来那个传说中要被吃掉的唐僧，是个善良、有学问、值得尊敬的人。他从村民的口中得知，孙悟空、猪八戒、沙和尚，并非青面獠牙的怪物，而是斩妖除魔的英雄。&lt;/p&gt;
&lt;p&gt;“他们是好人啊。”&lt;/p&gt;
&lt;p&gt;这一刻，冰冷的任务、遥远的标签，第一次在他心中化为了有血有肉的生命。他的世界观被撼动了，内心善良的火花被点燃。他不再是一个只想着分一杯羹的麻木小妖，而是一个开始独立思考、拥有了共情能力的“人”。这份觉醒，是小人物身上最可贵的光芒。他决定做点什么，哪怕微不足道。&lt;/p&gt;
&lt;h3&gt;↯ · 无声的结局，与一抹温柔的亮色&lt;/h3&gt;
&lt;p&gt;故事的结局，是整部短片最让人意难平，也最富深意的地方。小猪妖怀揣着他那颗滚烫的善心，冲出去想要报信，却在混乱中被孙悟空一棒子不经意地打死。他没能说出那句提醒，没能改变任何事，就像一颗投入大海的石子，无声无息地消失了。&lt;/p&gt;
&lt;p&gt;这个结局是残酷的，它刺痛了我们：在巨大的、不可逆转的洪流面前，一个普通人的努力，似乎总是那么徒劳无功。&lt;/p&gt;
&lt;p&gt;然而，导演在结尾处留下了一抹最温柔的亮色。唐僧发现了小猪妖怀里揣着的家乡馅饼，孙悟空则从他身上摸出了那本皱巴巴的《如何保养你的兵器》，并最终给了他三根救命毫毛的“幻象”。这是一种无声的告慰，一种来自“英雄”对一个无名小卒的理解与尊重。&lt;/p&gt;
&lt;p&gt;小猪妖的努力并非全无意义。他或许没能改变故事的结局，但他守护了自己内心的善良，并最终被他想要拯救的人所理解。这就够了。&lt;/p&gt;
&lt;h3&gt;结语&lt;/h3&gt;
&lt;p&gt;《浪浪山的小妖怪》之所以能引发如此巨大的共鸣，是因为它用一个妖怪的外壳，包裹了一颗属于我们每个普通人的内核。我们或许一生都无法成为故事的主角，但我们都在自己的“浪浪山”上，认真地生活，怀揣着小小的梦想，坚守着内心的善良。&lt;/p&gt;
&lt;p&gt;我们都是那只努力擦亮手中弓箭的小猪妖，期盼着被看见，被认可。而这部短片告诉我们，即使在最平凡的角落里，每一次真诚的努力和每一次善良的选择，都自有其不可磨灭的价值和回响。&lt;/p&gt;</content:encoded><category>life</category><category>电影</category></item><item><title>博客这片自留地，情怀不老！</title><link>https://yourdomain.com/blog/202505271426/</link><guid isPermaLink="true">https://yourdomain.com/blog/202505271426/</guid><description>分类：thoughts | 标签：博客</description><pubDate>Tue, 27 May 2025 06:26:47 GMT</pubDate><content:encoded>&lt;p&gt;前几天读了 &lt;a href=&quot;https://jefftcann.com/2025/05/25/loss-2/&quot;&gt;Jeff Cann&lt;/a&gt; 的一篇文章，心里挺有感触，也让我重新琢磨起博客这回事儿。他那几句话，一下子勾起了我的回忆，想起咱们在博客这片小天地里，那些来去匆匆却又分量十足的缘分。&lt;/p&gt;
&lt;p&gt;这么些年，眼看着博客圈里人来人往。有些曾经熟悉的名字，如今他们的博客页面再也不会更新，甚至听说有些朋友已经不在了。还有些人，写着写着就淡了，更新频率越来越低，直到某天，就这么停了。记得特别清楚有一个博主，他说自己写博客写到有些“魔怔”，怕是成了心病，结果几小时内就把所有文章、连同个人主页删得一干二净，好像他从没来过一样，真是让人唏嘘。当然，也有一些朋友，虽然不写了，但偶尔还会邮件聊上几句，这就像个温柔的提醒：有些情谊，就算不在聚光灯下，也依然在。&lt;/p&gt;
&lt;p&gt;虽然世事常变，但我打心底里盼着，博客这片自留地可千万别消失啊。对我来说，这依然是最有味道的交流方式。它不像那些被算法操控的平台，规定我们看什么，把我们的喜怒哀乐往设定好的方向上引。所以啊，像微博等主流的社交媒体，我早就敬而远之了。它们那种精心投喂的信息流，只会加剧偏见，潜移默化地影响我们怎么看世界，想想都觉得有点儿不舒服。&lt;/p&gt;
&lt;p&gt;博客就不一样了，它给我们一片净土，可以不急不躁地分享自己的想法，不用担心马上就得有回应，也没那么多七嘴八舌的干扰。&lt;/p&gt;
&lt;p&gt;我关注的一些博客，博主可能不常更新，但每次刷到新文章，都像是遇见了久违的老朋友，心里特熨帖。我不算是个爱评论的人，但每篇我都会认真读。偶尔点个赞，留个言，不一定是因为我完全同意博主的看法，更多的是因为我喜欢那些能让我打开思路、换个角度看问题的好东西。博客的魅力，就在于这份多元和纯粹——各色各样的声音，在一个相对安静的角落里交流思想，不求什么，不图什么。&lt;/p&gt;
&lt;p&gt;所以，真心感谢所有还在写博客、爱博客的朋友们——不管你是笔耕不辍，还是偶尔冒泡；不管你是把它当成一辈子的事儿，还是人生旅途中的一段风景。你们的文字，就像一座桥，连接着不同的时空和心灵，可能连你自己都不知道，不经意间就温暖了、启发了多少读者。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;只要我还在写，我就会一直在这里，用心读，用心想，用心和大家伙儿交流。&lt;/strong&gt;&lt;/p&gt;</content:encoded><category>thoughts</category><category>博客</category></item><item><title>如何在 HUGO 中打造更友好的 RSS 体验？</title><link>https://yourdomain.com/blog/202504241923/</link><guid isPermaLink="true">https://yourdomain.com/blog/202504241923/</guid><description>分类：tech | 标签：折腾, rss</description><pubDate>Thu, 24 Apr 2025 11:23:49 GMT</pubDate><content:encoded>&lt;p&gt;{{&amp;lt; alert “warning” “重要通知” “true” &amp;gt;}}
因为站点重构，本站的 RSS 订阅地址已变更，暂时不再添加可视化的RSS订阅页面，请使用原始的 RSS 订阅地址：&lt;a href=&quot;/atom.xml&quot;&gt;Atom&lt;/a&gt;
{{&amp;lt; /alert &amp;gt;}}&lt;/p&gt;
&lt;h3&gt;缘由&lt;/h3&gt;
&lt;p&gt;今天浏览博友文章，无意中看见了 &lt;strong&gt;@清羽飞扬&lt;/strong&gt; 最近发表了一篇文章&lt;a href=&quot;https://blog.liushen.fun/posts/caee2d9f/&quot;&gt;《美化你的RSS订阅地址》&lt;/a&gt;，看了他的实现过程，随即在网上搜索了下，还发现了&lt;a href=&quot;https://jakelazaroff.com/rss.xml&quot;&gt;jakelazaroff&lt;/a&gt;，乍一看，以为是一个简洁的静态页面，仔细观察发现原来这个就是我们平时使用 Feed 订阅页面，是不是让人耳目一新？&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;可以先看看我现在的 &lt;em&gt;&lt;a href=&quot;/index.xml&quot;&gt;Feed&lt;/a&gt;&lt;/em&gt;，或许说不上很好看，但应该比枯燥的 XML 标签看起来要友好一点吧 (つд⊂)&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://i.p-i.vip/89/20250915-68c7f8d03731e.webp&quot; alt=&quot;效果图&quot; /&gt;&lt;/p&gt;
&lt;h2&gt;什么是 RSS？&lt;/h2&gt;
&lt;p&gt;RSS（Really Simple Syndication）是一种消息来源格式规范，它可以让用户订阅博客、新闻网站等内容源。通过 RSS，读者可以使用 RSS 阅读器集中阅读多个网站的更新，而不需要分别访问每个网站。&lt;/p&gt;
&lt;p&gt;尽管社交媒体和平台算法主导了内容分发，但 RSS 依然具有不可替代的价值：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;用户控制权：读者自主选择信息源，内容按时间顺序排列，不受算法干预。&lt;/li&gt;
&lt;li&gt;无干扰阅读：RSS Feed 本身通常不包含网站广告和无关的界面元素（虽然原文可能有），阅读体验更纯粹。&lt;/li&gt;
&lt;li&gt;高效聚合：一站式阅读所有关注的内容更新。&lt;/li&gt;
&lt;li&gt;保护隐私：相比社交平台，RSS 订阅通常不涉及複杂的用户追踪。&lt;/li&gt;
&lt;li&gt;开放标准：不被单一平台锁定，具有良好的兼容性和持久性。&lt;/li&gt;
&lt;li&gt;内容备份与离线阅读：部分阅读器支持缓存内容，方便离线阅读。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;提供 RSS Feed，是对那些偏爱这种阅读方式的读者的尊重，也是内容开放性的一种体现。&lt;/p&gt;
&lt;h2&gt;Hugo 的 RSS 支持&lt;/h2&gt;
&lt;p&gt;Hugo 默认就支持 RSS 输出，但要让它更好用，我们需要做一些配置优化。&lt;/p&gt;
&lt;h3&gt;基础配置&lt;/h3&gt;
&lt;p&gt;在 &lt;code&gt;hugo.toml&lt;/code&gt;（或 &lt;code&gt;config.toml&lt;/code&gt;）中添加基本的 RSS 配置：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;# RSS 基础设置&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;[&lt;/span&gt;&lt;span&gt;services&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;rss&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  limit = &lt;/span&gt;&lt;span&gt;20&lt;/span&gt;&lt;span&gt; # 输出最近的 20 篇文章&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  fullText = &lt;/span&gt;&lt;span&gt;true&lt;/span&gt;&lt;span&gt; # 输出全文内容&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;# 确保输出包含 RSS&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;[&lt;/span&gt;&lt;span&gt;outputs&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  home = [&lt;/span&gt;&lt;span&gt;&apos;HTML&apos;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;&apos;RSS&apos;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;&apos;JSON&apos;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;&apos;SITEMAP&apos;&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;RSS 模板美化&lt;/h3&gt;
&lt;p&gt;为了让 RSS feed 在浏览器中显示得更美观，我们可以添加一个 XSL 样式表。在 &lt;code&gt;static/rss.xsl&lt;/code&gt; 创建样式文件：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;&amp;lt;?&lt;/span&gt;&lt;span&gt;xml&lt;/span&gt;&lt;span&gt; version&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;1.0&quot;&lt;/span&gt;&lt;span&gt; encoding&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;utf-8&quot;&lt;/span&gt;&lt;span&gt;?&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;lt;&lt;/span&gt;&lt;span&gt;xsl:stylesheet&lt;/span&gt;&lt;span&gt; version&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;3.0&quot;&lt;/span&gt;&lt;span&gt; xmlns:xsl&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;http://www.w3.org/1999/XSL/Transform&quot;&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  &amp;lt;&lt;/span&gt;&lt;span&gt;xsl:output&lt;/span&gt;&lt;span&gt; method&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;html&quot;&lt;/span&gt;&lt;span&gt; version&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;1.0&quot;&lt;/span&gt;&lt;span&gt; encoding&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;UTF-8&quot;&lt;/span&gt;&lt;span&gt; indent&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;yes&quot;&lt;/span&gt;&lt;span&gt;/&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  &amp;lt;&lt;/span&gt;&lt;span&gt;xsl:template&lt;/span&gt;&lt;span&gt; match&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;/&quot;&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    &amp;lt;&lt;/span&gt;&lt;span&gt;html&lt;/span&gt;&lt;span&gt; xmlns&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;http://www.w3.org/1999/xhtml&quot;&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      &amp;lt;&lt;/span&gt;&lt;span&gt;head&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        &amp;lt;&lt;/span&gt;&lt;span&gt;title&lt;/span&gt;&lt;span&gt;&amp;gt;&amp;lt;&lt;/span&gt;&lt;span&gt;xsl:value-of&lt;/span&gt;&lt;span&gt; select&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;/rss/channel/title&quot;&lt;/span&gt;&lt;span&gt;/&amp;gt; - RSS Feed&amp;lt;/&lt;/span&gt;&lt;span&gt;title&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        &amp;lt;&lt;/span&gt;&lt;span&gt;meta&lt;/span&gt;&lt;span&gt; http-equiv&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;Content-Type&quot;&lt;/span&gt;&lt;span&gt; content&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;text/html; charset=utf-8&quot;&lt;/span&gt;&lt;span&gt;/&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        &amp;lt;&lt;/span&gt;&lt;span&gt;meta&lt;/span&gt;&lt;span&gt; name&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;viewport&quot;&lt;/span&gt;&lt;span&gt; content&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;width=device-width, initial-scale=1, maximum-scale=1&quot;&lt;/span&gt;&lt;span&gt;/&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        &amp;lt;&lt;/span&gt;&lt;span&gt;style&lt;/span&gt;&lt;span&gt; type&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;text/css&quot;&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;          body {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;            max-width: 650px;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;            margin: 0 auto;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;            font-family: -apple-system, BlinkMacSystemFont, &quot;Segoe UI&quot;, Roboto, Helvetica, Arial, sans-serif;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;            padding: 2rem;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;            line-height: 1.5;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;            color: #333;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;            background: #f5f5f5;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;          }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;          /* 其他样式... */&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        &amp;lt;/&lt;/span&gt;&lt;span&gt;style&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      &amp;lt;/&lt;/span&gt;&lt;span&gt;head&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      &amp;lt;&lt;/span&gt;&lt;span&gt;body&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        &amp;lt;&lt;/span&gt;&lt;span&gt;h1&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;          &amp;lt;&lt;/span&gt;&lt;span&gt;span&lt;/span&gt;&lt;span&gt; class&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;rss-icon&quot;&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;            &amp;lt;&lt;/span&gt;&lt;span&gt;svg&lt;/span&gt;&lt;span&gt; viewBox&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;0 0 24 24&quot;&lt;/span&gt;&lt;span&gt; xmlns&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;http://www.w3.org/2000/svg&quot;&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;              &amp;lt;&lt;/span&gt;&lt;span&gt;circle&lt;/span&gt;&lt;span&gt; cx&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;6&quot;&lt;/span&gt;&lt;span&gt; cy&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;18&quot;&lt;/span&gt;&lt;span&gt; r&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;2&quot;&lt;/span&gt;&lt;span&gt;/&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;              &amp;lt;&lt;/span&gt;&lt;span&gt;path&lt;/span&gt;&lt;span&gt; d&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;M4 4a16 16 0 0 1 16 16&quot;&lt;/span&gt;&lt;span&gt;/&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;              &amp;lt;&lt;/span&gt;&lt;span&gt;path&lt;/span&gt;&lt;span&gt; d&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;M4 11a9 9 0 0 1 9 9&quot;&lt;/span&gt;&lt;span&gt;/&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;            &amp;lt;/&lt;/span&gt;&lt;span&gt;svg&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;          &amp;lt;/&lt;/span&gt;&lt;span&gt;span&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;          &amp;lt;&lt;/span&gt;&lt;span&gt;a&lt;/span&gt;&lt;span&gt; href&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;{/rss/channel/link}&quot;&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;            &amp;lt;&lt;/span&gt;&lt;span&gt;xsl:value-of&lt;/span&gt;&lt;span&gt; select&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;/rss/channel/title&quot;&lt;/span&gt;&lt;span&gt;/&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;          &amp;lt;/&lt;/span&gt;&lt;span&gt;a&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        &amp;lt;/&lt;/span&gt;&lt;span&gt;h1&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        &amp;lt;!-- 其他内容... --&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      &amp;lt;/&lt;/span&gt;&lt;span&gt;body&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    &amp;lt;/&lt;/span&gt;&lt;span&gt;html&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  &amp;lt;/&lt;/span&gt;&lt;span&gt;xsl:template&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;lt;/&lt;/span&gt;&lt;span&gt;xsl:stylesheet&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;在自定义的 &lt;code&gt;rss.xml&lt;/code&gt; 模板中引入 &lt;code&gt;xsl&lt;/code&gt; 样式文件。&lt;/p&gt;
&lt;h3&gt;高级配置（可选）&lt;/h3&gt;
&lt;p&gt;如果需要更细致的控制，可以添加以下配置：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;# RSS 输出格式设置&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;[&lt;/span&gt;&lt;span&gt;outputFormats&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  [&lt;/span&gt;&lt;span&gt;outputFormats&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;RSS&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    mediatype = &lt;/span&gt;&lt;span&gt;&quot;application/rss+xml&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    baseName = &lt;/span&gt;&lt;span&gt;&quot;feed&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;# 为分类和标签页面启用 RSS&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;[&lt;/span&gt;&lt;span&gt;outputs&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  section = [&lt;/span&gt;&lt;span&gt;&apos;HTML&apos;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;&apos;RSS&apos;&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  taxonomy = [&lt;/span&gt;&lt;span&gt;&apos;HTML&apos;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;&apos;RSS&apos;&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;# 多语言支持&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;[&lt;/span&gt;&lt;span&gt;languages&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  [&lt;/span&gt;&lt;span&gt;languages&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;zh-cn&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    languageName = &lt;/span&gt;&lt;span&gt;&quot;简体中文&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    weight = &lt;/span&gt;&lt;span&gt;1&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    [&lt;/span&gt;&lt;span&gt;languages&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;zh-cn&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;params&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      rss_sections = [&lt;/span&gt;&lt;span&gt;&quot;posts&quot;&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;RSS 的使用&lt;/h2&gt;
&lt;p&gt;读者可以通过以下方式订阅你的博客：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;使用在线 RSS 阅读器：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://feedly.com&quot;&gt;Feedly&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.innoreader.com&quot;&gt;Inoreader&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://newsblur.com&quot;&gt;NewsBlur&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;使用本地 RSS 阅读器应用&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;只需将博客的 RSS 链接（通常是 &lt;code&gt;https://你的域名/index.xml&lt;/code&gt;）添加到这些阅读器中即可开始订阅。&lt;/p&gt;
&lt;h2&gt;总结&lt;/h2&gt;
&lt;p&gt;添加 RSS 功能可以让你的博客内容更容易被读者订阅和追踪。Hugo 的默认 RSS 支持已经很好，通过简单的配置就能满足基本需求。如果需要更好的用户体验，可以添加 XSL 样式表来美化 RSS 页面的显示效果。&lt;/p&gt;
&lt;p&gt;记住，RSS 不仅是一个传统功能，更是一种尊重用户的表现。它让用户能够以自己喜欢的方式来消费内容，不被算法和平台所限制。&lt;/p&gt;</content:encoded><category>tech</category><category>折腾</category><category>rss</category></item><item><title>理性看待戒烟：我的决心、挑战与可行之路</title><link>https://yourdomain.com/blog/202504112004/</link><guid isPermaLink="true">https://yourdomain.com/blog/202504112004/</guid><description>分类：life | 标签：思考</description><pubDate>Fri, 11 Apr 2025 12:04:18 GMT</pubDate><content:encoded>&lt;p&gt;吸烟这件事，对我来说已经不仅仅是一个习惯，更像是一种纠缠多年的生理与心理依赖。和许多朋友一样，戒烟的念头在我脑海里盘旋过无数次，有时甚至会尝试几天，但最终往往就在 &lt;em&gt;“下根一定”&lt;/em&gt; 中不了了之。然而，这一次，我决定认真面对它——&lt;strong&gt;我，一个二十多年的烟民打算开始戒烟了。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;写下这篇文章，一方面是整理思绪，梳理客观信息，另一方面，也是想为自己即将开始的这段旅程打打气，明确方向。我知道这条路不好走，充满了挑战，但也清楚地知道，彼岸有我渴望的改变。&lt;/p&gt;
&lt;h3&gt;即将面对的挑战：我预见的尼古丁依赖“反扑”&lt;/h3&gt;
&lt;p&gt;坦白说，想到戒烟，我最先感受到的不是兴奋，而是对困难的预估。我知道尼古丁的厉害，它像个老朋友，总在特定时刻“恰到好处”地出现，提供短暂的慰藉。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;生理上的硬仗： 我几乎能预见，停吸后的头几天甚至几周，身体会如何“抗议”。那种莫名的烦躁、难以集中的注意力、可能出现的睡眠问题，以及时不时涌上心头的、对那一口烟的强烈渴望……这些都是我必须正面迎击的戒断症状。一想到这些，确实有些打怵，但这也是必须跨过的坎。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;心理上的“惯性”： 对我而言，比生理依赖更难缠的，或许是心理上的习惯。晨起一杯咖啡配一支烟，似乎才是一天的正确打开方式；工作压力大或感到无聊时，下意识就想点燃一支；饭后那一支，更是仿佛成了一种仪式。打破这些“场景触发”的链接，改变长久以来的行为模式，将是我面临的巨大挑战。我需要找到新的方式来应对这些情境和情绪。&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;我所期待的改变：戒烟的现实吸引力&lt;/h3&gt;
&lt;p&gt;虽然预见到困难，但促使我下定决心的，是那些戒烟后实实在在、可以触摸到的好处。这不仅仅是为了一个模糊的“健康未来”，更是为了眼前的生活品质。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;身体的“轻”体验： 我期待呼吸能更顺畅些，不再轻易咳嗽气喘，也许爬楼梯或快走时能更轻松一点。我也很想重新找回敏锐的味觉和嗅觉，真正尝出食物本来的味道，闻到清晨空气的清新，而不是总被烟味笼罩。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;钱包的“厚”实感： 算了一笔账，每天一包烟累积下来，一年也是一笔不小的开销。把这笔钱省下来，无论是用于旅行、学习新技能，还是添置一件心仪已久的东西，都比“烧掉”更有价值。这种经济上的回报，对我来说是看得见的、非常具体的动力。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;社交与环境的“清爽”： 说实话，我有点厌倦了自己身上的烟味，以及需要时刻寻找吸烟点的尴尬。能够在任何场合都更自在，不必担心二手烟影响到家人和朋友，也是我向往的一种状态。&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;我的戒烟策略：正在规划的行动方案&lt;/h3&gt;
&lt;p&gt;光有决心还不够，我明白需要一个周全的计划来支撑。结合了解到的信息，我正在为自己规划一套策略：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;明确启动日与应对预案： 我会设定一个具体的戒烟日期，并提前告知家人和信得过的朋友，争取他们的监督和支持。同时，我会梳理出自己最容易吸烟的场景（比如早晨、饭后、工作间隙），并提前想好替代方案，比如用深呼吸、喝水、嚼无糖口香糖或者短暂散步来应对。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;积极寻求替代与转移： 当强烈的吸烟冲动来袭时，我计划尝试一些能让手和嘴忙起来的事情，比如做点简单的运动、听音乐转移注意力，或者找人聊聊天。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;考虑寻求外部帮助： 我知道单凭意志力可能不够。我正在认真考虑，在戒烟初期使用尼古丁替代疗法（比如贴片或口香糖）来缓解生理上的痛苦，让自己能更好地专注于对抗心理依赖。同时，我也会咨询医生，看看是否有适合我的戒烟药物或其他专业建议。相关的戒烟热线或APP，或许也能提供及时的支持。&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src=&quot;https://cdnfile.sspai.com/2023/12/01/6f07f8d07f8f9d3a962559d0685fedcd.jpg?imageView2/2/w/1120/q/90/interlace/1/ignore-error/1/format/webp&quot; alt=&quot;&quot; title=&quot;Photo by [ Arek Adeoye](https://unsplash.com/photos/shallow-focus-photography-of-person-walking-on-road-between-grass-ljoCgjs63SM?utm_content=creditCopyText&amp;amp;utm_medium=referral&amp;amp;utm_source=unsplash)&quot; /&gt;&lt;/p&gt;
&lt;h3&gt;写在最后：为自己，迈出这一步&lt;/h3&gt;
&lt;p&gt;整理这些信息，写下我的决定，感觉决心更加坚定了。我知道这会是一场“硬仗”，过程中可能会有反复，甚至暂时的失败。但这一次，我把它看作一个必须完成的项目，一个对自己负责的行动。戒烟，是为了摆脱一种束缚，赢回更多的自由和更好的生活状态。&lt;/p&gt;
&lt;p&gt;我准备好了，去迎接挑战，也去拥抱改变。如果你也正在考虑戒烟，希望我的思考和决心能给你一点参考或勇气。让我们一起，为成为那个更好的自己，迈出这理性而坚定的一步。&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;&lt;em&gt;好了，都写到这儿了，不得来一根压压惊~&lt;/em&gt;😂 【画外音】：这完蛋玩意儿……&lt;/p&gt;</content:encoded><category>life</category><category>思考</category></item><item><title>我们为什么要多读书？</title><link>https://yourdomain.com/blog/202504082128/</link><guid isPermaLink="true">https://yourdomain.com/blog/202504082128/</guid><description>分类：thoughts | 标签：思考</description><pubDate>Tue, 08 Apr 2025 13:28:34 GMT</pubDate><content:encoded>&lt;p&gt;在信息爆炸、节奏飞快的当下，短视频、碎片化信息充斥着我们的屏幕。我们似乎随时随地都能获取“知识”，快速、便捷、生动。这时，再捧起一本厚厚的书，沉下心来阅读，似乎显得有些“不合时宜”。于是，“我们为什么要多读书？”这个问题，比以往任何时候都更值得我深入思考。&lt;/p&gt;
&lt;h3&gt;首先，读书是获取系统知识、深度理解世界的最有效途径之一。&lt;/h3&gt;
&lt;p&gt;与碎片化信息不同，书籍，尤其是经典著作或严谨的非虚构作品，通常围绕一个主题进行深入、系统的阐述。它提供背景、分析逻辑、展示论证过程，帮助我们建立起完整的知识框架。通过阅读，我们能了解历史的脉络，理解科学的原理，探索哲学的思辨，把握经济的规律。这种系统性的学习，使我们能够超越表象，洞察事物的本质，形成更深刻、更全面的认知，从而在纷繁复杂的世界中保持清醒的头脑。&lt;/p&gt;
&lt;h3&gt;其次，读书是拓展生命宽度、培养同理心的重要方式。&lt;/h3&gt;
&lt;p&gt;文字是跨越时空的桥梁。通过阅读文学作品、传记故事，我们可以走进不同时代、不同地域、不同人物的生命。我们能体验莎士比亚笔下的爱恨情仇，感受雨果描绘的人间疾苦，跟随史铁生的文字思考生与死。我们得以“亲历”那些我们未曾经历的人生，理解那些与我们背景迥异的人们的喜怒哀乐。这种“代入式”的体验，极大地拓展了我们的生命经验，培养了我们的同理心和包容性，让我们更能理解人性的复杂与多样，也更能关怀我们身处的社会。&lt;/p&gt;
&lt;h3&gt;再者，读书是锻炼思维能力、提升个人心智的绝佳训练。&lt;/h3&gt;
&lt;p&gt;阅读，尤其是阅读有深度的书籍，绝非被动接收信息。它需要我们主动思考、分析、质疑、联想。我们需要跟上作者的思路，理解其论证逻辑，评价其观点，甚至与之进行思想的“辩论”。这个过程，无形中锻炼了我们的批判性思维、逻辑分析能力和独立思考能力。同时，沉浸式的阅读有助于培养我们的专注力，对抗这个时代的浮躁与注意力涣散。长期阅读更能塑造我们的心性，使我们内心更丰盈、更沉静。&lt;/p&gt;
&lt;h3&gt;此外，读书是提升语言能力、丰富精神世界的基础。&lt;/h3&gt;
&lt;p&gt;书籍是语言的宝库。阅读优秀的作品，能让我们接触到精准、优美、富有表现力的语言。潜移默化中，我们的词汇量得以扩充，语感得以提升，写作和表达能力也随之提高。更重要的是，书籍为我们提供了丰富的精神食粮。在阅读中，我们与伟大的灵魂对话，获得思想的启迪；我们找到情感的共鸣，得到心灵的慰藉；我们汲取智慧和力量，对抗生活的迷茫与空虚。阅读让我们的内心世界更加广阔、更加深邃。&lt;/p&gt;
&lt;h3&gt;最后，读书是为了更好地认识自己，寻找人生的意义。&lt;/h3&gt;
&lt;p&gt;在阅读中，我们不仅认识世界，也在不断反观自身。他人的故事、智者的思考，如同一面面镜子，映照出我们自己的思想、情感、困惑与渴望。通过与书本的对话，我们得以进行更深入的自我反思，探索“我是谁”、“我从哪里来”、“要到哪里去”这些根本性问题。书籍或许不能直接给出答案，但它们提供了无数种可能性、视角和思考路径，引导我们去寻找属于自己的答案和人生的意义。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;总而言之，读书并非仅仅为了获取信息或消遣娱乐，它更是一种深刻的生命体验和心智活动。在快节奏的时代，坚持阅读，意味着选择了一种更深入、更从容的生活方式。它帮助我们系统地认识世界，温润地理解他人，有效地提升自我，最终，是为了成为一个更完整、更智慧、更有深度的人。拿起书本吧，那里面有广阔的世界，也有未知的自己。&lt;/strong&gt;&lt;/p&gt;</content:encoded><category>thoughts</category><category>思考</category></item><item><title>成年人的悲哀，就是活成了自己曾经最讨厌的样子</title><link>https://yourdomain.com/blog/202503282209/</link><guid isPermaLink="true">https://yourdomain.com/blog/202503282209/</guid><description>分类：thoughts | 标签：思考</description><pubDate>Fri, 28 Mar 2025 14:09:27 GMT</pubDate><content:encoded>&lt;p&gt;上网无聊闲逛，看见了&lt;strong&gt;清雅阁&lt;/strong&gt;的一篇博文&lt;a href=&quot;https://www.puresky.top/archives/o1LOlODS&quot;&gt;《在所有失去的人中，我最怀念当初的我自己》&lt;/a&gt;，看到标题颇有感悟，不禁陷入了沉思……&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;一扇半开的旧木窗，窗外是褪色的黄昏，风掀起泛黄的日记纸页，散落在斑驳的窗台上。一本翻开的相册里，角落的照片微微卷边——那是多年前的操场、阳光和空荡的秋千。玻璃窗的倒影中，隐约有树枝摇曳，像时间划过的裂痕。桌角摆着一盏熄灭的铜制小灯，灯芯旁落着薄灰，而窗外渐暗的天色正一寸寸吞没最后的光。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;我时常站在镜子前，看着那个熟悉又陌生的人影。他的眼神不再清澈如泉，他的笑容不再毫无顾忌，他的背影里藏满了欲言又止的故事。原来，最遥远的距离，不是生与死，而是我与曾经的自己之间，隔着一整个成长的荒原。&lt;strong&gt;从一个天真烂漫的少年，到一个麻木认命的成年人，这种转变所带来的痛苦，是每个人都要经历的。&lt;/strong&gt;&lt;/p&gt;
&lt;h3&gt;一、白纸般的生命底色&lt;/h3&gt;
&lt;p&gt;最初的自己，像一张未被涂抹的白纸。那时的快乐很简单：一片落叶能成为书签，一阵风能吹散烦恼，一句“明天见”就足以点亮整颗心。我曾赤着脚在田野间奔跑，追逐蝴蝶，仰望星空，以为世界是一颗永远甜美的糖果。那时的眼泪是透明的，委屈了就哭，开心了就笑，连恨都带着天真的莽撞。朋友说：“你单纯得像一张白纸。”我却觉得，白纸多好啊，每一笔未来都能自己描画。&lt;/p&gt;
&lt;p&gt;后来，这张纸被生活的笔狠狠划破。初入社会时，我像一只笨拙的雏鸟，被人嘲笑“太傻”。有人故意刁难我，有人将我的真诚当作软弱。我躲在洗手间里哭，却擦干眼泪继续微笑。我以为只要不反抗，世界总会还我温柔——直到某天，我发现自己的白纸早已沾满了别人的指印。&lt;/p&gt;
&lt;h3&gt;二、被驯服的刺猬&lt;/h3&gt;
&lt;p&gt;成年后的我，学会了一身铠甲。我开始计算得失，权衡利弊，用“成熟”的名义收起棱角。面对不公，我沉默；面对虚伪，我附和；甚至面对伤害，我也能笑着说“没关系”。有人夸我稳重，我却知道，这份稳重不过是把真心锁进了抽屉。&lt;/p&gt;
&lt;p&gt;像一只刺猬，我把自己裹进坚硬的壳里。有人靠近，我便竖起尖刺，生怕泄露一丝脆弱。可夜深人静时，我又怀念那个敢爱敢恨的自己：他会为一句承诺等整个下午，会因一场离别哭肿眼睛，会毫无保留地拥抱世界。如今的“懂事”，更像一场精心策划的表演。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;我们很难坚持自己的理想，也很难在现实压力下找到自我实现的出路。最终，我们却活成了当初我们最讨厌的样子。然后只能安慰自己，说这就是成年人的样子。&lt;/strong&gt;&lt;/p&gt;
&lt;h3&gt;三、弄丢的拼图&lt;/h3&gt;
&lt;p&gt;某天整理旧物，翻出一沓泛黄的信纸。少年时代的字迹歪歪扭扭：“我要永远勇敢真诚！”突然眼眶一热。现在的我，拥有了曾经渴望的薪水、房子和体面，却弄丢了最珍贵的那块拼图——那个不怕摔跤、不惧孤独、不懂算计的自己。&lt;/p&gt;
&lt;p&gt;成年人的世界大雪纷飞。我们一边流泪一边赶路，一边得到一边失去。有人问我：“如果能回到过去，你想改变什么？”我摇摇头。我不愿篡改任何伤疤，因为它们教会我如何行走于荆棘。但我多想抱一抱当初的自己，对他说：“别怕，你未来会变得强大——但请别弄丢眼里的光。”&lt;/p&gt;
&lt;h3&gt;四、与自己的重逢&lt;/h3&gt;
&lt;p&gt;如今的我，开始学着撕下面具。在咖啡店主动和陌生人微笑，为一部电影放肆大哭，偶尔任性地拒绝“合群”。原来卸下铠甲并不难，难的是承认：&lt;strong&gt;我们怀念的不仅是曾经的单纯，更是那份敢于真实的勇气。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;人生是一场单程旅途，我们注定要与无数个自己告别。但总有些东西值得坚守：比如流泪时不遮掩伤口的坦荡，比如受伤后仍相信美好的天真。那个最初的自己从未真正消失——他只是躲在岁月的褶皱里，等某天，我们鼓起勇气说：“你好，我们重新认识吧。”&lt;/p&gt;
&lt;p&gt;窗外的樱花又开了。浴室镜起雾时，我写下“怂”字，水珠滑落后却变成“从心”。真正的成长，不是活成标准答案里的“成功模板”，而是像《狂人日记》里质疑“从来如此便对吗”的清醒者， &lt;strong&gt;在妥协与坚持的夹缝中，守护住最初让自己眼眶发热的东西。&lt;/strong&gt; 那些深夜改方案时听的摇滚乐、孩子藏在书包底的恐龙贴纸，都是荒芜现实里倔强的绿芽——提醒我们：成为“讨厌的样子”不是终点，而是觉醒的起点。&lt;/p&gt;</content:encoded><category>thoughts</category><category>思考</category></item><item><title>人生如天气</title><link>https://yourdomain.com/blog/202503272237/</link><guid isPermaLink="true">https://yourdomain.com/blog/202503272237/</guid><description>分类：thoughts | 标签：思考</description><pubDate>Thu, 27 Mar 2025 14:37:15 GMT</pubDate><content:encoded>&lt;p&gt;今天祭祖，路上购买祭品相关物品时，偶听路人对人生和天气的见解：”昨日风起云涌，今朝艳阳高照，这人生啊就如这天气。”这样的比喻让我不得不思考，生活的起伏如同自然界的变化，时而暴风骤雨，时而风和日丽。&lt;/p&gt;
&lt;p&gt;生命本是一朵游云，在晨昏交替中舒展着万千姿态。它时而聚成羊脂玉般的絮团，时而散作游丝般的薄纱，以永恒的变幻演绎着天地间最动人的舞剧。正如我们的思绪总在明暗交织中流转——当困顿如雾霭笼罩心原，暴雨般的情绪会裹挟着雷鸣电闪席卷而来；而当感恩的晨风拂过心田，又会在碧空下舒展成金色的麦浪。&lt;/p&gt;
&lt;p&gt;昨日的光景恰似这般。晨光初现时，我的世界澄澈如水晶杯盛满山泉，连呼吸都带着薄荷的清凉。可正午未至，骤雨已敲碎这份安宁，冰雹般的烦忧砸向心窗，连檐角的风铃都噤了声。直到暮色四合时，天边却忽现鎏金晚霞，雨后的泥土蒸腾着草木香，恍然惊觉：原来所有的雷暴，都在为彩虹铺设天阶。&lt;/p&gt;
&lt;p&gt;命运总爱编织迷雾般的谜题。我们或许永远读不懂那些断裂的篇章、错位的韵脚，但岁月会在褶皱里悄悄藏下锦囊：暴雨教会我们锻造舟楫，寒霜赠予我们淬炼锋芒，就连看似徒劳的等待，也终将在某个黎明兑现成破土的绿芽。这或许就是生命最狡黠的浪漫——它以无常为笔，将我们的伤痕勾勒成星辰的轨迹。&lt;/p&gt;
&lt;p&gt;“人这一世，来了又去，谁说得清爽！”这种无法预测的命运就像潮起潮落，每个人都在其中经历着自己的风雨。你看那历经雷击的老松，年轮里沉淀着琥珀色的光；你看那迁徙万里的候鸟，羽翼上凝结着破空的歌。当命运的季风再度袭来时，让我们像海岸的礁石般舒展怀抱，因为每一道浪痕，都是光阴馈赠的勋章。&lt;/p&gt;
&lt;p&gt;有时，我们无法理解生活，无法理解它的曲折，但它总能给我们带来教训、力量和智慧，让我们继续前行。这就是生活变得有趣和美丽的原因。&lt;/p&gt;
&lt;p&gt;星云大师说得妙，人生就该像四季：春天要活得像花开，夏天要像太阳般热乎，秋天得学果实沉甸甸，冬天要像雪地般敞亮。起风了就把衣领竖起来，下雨了就撑开伞，晴天记得晒被子——日子嘛，总归要过下去的。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;每一个人的命运都独一无二，而我们每个人都在这幅画卷中，绘制着自己的未来。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;mark&gt;谨以此文与君共勉&lt;/mark&gt;&lt;/p&gt;</content:encoded><category>thoughts</category><category>思考</category></item><item><title>SVG 签名动画制作：从设计到动画实现全流程解析</title><link>https://yourdomain.com/blog/202503192307/</link><guid isPermaLink="true">https://yourdomain.com/blog/202503192307/</guid><description>分类：tech | 标签：折腾</description><pubDate>Wed, 19 Mar 2025 15:07:40 GMT</pubDate><content:encoded>&lt;p&gt;{{&amp;lt; alert “warning” “重要通知” “true” &amp;gt;}}
因站点重构，意为简化站点，故将签名动画移除
{{&amp;lt; /alert &amp;gt;}}&lt;/p&gt;
&lt;p&gt;作为一个曾经被 SVG 劝退 800 次的手残选手，我懂你！每次看到别人博客里那些会自己画出来的炫酷签名，感觉就像在裸考数学考场上看别人提前交卷——弱小、可怜、又柠檬精附体。&lt;/p&gt;
&lt;p&gt;直到最近在 &lt;a href=&quot;https://www.lxchapu.com/&quot;&gt;柃夏 chapu&lt;/a&gt; 的博客中，发现一篇文章写的很好，所以动手制作了一个属于自己的动态签名，并将制作过程记录下来。&lt;/p&gt;
&lt;h2&gt;一、前期准备&lt;/h2&gt;
&lt;p&gt;1.​选择字体&lt;/p&gt;
&lt;p&gt;访问 &lt;a href=&quot;https://fonts.google.com/&quot;&gt;fonts.google.com&lt;/a&gt;，挑选喜欢的字体（大部分仅支持英文，中文需自备字体文件），记下字体名称备用。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://i.p-i.vip/89/20250915-68c7f8cbc0e97.webp&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;2.​生成 SVG 代码&lt;/p&gt;
&lt;p&gt;前往 &lt;a href=&quot;https://danmarshall.github.io/google-font-to-svg-path/&quot;&gt;Google Font to Svg Path&lt;/a&gt; 网站，选择上述字体，在 text 中输入文字，勾选 union 选项，取消勾选 Non - Scaling Stroke，复制生成的 SVG 代码。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://i.p-i.vip/89/20250915-68c7f8cbda34f.webp&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;h2&gt;二、调整 SVG 画布&lt;/h2&gt;
&lt;p&gt;1.​初步调整&lt;/p&gt;
&lt;p&gt;将 SVG 代码粘贴到&lt;a href=&quot;https://codepen.io/Seer-Su-the-vuer/pen/PwoQBpV&quot;&gt;动画&lt;/a&gt;网页中，若画布小无法完整显示签名，修改 &lt;code&gt;viewBox&lt;/code&gt; 属性。例如将 &lt;code&gt;viewBox=&quot;0 0 177.295 72.999&quot;&lt;/code&gt;改为 &lt;code&gt;viewBox=&quot;-1 1 300 90&quot;&lt;/code&gt;。&lt;/p&gt;
&lt;p&gt;2.​进一步调整（如需要）​&lt;/p&gt;
&lt;p&gt;若仍不理想，可借助 Adobe Illustrator 等专业工具调整 SVG 的宽高。&lt;/p&gt;
&lt;p&gt;三、添加动画和样式&lt;/p&gt;
&lt;p&gt;1.​添加 CSS 样式和动画&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;在 CSS 中为 SVG 签名添加样式和动画，代码如下：&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;.animated-signature&lt;/span&gt;&lt;span&gt; path&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  stroke-dasharray&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;2400&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  stroke-dashoffset&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;2400&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  fill&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;transparent&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  animation&lt;/span&gt;&lt;span&gt;: drawSignature &lt;/span&gt;&lt;span&gt;8&lt;/span&gt;&lt;span&gt;s&lt;/span&gt;&lt;span&gt; linear&lt;/span&gt;&lt;span&gt; infinite&lt;/span&gt;&lt;span&gt; both&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  stroke-width&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;2&lt;/span&gt;&lt;span&gt;px&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  stroke&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;black&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;@keyframes&lt;/span&gt;&lt;span&gt; drawSignature&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  0%&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    stroke-dashoffset&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;2400&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  15%&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    fill&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;transparent&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  35%&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  75%&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    stroke-dashoffset&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;0&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    fill&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;black&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  90%&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  to&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    stroke-dashoffset&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;2400&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    fill&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;transparent&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;2.嵌入 html&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;在 HTML 中嵌入 SVG 代码时，删除多余属性和标签，保留 &lt;code&gt;viewBox&lt;/code&gt; 属性，其他属性根据是否影响渲染决定是否删除。&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;&amp;lt;&lt;/span&gt;&lt;span&gt;svg&lt;/span&gt;&lt;span&gt; width&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;300&quot;&lt;/span&gt;&lt;span&gt; height&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;100&quot;&lt;/span&gt;&lt;span&gt; viewBox&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;-1 1 300 90&quot;&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    &amp;lt;&lt;/span&gt;&lt;span&gt;g&lt;/span&gt;&lt;span&gt; stroke-linecap&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;round&quot;&lt;/span&gt;&lt;span&gt; fill-rule&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;evenodd&quot;&lt;/span&gt;&lt;span&gt;&amp;gt;...&amp;lt;/&lt;/span&gt;&lt;span&gt;g&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;lt;/&lt;/span&gt;&lt;span&gt;svg&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&quot;https://i.p-i.vip/89/20250915-68c7f8cc02d5e.gif&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;</content:encoded><category>tech</category><category>折腾</category></item><item><title>[转载]一位父亲的灵魂叩问：我们究竟在为什么而养育孩子？</title><link>https://yourdomain.com/blog/202503041922/</link><guid isPermaLink="true">https://yourdomain.com/blog/202503041922/</guid><description>分类：thoughts | 标签：思考</description><pubDate>Tue, 04 Mar 2025 11:22:18 GMT</pubDate><content:encoded>&lt;p&gt;&lt;span&gt;本文部分内容转载自微信公众号&lt;/span&gt;&lt;a href=&quot;https://mp.weixin.qq.com/s/giVeS4kULspnKvqAz5DJPw&quot;&gt;黄杨 ME&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;今天想与各位准父母和初为人父的伙伴们分享一位特别父亲的思考——&lt;a href=&quot;https://sive.rs/pa&quot;&gt;Derek Sivers&lt;/a&gt;（巧合的是，这也是我和他为数不多且唯一的一个共同点）。他近期发布了一篇深度长文&lt;a href=&quot;https://sive.rs/pa&quot;&gt;《Parenting : Who is it really for?》（直译为《养育子女：究竟为了谁？》）&lt;/a&gt;已在全网引发热议，评论区已有 600 余条留言，许多精彩的长评都值得细细品读。&lt;/p&gt;
&lt;p&gt;这篇文章带来的思想冲击力远超预期，以至于我在反复研读后仍觉震撼。虽然原本计划挑选部分高赞评论与诸位共鉴，但实在难以取舍——每条留言都像是一面镜子，映照出不同文化背景下父母对亲子关系的深层思考。因此最诚恳的建议，是各位亲自移步原文 sive.rs/pa 感受这场思想盛宴。&lt;/p&gt;
&lt;p&gt;考虑到语言门槛，我特别借助「沉浸式翻译插件」辅助及人工润色完成译介工作。尽管尽力确保译文准确传达原意，但受限于个人语言造诣，若您在阅读中感受到任何生硬之处，恳请不吝赐教。对于具备良好英文功底的读者，我强烈推荐直接阅读作者原汁原味的思考——毕竟有些灵魂的震颤，唯有母语才能完美承载。&lt;/p&gt;
&lt;p&gt;当您合上这篇探讨生命本质的育子哲学时，或许会与我们产生同样的共鸣：那些看似理所当然的教养责任背后，其实都藏着对生命最深邃的叩问。&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;&lt;strong&gt;下面是文章正文：&lt;/strong&gt;&lt;/p&gt;
&lt;h1&gt;Parenting : Who is it really for?&lt;/h1&gt;
&lt;p&gt;自从五年前我儿子出生以来，我每周至少花三十个小时和他在一起，一对一地全神贯注地照顾他。但我以前从未写过关于育儿的文章，因为这是一个敏感话题——太容易被误解。&lt;/p&gt;
&lt;p&gt;那我今天为什么要写这件事呢？因为我意识到，我在育儿上为他做的一切也是为了我自己。这个想法值得分享。&lt;/p&gt;
&lt;p&gt;以下是我自儿子出生以来一直为他做的事情：&lt;/p&gt;
&lt;h3&gt;培养持久的专注力（Cultivating a long attention span）&lt;/h3&gt;
&lt;p&gt;不管他现在在做什么，此刻才是最重要的，所以我会鼓励他一直做下去。我从来不说“快点，我们走！”&lt;/p&gt;
&lt;p&gt;我们会去海边或森林，花半天时间用树枝和沙子做东西。&lt;/p&gt;
&lt;p&gt;其他家庭来游乐场玩上二三十分钟，而我们却在那里待上几个小时。没有家庭会像我们这样玩，其他大人们会觉得很无聊。&lt;/p&gt;
&lt;p&gt;当然，作为成年人我也会想要做点别的事情，但我会控制自己，把注意力放回当下。&lt;/p&gt;
&lt;h3&gt;进入他的世界（Entering his world）&lt;/h3&gt;
&lt;p&gt;我的生活中有很多想要去做的事情，但当我和他在一起时，我停掉一切，关掉电话，关掉电脑。&lt;/p&gt;
&lt;p&gt;我试着用他的眼光来看待周围的事物，站在他的角度来理解问题。当他不开心的时候，我就会试着回忆一下自己在他这个年纪的时候是什么样子的，并与他产生共鸣。&lt;/p&gt;
&lt;p&gt;当他编了一个故事时，我就进到他编造的世界。如果他说我们是巴黎的猫，那我们就是巴黎的猫。牛头怪在追我们？我们就一起跑。&lt;/p&gt;
&lt;p&gt;当然，我很想看手机。我们大多数人现在都有这种瘾。但我会问自己：“哪个更重要？”然后我就会关掉手机。&lt;/p&gt;
&lt;h3&gt;开拓认知疆界（Broadening his inputs）&lt;/h3&gt;
&lt;p&gt;我希望尽可能的拓宽他的认知视野。&lt;/p&gt;
&lt;p&gt;我们尽可能去不同的森林、海滩、山脉和城镇玩耍，触摸和嗅闻我们能接触到的一切。&lt;/p&gt;
&lt;p&gt;我常在家播放非常多元的背景音乐。当我们在家里玩的时候，他会听波斯传统音乐、印度古典音乐、60 年代爵士乐、glitch、巴托克、史蒂夫 - 汪达（72 至 76 年）、大量巴赫、保加利亚合唱团的音乐，或者其他音乐。&lt;/p&gt;
&lt;p&gt;从他三岁起，我们就买了新西兰交响乐团的季票，而且从未错过任何一场音乐会。我带他去看歌剧《卡门》，他从头到尾都目不转睛。&lt;/p&gt;
&lt;p&gt;我们每周都会从图书馆借几本新书，每晚一起阅读一个小时。&lt;/p&gt;
&lt;p&gt;我们看的电影种类繁多，但总是会从头看到尾，这样他就能看到完整的故事情节。对于迪士尼的大片，我们会看葡萄牙语或中文翻译版。&lt;/p&gt;
&lt;h3&gt;And now, my point:&lt;/h3&gt;
&lt;p&gt;我之所以最终写下这篇文章，是因为我意识到，&lt;strong&gt;我做这些事情既是为了他，也是为了我自己。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;通过培养他长久的专注力，我也在培养自己的专注力。&lt;/p&gt;
&lt;p&gt;通过进入他的世界，我也打开了自己，就像冥想一样。&lt;/p&gt;
&lt;p&gt;通过扩宽他的输入，我同时也在扩宽自己。&lt;/p&gt;
&lt;p&gt;我以为我是无私的。但实际上，就像我们认为无私的大多数事情一样，这些事情对我和他都有好处。&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;PS：这篇读下来特别有共鸣！原来陪孩子玩还能治自己的手机瘾。作者每周 30 小时全身心陪伴，听着吓人，细想才发现重点不在时长，而是那份「活在当下」的劲头。带孩子堆沙堡能堆到日落，这不光是培养孩子专注力，更是治好了我们成年人三分钟刷次朋友圈的毛病。最戳心的是那句「迪士尼电影看外语版不为学语言，就为让孩子知道世界有很多打开方式」&lt;strong&gt;——当爸妈的何尝不是跟着重新认识了世界？那些我们觉得幼稚的游戏，原来都是自我疗愈的良药。&lt;/strong&gt; 养孩子这事儿，真像照镜子，照见自己最需要成长的地方。到底是我们陪伴孩子成长，还是孩子让我们成长，值得我们每个父母好好思考。&lt;/p&gt;</content:encoded><category>thoughts</category><category>思考</category></item><item><title>暂停键与重启键之间</title><link>https://yourdomain.com/blog/202502282346/</link><guid isPermaLink="true">https://yourdomain.com/blog/202502282346/</guid><description>分类：thoughts | 标签：思考</description><pubDate>Fri, 28 Feb 2025 15:47:06 GMT</pubDate><content:encoded>&lt;p&gt;时钟拨回到 2024 年 10 月，我在离职协议上签下名字。办公楼的玻璃幕墙正将暮色折射成碎片，如同公司财报上那些断崖式下跌的曲线。当同事们用保温杯装枸杞的年纪撞上行业寒冬，我选择主动松开紧握十年的安全绳——以陪伴幼子的名义。可不成想，这一陪就到了现在，已经过去快半年了，成了一个全职的奶爸。&lt;/p&gt;
&lt;p&gt;其实不管别人是羡慕还是看笑话，对我来说都不重要，因为真正在意我结果的人也只有自己跟少数亲友，而人生最后只有自己能对自己负责。&lt;/p&gt;
&lt;p&gt;十几岁的时候，我们往往还没有建立起足够的觉悟，总会认为未来有无限的时间。但到了四十岁，一个充满复杂性和独特魅力的年龄阶段，常被称为人生的”中场”。这个年龄如同秋日清晨的太阳，既带着盛夏未褪的热度，又透着冬日将至的清醒。&lt;/p&gt;
&lt;p&gt;它让人在现实与理想之间找到新的支点，在承担责任的同时，开始真正懂得如何为自己而活。它既是成熟的象征，也是转折的起点。&lt;/p&gt;
&lt;p&gt;所以，当我再次坐到电脑旁，内心有十足的觉悟，汽笛重新拉响，终于到了重新出发的时候了。&lt;/p&gt;
&lt;p&gt;近几年，随着年龄增长，我时常会感到记忆的能力在衰退。那些留下深刻印象，从而被写入深度记忆收藏库的好瞬间，变得越来越稀缺。记忆的沙漏确实在加速流逝，那些曾以为刻骨铭心的项目节点渐渐模糊成马赛克。&lt;/p&gt;
&lt;p&gt;自成年起，我一直是一个只和老朋友一起玩的人，和新朋友常常需要先经过以年计的时间，充分预热，才能稍微熟络发展一下。过去的一年，最令我感到惊喜的事情之一，就是建立、发展了很多新的友谊。这份喜悦不只关乎友谊本身，更在于我追越了过去的自己。&lt;/p&gt;
&lt;p&gt;前些天陪孩子背课文，突然瞥见”知足常乐”四个字。想起小时候总被说要“知足”，但大人的“知足”总是扭曲的解释，彷彿在说“你已经有这么多了，为什么还不满足？”是的，要强迫我接受所有他们认为对的事情；长大之后我终于理解了“知足”的真正意义：“知道什麽对自己足够，同时也知道什麽对自己还不够”。&lt;/p&gt;
&lt;p&gt;在一个没有希望的时代，依然不要停止自我审视。因为往往是在我们意识到「感觉也没有一定要活下去的理由」之后，我们才更接近值得一过的人生。&lt;/p&gt;
&lt;p&gt;写到结尾，必须要说，今年后半段其实跟很多朋友聊过现代性失灵的话题。从某些大一点的角度，我们都认为世界和时代正变得愈发黯淡、绝望。&lt;/p&gt;
&lt;p&gt;历史不会给我们发复活卡，但允许勇敢者在中场休息时更换轨道。当消费主义制造的肾上腺素消退，裸露出的生命基岩上，或许正蛰伏着超越 996 的另一种存在主义脚本。在一个没有希望的社会里爬行，靠消费主义抚慰伤口，正在成为我们这代人的宿命。&lt;/p&gt;</content:encoded><category>thoughts</category><category>思考</category></item><item><title>哲学：重塑人类认知的底层算法</title><link>https://yourdomain.com/blog/202502241651/</link><guid isPermaLink="true">https://yourdomain.com/blog/202502241651/</guid><description>分类：tech | 标签：思考</description><pubDate>Mon, 24 Feb 2025 08:51:09 GMT</pubDate><content:encoded>&lt;p&gt;在知识付费浪潮中，罗振宇曾提出一个令人深思的论断：成年人读书的核心价值在于认知系统的迭代升级。这个观点如同棱镜般折射出现代人的生存困境——当基础教育构建的认知框架遭遇现实世界的混沌无序，我们突然发现，那些曾经笃信的科学公式无法安抚深夜失眠时的焦虑，宗教箴言难以破解职场竞争的生存密码，就连佛陀的色空之辨也消解不了房贷压顶的窒息感。&lt;/p&gt;
&lt;p&gt;这正是哲学存在的深层意义。它不同于具体学科的知识积累，而是一套能够重构思维底层操作系统的元程序。就像计算机工程师需要理解二进制逻辑才能开发应用软件，哲学训练赋予人类的，是解析世界运行机制的源代码权限。当牛顿用三大定律构建经典物理大厦时，背后是培根归纳法的哲学支撑；当爱因斯坦突破绝对时空观时，暗合着马赫对经验主义的哲学反思。这种思维的重构力量，在古希腊德尔斐神庙”认识你自己”的箴言中就已初现端倪。&lt;/p&gt;
&lt;p&gt;哲学家对第一性原理的执着追问，本质上是在进行人类思维的极限训练。就像围棋选手通过死活题磨砺棋感，苏格拉底的诘问法实质是思维的负重训练。当老子说”道可道非常道”，不是故弄玄虚的谜语，而是在提醒我们：所有具象认知都建立在对本源问题的持续叩问之上。这种思维体操的成果，在当代量子力学领域展现得尤为明显——海森堡的测不准原理，本质上就是哲学家追问”确定性”的现代科学回声。&lt;/p&gt;
&lt;p&gt;现代教育体系制造的知识割裂，在哲学维度得到根本性弥合。经历过高考洗礼的人都深有体会，那些被分割在文理各科的知识模块，就像散落银河的星辰，而哲学恰似引力定律，让离散的知识产生内在联结。当学生困惑于热力学”熵增定律”与生物学”生命负熵”的矛盾时，哲学的系统思维能揭示其内在统一；当量子纠缠挑战经典因果论时，康德的二律背反早已为此预留了思维接口。&lt;/p&gt;
&lt;p&gt;这种认知整合产生的不仅是知识图谱的升级，更是思维维度的跃迁。掌握哲学思维如同获得认知领域的上帝视角，既能深入分子层面的细节解析，又能保持星系尺度的宏观视野。就像顶级战略家既能处理具体战役的沙盘推演，又能把握文明兴衰的历史周期，哲学训练出的思维弹性，使现代人得以在专业分工与跨界创新之间自由切换。&lt;/p&gt;
&lt;p&gt;这种思维革命最终指向人类永恒的命题——幸福。从伊壁鸠鲁学派到存在主义，哲学史上所有思想流派的终极关怀，本质上都是为不确定的人生寻找确定性支点。但现代哲学给出的答案颇具辩证智慧：真正的确定性不在于消除不确定性，而在于建立与不确定性共存的认知框架。这就像海明威笔下的老人，明知海洋充满未知，却依然保持出航的勇气，这种认知层面的豁达，正是哲学赋予现代人的精神锚点。&lt;/p&gt;
&lt;p&gt;当我们凝视维特根斯坦的哲学终结论时，或许会发现一个吊诡的真相：哲学的价值不在于提供标准答案，而在于永远保持追问的姿态。这种持续自我更新的认知系统，恰似普罗米修斯盗取的火种，既照亮前行之路，也注定要承受灼烧之痛。但正是这种痛苦的重构过程，让人类在 ChatGPT 横行的时代，依然保有不可替代的思维尊严。&lt;/p&gt;</content:encoded><category>tech</category><category>思考</category></item><item><title>深夜自救指南：给「睡不着星人」的科学安眠处方</title><link>https://yourdomain.com/blog/202502182123/</link><guid isPermaLink="true">https://yourdomain.com/blog/202502182123/</guid><description>分类：life | 标签：生活</description><pubDate>Tue, 18 Feb 2025 13:23:57 GMT</pubDate><content:encoded>&lt;p&gt;我们都有过这样的经历——躺在床上，盯着天花板，拼命想睡着。时钟滴答滴答地过了午夜，然后是凌晨 2 点，然后是凌晨 4 点。早晨来得太快，我们靠咖啡因和意志力熬过一天。如果这听起来很熟悉，那你并不孤单。数百万人都在与失眠作斗争，尽管他们尽了最大的努力，但还是辗转反侧。或者还在熬夜为着各自的理想或者生计奋战着？&lt;/p&gt;
&lt;p&gt;刚在 B 站看了窦文涛的&lt;a href=&quot;https://www.bilibili.com/video/BV1aP411y71y/?vd_source=7cc72851b5e4b54795084e8608e529d9&quot;&gt;《夜深了，怎么睡个好觉……》&lt;/a&gt;，想想当初的自己，曾经也是一个长达近 20 年的习惯性失眠患者，看过不少医生，期间竟然有医生给我诊断出“人格分裂”，我也是一个大写的“服”送给他。我睡眠一直不好，属于那种睡眠比较轻的，有点儿动静就会醒的，从小到大上学没迟到过也算是个优点。所以我那会儿特羡慕那些倒头就睡，还睡到自然醒的，现在想想，其实我也是幸运的，因为现在我几乎每天都能睡个好觉。&lt;/p&gt;
&lt;p&gt;中国睡眠研究会数据显示，我国成年人失眠发生率高达 38.2%，这意味着每 3 个人中就有 1 人在与黑夜对峙。这个令人心惊的数字背后，是无数人在深夜亮起的手机屏幕，是清晨镜中疲惫的黑眼圈，更是当代人无处安放的焦虑。&lt;/p&gt;
&lt;h3&gt;睡眠危机：被偷走的黄金八小时&lt;/h3&gt;
&lt;p&gt;现代人的昼夜节律正在经历前所未有的挑战。凌晨 2 点的朋友圈依然活跃，24 小时便利店的白光刺破夜幕，电子设备的蓝光像无形的绳索将我们捆绑在清醒状态。神经科学研究显示，智能手机屏幕发出的 450nm 短波蓝光，会使褪黑素分泌延迟 40 分钟以上，相当于每天人为制造一小时时差。&lt;/p&gt;
&lt;p&gt;在深圳某互联网公司工作的林然是典型代表。连续三年凌晨 2 点入睡，靠褪黑素软糖维持 5 小时浅睡眠，体检报告上「免疫力下降」「心脏早搏」的警示犹如一记重锤。这不仅是个人健康危机，世界卫生组织早已将「睡眠负债」列为 21 世纪全球流行病。&lt;/p&gt;
&lt;h3&gt;睡眠革命：重建昼夜节律的四个维度&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;光环境管理：日落后将室内灯光调至 2000K 以下暖色调，睡前 90 分钟佩戴琥珀色防蓝光眼镜。哈佛医学院实验证明，这能使褪黑素分泌量提升 50%&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;体温调节术：日本睡眠医学专家发现，睡前 90 分钟 40℃温水泡脚，可使核心体温下降 0.3℃，触发睡眠启动机制。配合手脚露出被子的「散热睡姿」，能缩短入睡时间 37%&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;认知脱敏训练：建立「床=睡眠」的条件反射，若卧床 20 分钟未入睡，立即起身进行低刺激活动。美国睡眠基金会建议阅读纸质书时保持灯光低于 30 瓦&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;呼吸重构法：4-7-8 呼吸法（吸气 4 秒 - 屏息 7 秒 - 呼气 8 秒）重复三次，能激活副交感神经。斯坦福大学研究显示，此法可使心率降低 15bpm，焦虑水平下降 40% &lt;strong&gt;（我就是使用的这个方法，效果显著）&lt;/strong&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;睡眠文明：与自我和解的艺术&lt;/h3&gt;
&lt;p&gt;在哥本哈根诞生全球首个「睡眠友好型社区」，街道照明智能调节色温。这些创新揭示：优质睡眠不仅是生理需求，更是文明程度的标尺。&lt;/p&gt;
&lt;p&gt;当我们为每个辗转反侧的夜晚寻找解药时，或许更应聆听身体的本真诉求。放下对「秒睡」的执念，接纳偶尔的失眠波动，在黑暗中共处中获得真正的安宁。正如睡眠科学家沃克所说：「善待睡眠，就是善待醒着的自己。」此刻，不妨关闭屏幕，让意识沉入温柔的夜色，明天太阳升起时，自会遇见神清气爽的晨光。&lt;/p&gt;</content:encoded><category>life</category><category>生活</category></item><item><title>春节档观影记录</title><link>https://yourdomain.com/blog/202502111636/</link><guid isPermaLink="true">https://yourdomain.com/blog/202502111636/</guid><description>分类：life | 标签：电影</description><pubDate>Tue, 11 Feb 2025 08:36:58 GMT</pubDate><content:encoded>&lt;p&gt;闲来无事，就把春节档看过的影片记录一下。 &lt;strong&gt;首先，所有评论仅代表个人观点。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;{{&amp;lt; figure mode=“poster” images=“&lt;a href=&quot;https://p0.pipi.cn/mediaplus/bigdata_mmdb_mmdbtask/cdf05c146e39a1a2193de2f1b6b6efcc1f47f.jpg?imageMogr2/thumbnail/2500x2500%3E,https://p0.pipi.cn/mmdb/25bfd687b53339d23c50c8588067ec0d47e39.jpeg?imageMogr2/thumbnail/2500x2500%3E&quot;&gt;https://p0.pipi.cn/mediaplus/bigdata_mmdb_mmdbtask/cdf05c146e39a1a2193de2f1b6b6efcc1f47f.jpg?imageMogr2/thumbnail/2500x2500%3E,https://p0.pipi.cn/mmdb/25bfd687b53339d23c50c8588067ec0d47e39.jpeg?imageMogr2/thumbnail/2500x2500%3E&lt;/a&gt;” &amp;gt;}}&lt;/p&gt;
&lt;h3&gt;&lt;a href=&quot;https://movie.douban.com/subject/34780991/&quot;&gt;哪吒之魔童闹海&lt;/a&gt; (2025)&lt;/h3&gt;
&lt;p&gt;首先恭喜《哪吒之魔童闹海》登上中国影史票房冠军！&lt;/p&gt;
&lt;p&gt;没有凑热闹人多的时候去看，在年初八错峰去看的，带着俩小朋友，看完之后很开心。说实话，确实好看，一代人有一代人心目中的哪吒，似乎在每个时代哪吒的故事都会被重新演绎，而饺子导演的这两部作品就是属于我家俩宝的时代，让我看到了这个时代的哪吒，不变的是对命运和桎梏的反抗，对自己人生的掌握，改变的是对亲情、友情的深入挖掘。总而言之，《哪吒 2》给我带来了一场超乎想象的视听与情感盛宴。我相信《哪吒 2》会成为电影史上的又一经典，被更多观众铭记，激励一代又一代的人勇敢面对生活中的挑战，守护心中的爱与正义。&lt;/p&gt;
&lt;p&gt;{{&amp;lt; figure mode=“poster” images=“&lt;a href=&quot;https://p0.pipi.cn/mediaplus/friday_image_fe/0fa33452d8a52915d501e8f6303ed09e6c9da.jpg?imageMogr2/thumbnail/2500x2500%3E,https://p0.pipi.cn/mediaplus/friday_image_fe/0fa334fc4ea6e34948ce63fb68b9661683181.jpg?imageMogr2/thumbnail/2500x2500%3E&quot;&gt;https://p0.pipi.cn/mediaplus/friday_image_fe/0fa33452d8a52915d501e8f6303ed09e6c9da.jpg?imageMogr2/thumbnail/2500x2500%3E,https://p0.pipi.cn/mediaplus/friday_image_fe/0fa334fc4ea6e34948ce63fb68b9661683181.jpg?imageMogr2/thumbnail/2500x2500%3E&lt;/a&gt;” &amp;gt;}}&lt;/p&gt;
&lt;h3&gt;&lt;a href=&quot;https://movie.douban.com/subject/30181250/&quot;&gt;封神第二部：战火西岐&lt;/a&gt; (2025)&lt;/h3&gt;
&lt;p&gt;本来也没有对这部影片抱什么希望，纯粹是为了打发时间。&lt;/p&gt;
&lt;p&gt;趁着年初三孩子跟老婆串门，自己去影院看了这部影片，我对《封神》的印象还停留在儿时的《封神演义》，现在国内的大部分商业片除了华丽的画面，内容基本没有什么可圈可点的，现在只要是个明星都敢去导个电影来，实在是不吐不快，故事胡编乱造，生拉硬扯，无非就是拉些流量明星来凑一出闹剧，再收割一波韭菜。&lt;/p&gt;
&lt;p&gt;{{&amp;lt; figure mode=“poster” images=“&lt;a href=&quot;https://p0.pipi.cn/mediaplus/friday_image_fe/0fa334528458d3d8a14a18dbb0464d46fd792.jpg?imageMogr2/thumbnail/2500x2500%3E,https://p0.pipi.cn/mediaplus/friday_image_fe/0fa3345249477e15d525cbe0e9ea109e6adc1.jpg?imageMogr2/thumbnail/2500x2500%3E&quot;&gt;https://p0.pipi.cn/mediaplus/friday_image_fe/0fa334528458d3d8a14a18dbb0464d46fd792.jpg?imageMogr2/thumbnail/2500x2500%3E,https://p0.pipi.cn/mediaplus/friday_image_fe/0fa3345249477e15d525cbe0e9ea109e6adc1.jpg?imageMogr2/thumbnail/2500x2500%3E&lt;/a&gt;” &amp;gt;}}&lt;/p&gt;
&lt;h3&gt;&lt;a href=&quot;https://movie.douban.com/subject/36282639/&quot;&gt;唐探 1900&lt;/a&gt; (2025)&lt;/h3&gt;
&lt;p&gt;从 2015 年第一部《唐人街探案》至今，“唐探”这个系列不知不觉竟已走过了 10 年。&lt;/p&gt;
&lt;p&gt;这部影片算是我今年除了《哪吒 2》唯二好评的影片，真没想到 70% 期待拿出 90% 的成品，唐探系列最佳。在唐探的世界里，所谓推理，不过是把重要的细节放大。对我而言，放大的可能是因为一部电影了解一点点关于历史、背景的「彩蛋」，舒适区有零星一点点的拓展。电影是，人也是，得往更广、更深的地方走一步，进一寸有一寸的欢喜。&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;看电影的意义在于，能够从不同视角看待同一个故事，从不同角度看待同一个人，从不同视角看待同一个世界。更多的时候，观影之于我也只是一种消遣放松的方式，是一种对生活的一种重新认识。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;完备的逻辑、感人的剧情、深邃的哲思，都很牛逼，那是人类智慧的产物。但是爱看耍酷，大概是人类的本能吧。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;以上，就是我今年春节档看的电影，希望大家都能有不一样的感悟。&lt;/p&gt;</content:encoded><category>life</category><category>电影</category></item><item><title>从孩子到父母：那些年我们经历的&apos;角色反转&apos;与成长</title><link>https://yourdomain.com/blog/202502102258/</link><guid isPermaLink="true">https://yourdomain.com/blog/202502102258/</guid><description>分类：thoughts | 标签：生活</description><pubDate>Mon, 10 Feb 2025 14:58:32 GMT</pubDate><content:encoded>&lt;p&gt;人生中的大事小情都会在你身上留下印记，或好或坏。有些经历确实能彻底改变你对生活的看法。&lt;/p&gt;
&lt;p&gt;小时候咱们都跟爹妈撂过狠话：“那你们生我干啥？“其实压根没走心，八成就是撒泼耍赖没得到满足时脱口而出的气话。&lt;/p&gt;
&lt;p&gt;当你成为父母后，听到孩子说出类似的话，你才会真正意识到这些话对一个家长来说有多么荒唐。孩子的反应通常会是：“这就是你为我做了这么多之后想对我说的话吗？“但作为父母，我们从来不会把这些话放在心上，也不会真的往心里去。&lt;/p&gt;
&lt;p&gt;想想看，人生的视角真是奇妙啊！我们曾经也是孩子，那时候总觉得父母不理解自己，甚至有时候还会觉得他们有点”不可理喻”。然而，当我们自己也成了父母，经历了生活的种种洗礼，才明白当年的自己是多么天真、任性。那些曾经让我们觉得委屈、愤怒的事情，现在回头看，不过是成长中的小插曲罢了。&lt;/p&gt;
&lt;p&gt;生活就是这样，总是在不经意间教会我们很多东西。尤其是当了父母之后，你会发现自己的心态完全变了。以前可能觉得父母唠叨、管得太多，但现在轮到自己面对孩子时，才懂得那种”为你好”的心情。你会开始理解父母当年的苦心，也会更加感激他们为你付出的一切。&lt;/p&gt;
&lt;p&gt;而更有趣的是，虽然我们知道孩子有时候说气话并不是真心的，但还是会忍不住反思：是不是自己哪里做得不够好？是不是沟通方式需要调整？毕竟，每个孩子都是独立的个体，他们的想法和感受也需要被尊重。所以，当孩子说出那些看似”伤人”的话时，我们其实更多的是心疼，而不是生气。&lt;/p&gt;
&lt;p&gt;人生就是这样一步步走过来的。从孩子到父母，角色的转换让我们学会了换位思考，也让我们更加珍惜家庭之间的爱与包容。或许，这就是所谓的”生命事件”带来的成长吧——真奇妙！当年当孩子的，后来自己当了爹妈，才懂人生每个阶段经历的事儿，真的能让人脱胎换骨。&lt;/p&gt;
&lt;p&gt;&lt;em&gt;P.S. 我家的小孩子们都很聪明，也很努力，但我还是觉得他们的成长经历都不如我。&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;趁着孩子们还未开学，带着孩子们一起去体验了一把滑雪，也是她们长这么大的第一次玩雪。说实话，现在的孩子们太累了，不过，我还是很珍惜与她们一起相处的时光，也很喜欢她们的笑容。&lt;/p&gt;</content:encoded><category>thoughts</category><category>生活</category></item><item><title>RSS 聚合与博客收录：关于尊重与边界的思考</title><link>https://yourdomain.com/blog/202501252327/</link><guid isPermaLink="true">https://yourdomain.com/blog/202501252327/</guid><description>分类：thoughts | 标签：思考</description><pubDate>Sat, 25 Jan 2025 15:28:32 GMT</pubDate><content:encoded>&lt;p&gt;最近看到好友 @mantyke.icu 发布的一篇声明，内心颇受触动。我在 links 页面将所有友链 RSS 聚合本意只是方便自己查看博友的文章，不想在单独使用第三方的阅读器，当然这些并不能作为接口。作为将她的博客收录在自己的 RSS 聚合页面的一方，对她造成困扰，也深感抱歉。同时也意识到这件事情带来的不愉快，也让我对博客收录、RSS 聚合这件事有了更深的思考。&lt;/p&gt;
&lt;h3&gt;关于误解&lt;/h3&gt;
&lt;p&gt;长期以来，我们可能对 RSS 有一些误解：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;RSS 提供 = 默认同意被收录、转载&lt;/li&gt;
&lt;li&gt;公开博客 = 默认同意任何形式的传播&lt;/li&gt;
&lt;li&gt;技术可行 = 道德可行&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;但正如原文所说&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;“RSS 是一种消息来源格式规范，而不是对内容传播、分发的许可。” 这句话让我深思。“&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3&gt;关于尊重&lt;/h3&gt;
&lt;p&gt;在互联网世界里，每个创作者都应该拥有对自己作品的基本控制权。虽然技术上我们可以很容易地将别人的内容收录、转载，但这不代表我们应该在未经允许的情况下这样做。&lt;/p&gt;
&lt;p&gt;几个重要的观点：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;征得同意很重要&lt;/li&gt;
&lt;li&gt;收到撤销请求时应该立即响应&lt;/li&gt;
&lt;li&gt;尊重创作者的选择权&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;反思与改进&lt;/h3&gt;
&lt;p&gt;这件事让我意识到，即便出发点是好的（比如想要分享优质内容），如果方式不当，反而会适得其反。正如原文提到的：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;“两次事件都只走向了创作者更警惕的自我防御，更严密的封闭措施，更紧缩的社交策略。”&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;这绝非任何人想看到的结果。&lt;/p&gt;
&lt;h3&gt;建议的做法&lt;/h3&gt;
&lt;p&gt;对于想要制作博客聚合页面的朋友，我建议：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;事先征得博主同意&lt;/li&gt;
&lt;li&gt;明确说明收录用途&lt;/li&gt;
&lt;li&gt;提供简单的退出机制&lt;/li&gt;
&lt;li&gt;尊重博主的选择&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;结语&lt;/h3&gt;
&lt;p&gt;互联网应该是开放的，但开放不等于没有边界。尊重他人的选择，或许才是真正的”互联网精神”。这件事给了我很大的启发，也希望能引发更多人的思考。&lt;/p&gt;
&lt;p&gt;作为当事人之一，我会立即响应好友的请求，调整自己的做法。毕竟，良好的互联网环境需要每个人的共同维护。&lt;/p&gt;</content:encoded><category>thoughts</category><category>思考</category></item><item><title>我的父母不是英雄，他们和我一样</title><link>https://yourdomain.com/blog/202501162043/</link><guid isPermaLink="true">https://yourdomain.com/blog/202501162043/</guid><description>分类：thoughts | 标签：生活, 思考</description><pubDate>Thu, 16 Jan 2025 12:43:36 GMT</pubDate><content:encoded>&lt;p&gt;那是一个安静的下午，我独自坐在一家舒适的咖啡店里，灯光温暖，背景中传来轻柔的谈话声。我一边喝着咖啡，一边听着一首歌——萨莎·亚历克斯·斯隆的《Older》。起初，这只是空气中的旋律，但后来歌词打动了我：“我的父母不是英雄；他们和我一样。”&lt;/p&gt;
&lt;p&gt;歌词让我措手不及，触动了我的内心深处。我坐在那里，目不转睛，听着这首歌揭示了我从未完全承认的真相。这首歌好像打开了我心中一扇隐藏的门，门后是童年的回忆和对父母的新看法。&lt;/p&gt;
&lt;p&gt;那天下午，咖啡店不再只是个休息的地方，而变成了一个反思的空间——在这里，&lt;strong&gt;我开始真正理解从孩子成长为成年人的旅程，看清父母的真实面目。&lt;/strong&gt;&lt;/p&gt;
&lt;h2&gt;为人父母的无声牺牲&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;“很多时候，我们会忘记自己的父母也曾经年轻过。”&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;作为孩子，我们常常只从角色的角度看待父母——养育者、保护者，有时是管教者。我们很少把他们看作有自己梦想、恐惧和欲望的个体。&lt;/p&gt;
&lt;p&gt;我记得小时候，父亲下班回家很晚，他的肩膀因疲惫而沉重。当时，我对此并不在意。对我来说，那只是父亲的职责。但现在，我意识到那些漫长的时间不是为了他自己，而是为了我们。每一次熬夜，每一次错过放松或个人成长的机会，都是他为了让我们过上更好的生活而做出的选择。&lt;/p&gt;
&lt;p&gt;我母亲的牺牲虽然不为人知，但同样意义深远。她热爱绘画——我们家曾挂满了她的作品。但随着我长大，她的画笔和画布消失了，取而代之的是学校打包的午餐、叠得整整齐齐的衣服和深夜辅导的家庭作业。她从不抱怨，她只是付出。&lt;/p&gt;
&lt;p&gt;为人父母不仅仅是养育孩子，而是要放下自己的一部分，确保孩子得到他们需要的东西。这是一种默默的爱，我们往往直到长大到足以看清它的真谛时才会注意到。&lt;/p&gt;
&lt;h2&gt;用老年人的眼光看待父母之道&lt;/h2&gt;
&lt;p&gt;这首歌完美地捕捉了这种转变。&lt;strong&gt;“我的父母不是英雄；他们和我一样。”&lt;/strong&gt; 小时候，我们常常把父母捧在手心，希望他们是完美的。但随着年龄的增长，我们开始看到他们盔甲上的裂痕——他们的错误、他们的挣扎和他们的人性。&lt;/p&gt;
&lt;p&gt;意识到我们的父母不是超级英雄可能会让人感到震惊。在成长的过程中，我经常质疑他们的决定。为什么他们对某些事情如此严格？为什么他们似乎总是为钱争吵？为什么他们不追逐自己的梦想？&lt;/p&gt;
&lt;p&gt;但我越反思他们的选择，就越能理解。他们并不是想做到完美，而是想做到最好。有时，他们做到最好意味着为了家庭而牺牲自己的愿望。&lt;/p&gt;
&lt;h2&gt;童年的记忆和我的感悟&lt;/h2&gt;
&lt;p&gt;回想起童年，有一段记忆让我印象深刻。当时我大概八岁，班上正在举办才艺表演。我想表演，但没有服装。那天晚上，我妈妈熬夜，用她存下来的碎布缝制了一套衣服。&lt;/p&gt;
&lt;p&gt;当时，我只是因为有衣服穿而兴奋。我没有意识到这背后的努力和爱。但现在，当我回忆起她坐在昏暗的厨房灯光下，工作了一整天后深夜还在缝衣服时，我的看法就不同了。那件衣服不仅仅是一件衣服——它是一件礼物，是她为了让我开心而付出的时间和精力的一部分。&lt;/p&gt;
&lt;p&gt;同样，我记得我父亲放弃了一次晋升，而这需要我们搬家。当时我不明白这个决定有多重要。他只是说：“我认为我们最好留在这里。”现在我意识到留下来意味着我们的稳定，但很可能意味着他要放弃自己想要的东西。&lt;/p&gt;
&lt;p&gt;这些记忆在当时是如此平凡，如今却因父母默默做出的牺牲而熠熠生辉。他们让我想起，每一个看似简单的决定背后都蕴藏着一种深沉的爱，这种爱常常被忽视。&lt;/p&gt;
&lt;h2&gt;宽恕之旅&lt;/h2&gt;
&lt;p&gt;在那家咖啡店里听着《Older》的歌，我意识到成长的一部分就是学会原谅——不仅是原谅父母的不完美，还要原谅我们自己没有早点理解他们。&lt;/p&gt;
&lt;p&gt;十几岁的时候，我经常和父母发生冲突，对他们的规则和决定感到沮丧。我无法超越自己的视角。但成年后，我学会了看清大局。他们的选择并不总是完美的，但他们是出于最好的意图。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;宽恕并不意味着无视他们的不足之处。它意味着承认他们的人性，认识到他们的挣扎，并选择放下怨恨。它意味着用同理心取代评判，并将他们的牺牲视为爱的证明。&lt;/strong&gt;&lt;/p&gt;
</content:encoded><category>thoughts</category><category>生活</category><category>思考</category></item><item><title>长大后时间过得越来越快了</title><link>https://yourdomain.com/blog/202501142200/</link><guid isPermaLink="true">https://yourdomain.com/blog/202501142200/</guid><description>分类：life | 标签：生活, 思考</description><pubDate>Tue, 14 Jan 2025 13:25:27 GMT</pubDate><content:encoded>&lt;blockquote&gt;
&lt;p&gt;“我渐渐长大，我把所有的时间都花在了解时间之上，但我并不了解它，所以我感觉它是一位向我发动袭击的隐秘敌人，而我却没有能力抵抗它的攻击。我不知道它是怎么逝去的？它为什么要逝去？我们为何生于其中却不知其为何物？是它经过了我们，还是我们经过了它？还是说它是我们体验到的一种状态？如果它逝去了，那么它又去哪儿了？那些离开我们的岁月都去哪儿了？我们为什么不能像对旧衣物一样，把它保存在某个地方呢？”&lt;/p&gt;
&lt;p&gt;——摘自莱拉·朱哈尼的著作《长大的意义》&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;前不久（具体时间不太记得）刷到一条新闻说地球自转加快了，一天可能不到 24 小时。说实话，对于一个已过不惑之龄的人来说，我感觉一年的时间比我还是个孩子或青少年时要短得多。我们活得越久，世界就会变得越来越熟悉，我们就会失去对事物的热情，我们每年从中获得的感官信息量就越少。在我们看来，时间似乎一年比一年过得更快。&lt;/p&gt;
&lt;p&gt;我从年轻人那里听到，他们还没有长大，就已经渴望童年的悠闲。我从老人那里看到他们困惑于岁月从指尖溜走的速度，而我只知道时间本身并没有改变。&lt;/p&gt;
&lt;p&gt;记得上学那会儿，总觉得放学铃声怎么这么慢才响，一节课仿佛有一个世纪那么长。放学总会与三五好友院子里嬉戏打闹，总是盼望着寒暑假和过年，那时候，时间就是时间，它过得很慢。&lt;/p&gt;
&lt;p&gt;现在呢？打开电脑准备开始工作，还没缓过神来就到下班时间了。周一刚在朋友圈吐槽”又要开始一周的社畜生活了”，眨眼间周五又到了。&lt;/p&gt;
&lt;p&gt;前两天收拾房间，翻出了去年的手账本。看着年初写下的计划：学会写三件套，每周长跑三次…结果呢？已经 2025 了，那些目标还在纸上躺着。不是我懒，是真的感觉时间溜得太快了，连个喘息的机会都不给。&lt;/p&gt;
&lt;p&gt;有人说这是大脑在捣鬼。小时候什么都新鲜，大脑像个勤奋的记录员，把每件小事都存进记忆库里。现在的生活太规律了：早上挤地铁、打卡上班、对着电脑、回家躺平刷剧，日子久了，大脑觉得”这有啥好记的，都一样”，于是就开始偷懒，不认真记录了。&lt;/p&gt;
&lt;p&gt;说白了，不是时间变快了，是我们的生活太”循环”了。随着生活中的模式开始自我重复，我们注意到并记住的“时间片段”变得越来越少，越来越粗糙。回想起来，大部分记忆都与童年有关，近期的记忆却寥寥无几。这大概就是年龄越大越来越怀念小时候的原因之一吧？&lt;/p&gt;
&lt;p&gt;但说实话，谁还没有个生活压力呢？想换工作吧，又怕不适应；想搬家换个环境吧，又嫌麻烦；想学点新东西吧，又担心学不会…最后还是在舒适圈里原地打转。&lt;/p&gt;
&lt;p&gt;所以与其抱怨时间过得快，不如想办法给平淡的日子加点料。比如下班后绕个远路走回家，或者周末去个没去过的公园。不用非得整些惊天动地的大事，小小的改变也能让生活不那么单调。&lt;/p&gt;
&lt;p&gt;反正时间都是一分一秒过的，与其让它悄无声息地溜走，不如让它带着点故事离开。&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;PS：推荐一个来自号称自己是&lt;a href=&quot;https://theonlyrealtimetraveleronearth.wordpress.com/category/uncategorized/&quot;&gt;地球唯一时间旅行者的博客&lt;/a&gt;，很有意思，不过不要当真，图一乐！&lt;/p&gt;</content:encoded><category>life</category><category>生活</category><category>思考</category></item><item><title>总结：2024，有什么不一样？</title><link>https://yourdomain.com/blog/202412312241/</link><guid isPermaLink="true">https://yourdomain.com/blog/202412312241/</guid><description>分类：life | 标签：生活, 思考</description><pubDate>Tue, 31 Dec 2024 14:41:48 GMT</pubDate><content:encoded>&lt;h2&gt;前言&lt;/h2&gt;
&lt;p&gt;时光飞逝，即将 2025 了，转眼间女儿已经快八岁了。看着她在学校里认真学习、快乐成长，我不禁感慨时间过得真快。记得她刚出生时那个小小的身影，现在已经成为一个有主见、爱思考的小姑娘。&lt;/p&gt;
&lt;h2&gt;变化&lt;/h2&gt;
&lt;p&gt;说起变化，最大的莫过于我重新开始写博客。之前的博客因为各种原因停摆了很久，所有的文章和数据也不幸丢失。那些记录着生活点滴的文字，就这样消失在了数据的海洋中。当时的沮丧和遗憾难以言表，但现在想来，或许这也是一个全新开始的契机。&lt;/p&gt;
&lt;p&gt;生活总是充满惊喜和挑战，但只要保持积极向上的心态，相信每一天都会有新的收获。博客的重新开始，象征着 2025 年的新气象。就像春天的新芽，虽然经历了冬天的沉寂，但终将迎来蓬勃生长的季节。去年的一场大病，也让我重新审视了生活，也让我更加珍惜每一天。&lt;/p&gt;
&lt;p&gt;就像女儿常说的：“爸爸，失败了也没关系，重新开始就好啦！”这份纯真的鼓励，让我重拾写作的勇气。于是在 2025 年的开始，我重新搭建了这个博客。虽然以前的内容无法找回，但新的记录可以从现在开始。这一次，我依然选择了 &lt;strong&gt;&lt;a href=&quot;https://gohugo.io&quot;&gt;HUGO&lt;/a&gt;&lt;/strong&gt;  框架，主题是我借鉴了 &lt;strong&gt;&lt;a href=&quot;https://koobai.com&quot;&gt;KOOBAI&lt;/a&gt;&lt;/strong&gt; 的主题，做了一些小改动修改优化后成为现在的样子，希望能够更全面地记录生活的点滴。在此，也感谢 KOOBAI 和 HUGO。&lt;/p&gt;
&lt;p&gt;特别是最近完成的电影模块，让我可以好好整理这些年看过的影片。记得女儿第一次看《千与千寻》时着迷的样子，我们一起讨论剧情，分享感受的温馨时刻。这些都是值得被记录的美好回忆。&lt;/p&gt;
&lt;p&gt;回顾 2024 年初立下的目标，有些已经实现，有些还在路上。工作上遇到了一些挑战，但也收获了新的经验和成长。生活中，陪伴家人的时间依然是最珍贵的。看着女儿在钢琴和舞蹈课上的进步，分享她在学校的点点滴滴，这些都是最温暖的时刻。&lt;/p&gt;
&lt;p&gt;重新开始写博客，某种程度上也是为了给女儿留下一些值得回忆的文字。等她长大后，可以通过这些文章回望自己的童年，了解父母的想法和感受。虽然之前的记录遗失了，但未来的故事还很长，我们可以一起续写。&lt;/p&gt;
&lt;h2&gt;新的一年&lt;/h2&gt;
&lt;p&gt;稻盛和夫曾说过：&lt;strong&gt;除了疾病以外，其他所有的痛苦都来自于自己的认知&lt;/strong&gt;。我曾经天真地以为，我是靠这句话的安慰才活的这么乐观。后来才知道，那时候的自己还没经历过真正的痛苦。我们仍在不断进步，希望今年我们能再次做出一些重大改变。我还没有再次更新，但它即将到来！现在，我把这个博客扔进了互联网的虚空。&lt;/p&gt;
&lt;p&gt;展望未来，我希望能：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;坚持写博客，用文字记录生活，留下珍贵的回忆；&lt;/li&gt;
&lt;li&gt;继续完善博客功能，让它成为一个稳定、可靠的平台；&lt;/li&gt;
&lt;li&gt;在工作中寻找新的突破和机会；&lt;/li&gt;
&lt;li&gt;多陪伴家人，见证和记录女儿的成长；&lt;/li&gt;
&lt;li&gt;保持阅读和观影的习惯，让生活更加充实；&lt;/li&gt;
&lt;li&gt;保持健康，多锻炼身体，保持良好的心态；&lt;/li&gt;
&lt;li&gt;保持学习，不断充实自己，提高自己的能力；&lt;/li&gt;
&lt;li&gt;保持乐观，相信一切都会好起来的；&lt;/li&gt;
&lt;li&gt;保持感恩，珍惜身边的每一个人。&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;总结&lt;/h2&gt;
&lt;p&gt;这份重新出发的决心和期待，或许就是 2025 年最大的不一样吧。&lt;/p&gt;
&lt;p&gt;好啦！&lt;/p&gt;
&lt;p&gt;新年真的十分美好，然而没有钱，真是倒霉，人类的悲苦有时候也是相通的。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;食一碗人间烟火，饮几杯人生起落，愿诸君新年快乐。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;就从这一篇作为 2025 的新开端吧！&lt;/p&gt;
&lt;p&gt;当我坐在这里喋喋不休时，我不知道这会变成什么样子，但我感谢您在这里阅读它。&lt;/p&gt;</content:encoded><category>life</category><category>生活</category><category>思考</category></item><item><title>使用 CloudFlare 部署 Umami API</title><link>https://yourdomain.com/blog/202410121605/</link><guid isPermaLink="true">https://yourdomain.com/blog/202410121605/</guid><description>分类：tech | 标签：Cloudflare, Umami, API</description><pubDate>Sat, 12 Oct 2024 08:05:00 GMT</pubDate><content:encoded>&lt;p&gt;{{&amp;lt; alert “warning” “注意” “true” &amp;gt;}}
此方法仅适用于Umami V2版本， V3版此方法不适用
{{&amp;lt; /alert &amp;gt;}}&lt;/p&gt;
&lt;h2&gt;前言&lt;/h2&gt;
&lt;p&gt;最近在大家的疯狂安利下，捣鼓上了这样一款可以部署在Docker上的前端统计应用。前几天也试着将其&lt;a href=&quot;/202410092122/&quot;&gt;部署在Vercel&lt;/a&gt;了。特别是木木老师，更是直接放出了 &lt;a href=&quot;https://immmmm.com/hi-umami-api/&quot;&gt;《前端调用 Umami API 数据》&lt;/a&gt; 。不得不说，这个应用的可玩性成功的吸引了我的注意。&lt;/p&gt;
&lt;p&gt;Umami 是一个高颜值可自部署的统计应用，本站也一直在使用该项目统计网站的访客数据，最后通过AI的帮助也是成功的在Memos页面中调用了umami统计的浏览数据，于是做个记录。&lt;/p&gt;
&lt;h2&gt;教程&lt;/h2&gt;
&lt;h4&gt;获取 Umami API Token&lt;/h4&gt;
&lt;p&gt;如果你是在直接在 Umami 网站注册帐号使用的，可以直接在&lt;a href=&quot;https://umami.is/docs/cloud/api-key&quot;&gt;网站后台获取 Api Token&lt;/a&gt;。&lt;/p&gt;
&lt;p&gt;否则，如果是自托管，需要先进行身份验证。2（本文以下内容均以自托管为例）&lt;/p&gt;
&lt;p&gt;首先需要去 &lt;a href=&quot;https://hoppscotch.io/&quot;&gt;Hoppscotch&lt;/a&gt; 获取umami站点的登录token，然后按照下图序号选取，最后获取token备用。&lt;/p&gt;
&lt;p&gt;&lt;code&gt;POST https://your-umami-instance.com/api/auth/login&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;图中第5步，填入以下信息：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  &quot;username&quot;: &quot;your-username&quot;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  &quot;password&quot;: &quot;your-password&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&quot;https://i.p-i.vip/89/20250915-68c7f8d0e233d.webp&quot; alt=&quot;步骤顺序&quot; /&gt;&lt;/p&gt;
&lt;h4&gt;通过 CloudFlare 部署 API&lt;/h4&gt;
&lt;p&gt;登录 &lt;a href=&quot;https://www.cloudflare.com/&quot;&gt;CloudFlare&lt;/a&gt; 创建一个 Workers ，修改名称并点击部署。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;addEventListener&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&apos;fetch&apos;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;event&lt;/span&gt;&lt;span&gt; =&amp;gt;&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  event.&lt;/span&gt;&lt;span&gt;respondWith&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;handleRequest&lt;/span&gt;&lt;span&gt;(event));&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;});&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; API_BASE_URL&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; &apos;https://umami.yourdomain.com&apos;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; TOKEN&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; &apos;your_token&apos;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; WEBSITE_ID&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; &apos;your_website_id&apos;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; CACHE_KEY&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; &apos;umami_cache&apos;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; CACHE_TIME&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; 600&lt;/span&gt;&lt;span&gt;; &lt;/span&gt;&lt;span&gt;// Cache time in seconds&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;async&lt;/span&gt;&lt;span&gt; function&lt;/span&gt;&lt;span&gt; fetchUmamiData&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;startAt&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;endAt&lt;/span&gt;&lt;span&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  const&lt;/span&gt;&lt;span&gt; url&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; `${&lt;/span&gt;&lt;span&gt;API_BASE_URL&lt;/span&gt;&lt;span&gt;}/api/websites/${&lt;/span&gt;&lt;span&gt;WEBSITE_ID&lt;/span&gt;&lt;span&gt;}/stats?startAt=${&lt;/span&gt;&lt;span&gt;startAt&lt;/span&gt;&lt;span&gt;}&amp;amp;endAt=${&lt;/span&gt;&lt;span&gt;endAt&lt;/span&gt;&lt;span&gt;}`&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  const&lt;/span&gt;&lt;span&gt; response&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; await&lt;/span&gt;&lt;span&gt; fetch&lt;/span&gt;&lt;span&gt;(url, {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    headers: {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      &apos;Authorization&apos;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;`Bearer ${&lt;/span&gt;&lt;span&gt;TOKEN&lt;/span&gt;&lt;span&gt;}`&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      &apos;Content-Type&apos;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&apos;application/json&apos;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  });&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  if&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;!&lt;/span&gt;&lt;span&gt;response.ok) {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    console.&lt;/span&gt;&lt;span&gt;error&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;`Error fetching data: ${&lt;/span&gt;&lt;span&gt;response&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;statusText&lt;/span&gt;&lt;span&gt;}`&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    return&lt;/span&gt;&lt;span&gt; null&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  return&lt;/span&gt;&lt;span&gt; response.&lt;/span&gt;&lt;span&gt;json&lt;/span&gt;&lt;span&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;async&lt;/span&gt;&lt;span&gt; function&lt;/span&gt;&lt;span&gt; handleRequest&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;event&lt;/span&gt;&lt;span&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  const&lt;/span&gt;&lt;span&gt; cache&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; await&lt;/span&gt;&lt;span&gt; caches.&lt;/span&gt;&lt;span&gt;open&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;CACHE_KEY&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  const&lt;/span&gt;&lt;span&gt; cachedResponse&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; await&lt;/span&gt;&lt;span&gt; cache.&lt;/span&gt;&lt;span&gt;match&lt;/span&gt;&lt;span&gt;(event.request);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  if&lt;/span&gt;&lt;span&gt; (cachedResponse) {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    return&lt;/span&gt;&lt;span&gt; cachedResponse;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  const&lt;/span&gt;&lt;span&gt; now&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; Date.&lt;/span&gt;&lt;span&gt;now&lt;/span&gt;&lt;span&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  const&lt;/span&gt;&lt;span&gt; todayStart&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; new&lt;/span&gt;&lt;span&gt; Date&lt;/span&gt;&lt;span&gt;(now).&lt;/span&gt;&lt;span&gt;setHours&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;0&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;0&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;0&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;0&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  const&lt;/span&gt;&lt;span&gt; yesterdayStart&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; new&lt;/span&gt;&lt;span&gt; Date&lt;/span&gt;&lt;span&gt;(now &lt;/span&gt;&lt;span&gt;-&lt;/span&gt;&lt;span&gt; 86400000&lt;/span&gt;&lt;span&gt;).&lt;/span&gt;&lt;span&gt;setHours&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;0&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;0&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;0&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;0&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  const&lt;/span&gt;&lt;span&gt; lastMonthStart&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; new&lt;/span&gt;&lt;span&gt; Date&lt;/span&gt;&lt;span&gt;(now).&lt;/span&gt;&lt;span&gt;setMonth&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;new&lt;/span&gt;&lt;span&gt; Date&lt;/span&gt;&lt;span&gt;(now).&lt;/span&gt;&lt;span&gt;getMonth&lt;/span&gt;&lt;span&gt;() &lt;/span&gt;&lt;span&gt;-&lt;/span&gt;&lt;span&gt; 1&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  const&lt;/span&gt;&lt;span&gt; lastYearStart&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; new&lt;/span&gt;&lt;span&gt; Date&lt;/span&gt;&lt;span&gt;(now).&lt;/span&gt;&lt;span&gt;setFullYear&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;new&lt;/span&gt;&lt;span&gt; Date&lt;/span&gt;&lt;span&gt;(now).&lt;/span&gt;&lt;span&gt;getFullYear&lt;/span&gt;&lt;span&gt;() &lt;/span&gt;&lt;span&gt;-&lt;/span&gt;&lt;span&gt; 1&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  const&lt;/span&gt;&lt;span&gt; [&lt;/span&gt;&lt;span&gt;todayData&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;yesterdayData&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;lastMonthData&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;lastYearData&lt;/span&gt;&lt;span&gt;] &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; await&lt;/span&gt;&lt;span&gt; Promise&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;all&lt;/span&gt;&lt;span&gt;([&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    fetchUmamiData&lt;/span&gt;&lt;span&gt;(todayStart, now),&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    fetchUmamiData&lt;/span&gt;&lt;span&gt;(yesterdayStart, todayStart),&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    fetchUmamiData&lt;/span&gt;&lt;span&gt;(lastMonthStart, now),&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    fetchUmamiData&lt;/span&gt;&lt;span&gt;(lastYearStart, now)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  ]);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  const&lt;/span&gt;&lt;span&gt; responseData&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    today_uv: todayData?.visitors?.value &lt;/span&gt;&lt;span&gt;??&lt;/span&gt;&lt;span&gt; null&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    today_pv: todayData?.pageviews?.value &lt;/span&gt;&lt;span&gt;??&lt;/span&gt;&lt;span&gt; null&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    yesterday_uv: yesterdayData?.visitors?.value &lt;/span&gt;&lt;span&gt;??&lt;/span&gt;&lt;span&gt; null&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    yesterday_pv: yesterdayData?.pageviews?.value &lt;/span&gt;&lt;span&gt;??&lt;/span&gt;&lt;span&gt; null&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    last_month_pv: lastMonthData?.pageviews?.value &lt;/span&gt;&lt;span&gt;??&lt;/span&gt;&lt;span&gt; null&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    last_year_pv: lastYearData?.pageviews?.value &lt;/span&gt;&lt;span&gt;??&lt;/span&gt;&lt;span&gt; null&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  };&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  const&lt;/span&gt;&lt;span&gt; jsonResponse&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; new&lt;/span&gt;&lt;span&gt; Response&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;JSON&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;stringify&lt;/span&gt;&lt;span&gt;(responseData), {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    headers: {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      &apos;Content-Type&apos;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&apos;application/json&apos;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      &apos;Access-Control-Allow-Origin&apos;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&apos;*&apos;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      &apos;Access-Control-Allow-Methods&apos;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&apos;GET, POST, PUT, DELETE, OPTIONS&apos;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      &apos;Access-Control-Allow-Headers&apos;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&apos;Content-Type, Authorization&apos;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  });&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  event.&lt;/span&gt;&lt;span&gt;waitUntil&lt;/span&gt;&lt;span&gt;(cache.&lt;/span&gt;&lt;span&gt;put&lt;/span&gt;&lt;span&gt;(event.request, jsonResponse.&lt;/span&gt;&lt;span&gt;clone&lt;/span&gt;&lt;span&gt;()));&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  return&lt;/span&gt;&lt;span&gt; jsonResponse;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;通过访问部署好的地址，应该可以看见返回类似以下的数据串。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://i.p-i.vip/89/20250915-68c7f8c5bc6cb.webp&quot; alt=&quot;返回数据&quot; /&gt;&lt;/p&gt;
&lt;p&gt;最后在页面中嵌入。&lt;/p&gt;</content:encoded><category>tech</category><category>Cloudflare</category><category>Umami</category><category>API</category></item><item><title>Vercel 零成本部署 Umami</title><link>https://yourdomain.com/blog/202410092122/</link><guid isPermaLink="true">https://yourdomain.com/blog/202410092122/</guid><description>分类：tech | 标签：Vercel, Umami</description><pubDate>Wed, 09 Oct 2024 13:22:00 GMT</pubDate><content:encoded>&lt;p&gt;Umami 是一个简单、快速、注重隐私的开源网站统计工具，是 Google Analytics 的理想替代品。本文将指导你如何在 Vercel 上部署 Umami，并通过 Vercel Storage 创建 Neon PostgreSQL 数据库，搭建一个零成本、轻量级的网站流量统计系统。本教程特别为 Hugo 静态网站用户优化，确保生成的 Markdown 文件适配 Hugo 的静态网页生成。&lt;/p&gt;
&lt;h3&gt;前言&lt;/h3&gt;
&lt;p&gt;对于个人博客或中小型网站，Google Analytics 可能过于复杂，且在某些地区访问不便。而 Umami 提供了简洁的界面和核心指标，非常适合轻量级流量分析需求。通过 Vercel 的 Serverless 部署和 Vercel Storage 集成的 Neon 数据库，我们可以快速搭建一个高效的统计系统，无需服务器维护成本。&lt;/p&gt;
&lt;p&gt;以下是详细的部署步骤。&lt;/p&gt;
&lt;h3&gt;准备工作&lt;/h3&gt;
&lt;p&gt;在开始之前，确保你已准备以下内容：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;GitHub 账户&lt;/strong&gt;：用于 Fork Umami 仓库。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Vercel 账户&lt;/strong&gt;：用于部署 Umami 和创建 Neon 数据库。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Neon 账户&lt;/strong&gt;：已注册，用于通过 Vercel Storage 连接。&lt;/li&gt;
&lt;li&gt;一个运行中的 Hugo 网站（或其他静态网站），用于嵌入 Umami 的跟踪代码。&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;Fork Umami 仓库&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;访问 Umami 官方 GitHub 仓库：&lt;a href=&quot;https://github.com/umami-software/umami&quot;&gt;https://github.com/umami-software/umami&lt;/a&gt;。&lt;/li&gt;
&lt;li&gt;点击右上角的 &lt;strong&gt;Fork&lt;/strong&gt; 按钮，将仓库 Fork 到你的 GitHub 账户。&lt;/li&gt;
&lt;li&gt;（可选）如果你需要自定义 Umami，可以克隆仓库到本地进行修改，但本教程使用默认配置。&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;在 Vercel 部署 Umami&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;登录 &lt;a href=&quot;https://vercel.com/&quot;&gt;Vercel 官网&lt;/a&gt;，点击 &lt;strong&gt;Add New&lt;/strong&gt; &amp;gt; &lt;strong&gt;Project&lt;/strong&gt;。&lt;/li&gt;
&lt;li&gt;在 &lt;strong&gt;Import Git Repository&lt;/strong&gt; 页面，选择你刚刚 Fork 的 Umami 仓库。&lt;/li&gt;
&lt;li&gt;配置项目：
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Framework Preset&lt;/strong&gt;：选择 &lt;strong&gt;Next.js&lt;/strong&gt;（Umami 基于 Next.js 构建）。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Environment Variables&lt;/strong&gt;：暂时跳过，稍后配置 Neon 数据库的 &lt;code&gt;DATABASE_URL&lt;/code&gt;。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;点击 &lt;strong&gt;Deploy&lt;/strong&gt; 按钮，Vercel 将自动构建项目（此时可能因缺少数据库连接而失败，稍后修复）。&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;在 Vercel Storage 创建 Neon 数据库&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;在 Vercel 仪表板中，进入你的 Umami 项目。&lt;/li&gt;
&lt;li&gt;点击顶部导航的 &lt;strong&gt;Storage&lt;/strong&gt; 标签，然后选择 &lt;strong&gt;Create Database&lt;/strong&gt;。
&lt;img src=&quot;https://img.music-poster.art/2025/06/cba773362305001171fb5d0defb4f960.png&quot; alt=&quot;&quot; /&gt;&lt;/li&gt;
&lt;li&gt;在数据库类型中选择 &lt;strong&gt;Neon&lt;/strong&gt;，并登录你的 Neon 账户以授权 Vercel 访问。&lt;/li&gt;
&lt;li&gt;配置数据库：
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;项目名称&lt;/strong&gt;：任意，例如 &lt;code&gt;umami-project&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;数据库名称&lt;/strong&gt;：建议使用 &lt;code&gt;umami&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;云服务托管商&lt;/strong&gt;：选择你所在的地区（如 AWS 亚洲区域）以降低延迟。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;创建完成后，Vercel 会自动生成一个 &lt;strong&gt;DATABASE_URL&lt;/strong&gt; 并将其添加到项目的环境变量中，格式如下：
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;postgresql://[username]:[password]@[host]/[database]&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;返回项目设置，确认 &lt;strong&gt;Environment Variables&lt;/strong&gt; 中已包含 &lt;code&gt;DATABASE_URL&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;重新部署项目：点击 &lt;strong&gt;Deployments&lt;/strong&gt; 标签，选择最新部署，点击 &lt;strong&gt;Redeploy&lt;/strong&gt;。&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;配置 Umami&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;部署完成后，点击 &lt;strong&gt;Visit&lt;/strong&gt; 查看你的 Umami 实例，记下分配的默认域名（如 &lt;code&gt;your-project.vercel.app&lt;/code&gt;）。&lt;/li&gt;
&lt;li&gt;访问 Umami 网站，首次登录默认账户为：
&lt;ul&gt;
&lt;li&gt;用户名：&lt;code&gt;admin&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;密码：&lt;code&gt;umami&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;登录后立即更改密码以确保安全。&lt;/li&gt;
&lt;li&gt;在 Umami 仪表板中，点击 &lt;strong&gt;Add Website&lt;/strong&gt;，输入你的网站信息（如域名、名称）。
&lt;img src=&quot;https://img.music-poster.art/2025/06/2b0b37c13001ea761ffcd370f170defc.png&quot; alt=&quot;&quot; /&gt;&lt;/li&gt;
&lt;li&gt;Umami 会生成一段 JavaScript 跟踪代码，格式如下：
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;&amp;lt;&lt;/span&gt;&lt;span&gt;script&lt;/span&gt;&lt;span&gt; async&lt;/span&gt;&lt;span&gt; src&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;https://your-project.vercel.app/umami.js&quot;&lt;/span&gt;&lt;span&gt; data-website-id&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;YOUR_WEBSITE_ID&quot;&lt;/span&gt;&lt;span&gt;&amp;gt;&amp;lt;/&lt;/span&gt;&lt;span&gt;script&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
复制此代码。&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;在 Hugo 网站中嵌入跟踪代码&lt;/h3&gt;
&lt;p&gt;为了让 Umami 统计你的 Hugo 网站流量，需要将跟踪代码嵌入到网站中。这个一般需要你使用的hugo主题支持，如果不支持，则需要自己修改hugo主题。&lt;/p&gt;
&lt;p&gt;在 &lt;code&gt;hugo.yaml&lt;/code&gt;文件中配置即可:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;  analytics&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    enabled&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;true&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    umami&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      enabled&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;true&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      id&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;YOUR_WEBSITE_ID&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      src&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;https://your-project.vercel.app/umami.js&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      domains&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;&quot;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;其中的 &lt;code&gt;YOUR_WEBSITE_ID&lt;/code&gt; 替换为你在 Umami 中创建的网站 ID。&lt;code&gt;src&lt;/code&gt; 也需要替换为你在 Vercel 中部署的 Umami 项目域名。&lt;/p&gt;
&lt;p&gt;接着访问你的网站，Umami 将开始收集流量数据。&lt;/p&gt;
&lt;h3&gt;验证和优化&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;返回 Umami 仪表板，等待几分钟后检查是否有流量数据记录。&lt;/li&gt;
&lt;li&gt;验证跟踪代码是否正常工作：
&lt;ul&gt;
&lt;li&gt;打开浏览器的开发者工具（F12），切换到 &lt;strong&gt;Network&lt;/strong&gt; 面板，刷新页面，确认是否有对 &lt;code&gt;your-project.vercel.app/api/collect&lt;/code&gt; 的请求。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;（可选）自定义 Umami 仪表板：
&lt;ul&gt;
&lt;li&gt;添加多个网站进行跟踪。&lt;/li&gt;
&lt;li&gt;配置数据分享链接，方便与团队共享统计数据。&lt;/li&gt;
&lt;li&gt;调整 Umami 的主题或语言设置，支持中文界面。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;注意事项&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Neon 免费额度&lt;/strong&gt;：通过 Vercel Storage 创建的 Neon 数据库有存储和计算时间的限制，适合小型网站。如果流量较大，考虑升级到付费计划。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Vercel 免费额度&lt;/strong&gt;：Vercel 的免费计划每月提供 100GB 带宽，足以应对大多数个人网站需求。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;隐私合规&lt;/strong&gt;：Umami 注重隐私，但需确保你的网站遵守 GDPR 或其他隐私法规（如在欧盟地区运营）。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;安全&lt;/strong&gt;：定期备份 Neon 数据库，并确保 Umami 的管理员账户使用强密码。&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;结语&lt;/h3&gt;
&lt;p&gt;通过 Vercel 和其集成的 Neon 数据库，你可以在几分钟内搭建一个功能强大、成本为零的网站流量统计系统。Umami 的简洁界面和核心功能非常适合 Hugo 博客用户，无论是统计访问量、分析来源还是监控页面性能，都能满足需求。&lt;/p&gt;
&lt;p&gt;如果你有任何问题或需要进一步优化，欢迎在评论区交流！希望这篇教程能帮助你更好地了解你的网站流量。&lt;/p&gt;
&lt;h3&gt;参考资料&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Umami 官方文档：&lt;a href=&quot;https://umami.is/docs&quot;&gt;https://umami.is/docs&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Vercel Storage 文档：&lt;a href=&quot;https://vercel.com/docs/storage&quot;&gt;https://vercel.com/docs/storage&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Neon 数据库配置指南：&lt;a href=&quot;https://neon.tech/docs&quot;&gt;https://neon.tech/docs&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Hugo 文档：&lt;a href=&quot;https://gohugo.io/documentation/&quot;&gt;https://gohugo.io/documentation/&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</content:encoded><category>tech</category><category>Vercel</category><category>Umami</category></item><item><title>PC 上使用的应用信息实时显示到博客</title><link>https://yourdomain.com/blog/pc-app-info-display/</link><guid isPermaLink="true">https://yourdomain.com/blog/pc-app-info-display/</guid><description>分类：tech | 标签：WebSocket, 博客</description><pubDate>Tue, 19 Mar 2024 05:55:00 GMT</pubDate><content:encoded>&lt;p&gt;这个功能是在大佬 &lt;a href=&quot;https://1900.live/golink?target=aHR0cHM6Ly9pbm5laS5pbi8/cmVmPWNtcy4xOTAwLmxpdmU=&quot;&gt;Innei&lt;/a&gt;的博客上看到的，效果就是在网站logo处实时的显示博主在PC上使用的应用程序信息，虽然是个不太显眼的功能，但是效果还是挺好的，让我感觉整个网站因为这个变的不再死板，不断变动的程序图标让这个网站像博主的一个身外化身。&lt;/p&gt;
&lt;p&gt;真的很想要呀！&lt;/p&gt;
&lt;p&gt;遂翻阅了一下大佬的博客源码和一些相关的配套文件，自己大概有了一些思路，后来也将这些想法整理了一下后请教了 &lt;a href=&quot;https://1900.live/golink?target=aHR0cHM6Ly9qdzEuZGV2Lz9yZWY9Y21zLjE5MDAubGl2ZQ==&quot;&gt;一个球&lt;/a&gt; 大佬，大佬说这个思路是没什么问题。&lt;/p&gt;
&lt;p&gt;所以，喜欢就干！&lt;/p&gt;
&lt;p&gt;遂根据自己的思路搭配Kimi和ChatGPT终于在昨天将这个效果实现了一个大概，今天大概修复了一下BUG，撰文总结一下这次折腾和分享实现过程。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://cdn.1900.live/assets/2024/05/13PixPin_2024-05-13_21-02-16.gif&quot; alt=&quot;Image 4&quot; /&gt;&lt;/p&gt;
&lt;p&gt;根据PC激活的窗口动态切换&lt;/p&gt;
&lt;h2&gt;结构思路&lt;/h2&gt;
&lt;p&gt;对于这个功能的整个流程总结如下：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;电脑上有一个监控程序，定时或在切换程序时向服务器发送当前激活的程序信息&lt;/li&gt;
&lt;li&gt;服务器对发送来的信息进行鉴权、验证、过滤等处理后转发给所有客户端（既打开了我博客网站的浏览器）&lt;/li&gt;
&lt;li&gt;网页接收到信息后根据需求进行展示。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;根据这个结构我们需要一个可以上报数据的监控程序和一个架设API的VPS或可以架设API功能的云函数等，技术架构如下。&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;VPS起个Web服务和WebSocket服务&lt;/li&gt;
&lt;li&gt;PC端定时向服务器的Web服务发起一次POST请求&lt;/li&gt;
&lt;li&gt;web服务收到后将数据通过WebSocket转发给前端。&lt;/li&gt;
&lt;li&gt;前端根据需求展示数据&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;电脑监控程序。&lt;/h3&gt;
&lt;p&gt;能监控电脑应用并自动上报的程序我第一时间想到的是&lt;a href=&quot;https://1900.live/golink?target=aHR0cHM6Ly9naXRodWIuY29tL1BsYW5zaGl0L1RhaT9yZWY9Y21zLjE5MDAubGl2ZQ==&quot;&gt;Tai&lt;/a&gt;，这个程序我用了好几年了，还写过一篇推荐文 &lt;a href=&quot;https://1900.live/windows-programs-using-statistics/?ref=cms.1900.live&quot;&gt;在 Windows 上像智能手机那样统计软件使用情况&lt;/a&gt; ，它可以记录电脑上每个应用程序的使用时间，肯定是可以做这张方面监控的，所以我还跑去作者的项目提了个 &lt;a href=&quot;https://1900.live/golink?target=aHR0cHM6Ly9naXRodWIuY29tL1BsYW5zaGl0L1RhaS9pc3N1ZXMvMzM2P3JlZj1jbXMuMTkwMC5saXZl&quot;&gt;issue&lt;/a&gt; 咨询，作者 &lt;a href=&quot;https://1900.live/golink?target=aHR0cHM6Ly9naXRodWIuY29tL25vYmVydW1vdHRvP3JlZj1jbXMuMTkwMC5saXZl&quot;&gt;&lt;strong&gt;noberumotto&lt;/strong&gt;&lt;/a&gt;大佬说应该可以，并且给我说了要改那部分代码，同时给我推荐了他们另外一个开源项目 &lt;a href=&quot;https://1900.live/golink?target=aHR0cHM6Ly9naXRodWIuY29tL2dldHRhaS9zZW50cnk/cmVmPWNtcy4xOTAwLmxpdmU=&quot;&gt;sentry&lt;/a&gt; 。&lt;/p&gt;
&lt;p&gt;但是我研究了一下，发现如果要Debug这个程序需要安装几十个G的Visual Studio，遂暂时放弃了这个想法。&lt;/p&gt;
&lt;p&gt;后来发现 Innei 大佬那个项目 &lt;a href=&quot;https://1900.live/golink?target=aHR0cHM6Ly9naXRodWIuY29tL0lubmVpL1NoaXJvP3JlZj1jbXMuMTkwMC5saXZl&quot;&gt;Shiro&lt;/a&gt; 其实原本提供了一个Mac版的监控程序，并且有爱好者开发了支持Windows的 &lt;a href=&quot;https://1900.live/golink?target=aHR0cHM6Ly9naXRodWIuY29tL1ROWEcvUHJvY2Vzc1JlcG9ydGVyV2luZ28/cmVmPWNtcy4xOTAwLmxpdmU=&quot;&gt;Go&lt;/a&gt;、&lt;a href=&quot;https://1900.live/golink?target=aHR0cHM6Ly9naXRodWIuY29tL1ROWEcvUHJvY2Vzc1JlcG9ydGVyV2lucHk/cmVmPWNtcy4xOTAwLmxpdmU=&quot;&gt;Python&lt;/a&gt;、&lt;a href=&quot;https://1900.live/golink?target=aHR0cHM6Ly9naXRodWIuY29tL0NoaW5nQ2Rlc3UvU2hpcm9Qcm9jZXNzUmVwb3J0ZXI/cmVmPWNtcy4xOTAwLmxpdmU=&quot;&gt;C#&lt;/a&gt; 版本。&lt;/p&gt;
&lt;p&gt;我最后选用了 &lt;a href=&quot;https://1900.live/golink?target=aHR0cHM6Ly9naXRodWIuY29tL0NoaW5nQ2Rlc3UvU2hpcm9Qcm9jZXNzUmVwb3J0ZXI/cmVmPWNtcy4xOTAwLmxpdmU=&quot;&gt;C#&lt;/a&gt; 版本做测试，这个版本不是定时按频率发送，而是在切换窗口时发送，能减少无用发送，不过这个版本的媒体信息发送还不完善（所以当前在听什么歌的功能我还没实现）。&lt;/p&gt;
&lt;h3&gt;服务端的实现&lt;/h3&gt;
&lt;p&gt;我看了一下这个程序不一定非要搭配 &lt;a href=&quot;https://1900.live/golink?target=aHR0cHM6Ly9naXRodWIuY29tL0lubmVpL1NoaXJvP3JlZj1jbXMuMTkwMC5saXZl&quot;&gt;Shiro&lt;/a&gt; ，这个程序只是向一个api发送了当前程序的数据，只要接受这个数据并有相关的处理流程就好了。&lt;/p&gt;
&lt;p&gt;遂找 &lt;a href=&quot;https://1900.live/golink?target=aHR0cHM6Ly9raW1pLmFpLz9yZWY9Y21zLjE5MDAubGl2ZQ==&quot;&gt;Kimi&lt;/a&gt;要了一段启动API服务和WebSocket服务的demo代码，自己在本地测试一次性启动成功：&lt;/p&gt;
&lt;p&gt;展开&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; express&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; require&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&apos;express&apos;&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; { &lt;/span&gt;&lt;span&gt;WebSocketServer&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt;WebSocket&lt;/span&gt;&lt;span&gt; } &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; require&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&apos;ws&apos;&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; app&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; express&lt;/span&gt;&lt;span&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;// 允许解析JSON格式的请求体&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;app.&lt;/span&gt;&lt;span&gt;use&lt;/span&gt;&lt;span&gt;(express.&lt;/span&gt;&lt;span&gt;json&lt;/span&gt;&lt;span&gt;());&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;// 存放所有WebSocket客户端的数组&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;let&lt;/span&gt;&lt;span&gt; clients &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; [];&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;// 启动WebSocket服务器&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; wss&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; new&lt;/span&gt;&lt;span&gt; WebSocketServer&lt;/span&gt;&lt;span&gt;({ port: &lt;/span&gt;&lt;span&gt;8081&lt;/span&gt;&lt;span&gt; });&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;wss.&lt;/span&gt;&lt;span&gt;on&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&apos;connection&apos;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;function&lt;/span&gt;&lt;span&gt; connection&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;ws&lt;/span&gt;&lt;span&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  // 将新客户端添加到数组中&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  clients.&lt;/span&gt;&lt;span&gt;push&lt;/span&gt;&lt;span&gt;(ws);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  // 当有消息到达时，将其广播给所有客户端&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  ws.&lt;/span&gt;&lt;span&gt;on&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&apos;message&apos;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;function&lt;/span&gt;&lt;span&gt; incoming&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;message&lt;/span&gt;&lt;span&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    console.&lt;/span&gt;&lt;span&gt;log&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&apos;received: %s&apos;&lt;/span&gt;&lt;span&gt;, message);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  });&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  // 当客户端断开连接时，从数组中移除&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  ws.&lt;/span&gt;&lt;span&gt;on&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&apos;close&apos;&lt;/span&gt;&lt;span&gt;, (&lt;/span&gt;&lt;span&gt;e&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;span&gt;=&amp;gt;&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    clients &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; clients.&lt;/span&gt;&lt;span&gt;filter&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;client&lt;/span&gt;&lt;span&gt; =&amp;gt;&lt;/span&gt;&lt;span&gt; client &lt;/span&gt;&lt;span&gt;!==&lt;/span&gt;&lt;span&gt; ws);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    console.&lt;/span&gt;&lt;span&gt;log&lt;/span&gt;&lt;span&gt;(e&lt;/span&gt;&lt;span&gt;+&lt;/span&gt;&lt;span&gt;&apos;断开连接了，从列表中移除&apos;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  });&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;});&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;// 定义一个POST路由，它将接收数据并打印到控制台，然后通过WebSocket广播&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;app.&lt;/span&gt;&lt;span&gt;post&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&apos;/update&apos;&lt;/span&gt;&lt;span&gt;, (&lt;/span&gt;&lt;span&gt;req&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;res&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;span&gt;=&amp;gt;&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  console.&lt;/span&gt;&lt;span&gt;log&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&apos;Received Data:&apos;&lt;/span&gt;&lt;span&gt;, req.body);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  // 将数据作为消息发送给所有WebSocket客户端&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  clients.&lt;/span&gt;&lt;span&gt;forEach&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;client&lt;/span&gt;&lt;span&gt; =&amp;gt;&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    if&lt;/span&gt;&lt;span&gt; (client.readyState &lt;/span&gt;&lt;span&gt;===&lt;/span&gt;&lt;span&gt; WebSocket.&lt;/span&gt;&lt;span&gt;OPEN&lt;/span&gt;&lt;span&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      client.&lt;/span&gt;&lt;span&gt;send&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;JSON&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;stringify&lt;/span&gt;&lt;span&gt;(req.body));&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  });&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  res.&lt;/span&gt;&lt;span&gt;send&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&apos;Data received and broadcasted via WebSocket&apos;&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;});&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;// 启动HTTP服务器，监听3000端口&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; PORT&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; process.env.&lt;/span&gt;&lt;span&gt;PORT&lt;/span&gt;&lt;span&gt; ||&lt;/span&gt;&lt;span&gt; 3000&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;app.&lt;/span&gt;&lt;span&gt;listen&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;PORT&lt;/span&gt;&lt;span&gt;, () &lt;/span&gt;&lt;span&gt;=&amp;gt;&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  console.&lt;/span&gt;&lt;span&gt;log&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;`HTTP Server is running on port ${&lt;/span&gt;&lt;span&gt;PORT&lt;/span&gt;&lt;span&gt;}`&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;});&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;// 同时启动WebSocket服务器，监听8080端口&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;console.&lt;/span&gt;&lt;span&gt;log&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;`WebSocket Server is running on port 8081`&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;打开安装好的 Shiro C# 版本程序，并设置好API接口，发现果然程序开始正常运作，而且在运行日志里能看到程序向搭建好的本地api发了一个结构如下的json数据：&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://cdn.1900.live/assets/2024/05/13PixPin_2024-05-13_21-47-16.png&quot; alt=&quot;Image 5&quot; /&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  &quot;timestamp&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;1715344006&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  &quot;process&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;Chrome&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  &quot;key&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;apikey&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;网页端实现&lt;/h3&gt;
&lt;p&gt;服务端能接受到数据就基本上没什么问题了，接下来处理网页端的显示。&lt;/p&gt;
&lt;p&gt;网页端同步显示无非两种办法：轮询和WebSocket，前者设置定时器定时去请求服务就好了，后者则可以主动接收服务下发的信息，之前在咨询 一个球 大佬的时候证实了后者是可行的，所以肯定选WebSocket的方案。&lt;/p&gt;
&lt;p&gt;我以前也从来没用过WebSocket，大概查询了一下概念：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;WebSocket 是一种在单个TCP连接上进行全双工通信的协议，它允许客户端和服务器之间进行实时、连续的数据交换。以下是其主要特点的详细解释：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;全双工通信&lt;/strong&gt;：与传统的HTTP请求/响应模式不同，WebSocket 允许服务器主动向客户端发送消息，而不需要客户端的请求。这意味着数据可以在任何一方发起，实现真正的双向通信。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;持久连接&lt;/strong&gt;：一旦WebSocket连接建立，它将保持开放状态，直到客户端或服务器决定关闭它。这减少了因频繁建立和关闭连接而产生的开销。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;低延迟&lt;/strong&gt;：由于不需要为每个数据传输重新建立连接，WebSocket 可以减少通信的延迟，这对于需要快速响应的应用（如在线游戏或实时数据更新）非常重要。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;较少的控制开销&lt;/strong&gt;：与HTTP相比，WebSocket 在数据传输时的控制信息更少，这使得它在传输大量数据时更加高效。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;应用层协议&lt;/strong&gt;：WebSocket 是一个独立的、基于TCP的应用层协议，它通过一个HTTP请求升级到WebSocket连接。一旦升级完成，HTTP协议就不再参与通信过程。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;端口使用&lt;/strong&gt;：WebSocket 默认使用端口80和443，与HTTP和HTTPS相同，这使得它能够通过大多数防火墙和代理服务器&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/blockquote&gt;
&lt;p&gt;为了减少学习成本，我直接向Kimi要了一段如下要求的代码：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;使用javascript创建websocket，如果连接失败则继续尝试。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;展开&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;// 定义WebSocket的URL,设置成前面搭建的本地websocket地址&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; wsUrl&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; &apos;ws://localhost:8080&apos;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;// 保存WebSocket实例的变量&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;let&lt;/span&gt;&lt;span&gt; ws;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;// 定义重连的函数&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;function&lt;/span&gt;&lt;span&gt; reconnect&lt;/span&gt;&lt;span&gt;() {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    // 尝试重新连接&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    ws &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; new&lt;/span&gt;&lt;span&gt; WebSocket&lt;/span&gt;&lt;span&gt;(wsUrl);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    // 绑定事件处理函数&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    ws.onopen &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; onOpen;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    ws.onmessage &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; onMessage;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    ws.onclose &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; onClose;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    ws.onerror &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; onError;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;// 初始化WebSocket连接&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;function&lt;/span&gt;&lt;span&gt; initWebSocket&lt;/span&gt;&lt;span&gt;() {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    ws &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; new&lt;/span&gt;&lt;span&gt; WebSocket&lt;/span&gt;&lt;span&gt;(wsUrl);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    // 绑定事件处理函数&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    ws.onopen &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; onOpen;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    ws.onmessage &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; onMessage;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    ws.onclose &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; onClose;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    ws.onerror &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; onError;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;// 连接成功的处理函数&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;function&lt;/span&gt;&lt;span&gt; onOpen&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;event&lt;/span&gt;&lt;span&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    console.&lt;/span&gt;&lt;span&gt;log&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&apos;WebSocket connection opened:&apos;&lt;/span&gt;&lt;span&gt;, event);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    // 可以在这里发送消息等操作&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;// 接收到消息的处理函数&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;function&lt;/span&gt;&lt;span&gt; onMessage&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;event&lt;/span&gt;&lt;span&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    console.&lt;/span&gt;&lt;span&gt;log&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&apos;WebSocket message received:&apos;&lt;/span&gt;&lt;span&gt;, event.data);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    // 处理接收到的消息&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;// 连接关闭的处理函数&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;function&lt;/span&gt;&lt;span&gt; onClose&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;event&lt;/span&gt;&lt;span&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    console.&lt;/span&gt;&lt;span&gt;log&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&apos;WebSocket connection closed:&apos;&lt;/span&gt;&lt;span&gt;, event);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;// 连接错误的处理函数&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;function&lt;/span&gt;&lt;span&gt; onError&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;event&lt;/span&gt;&lt;span&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    console.&lt;/span&gt;&lt;span&gt;error&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&apos;WebSocket error occurred:&apos;&lt;/span&gt;&lt;span&gt;, event);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;// 初始化WebSocket连接&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;initWebSocket&lt;/span&gt;&lt;span&gt;();&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;测试WebSocket的代码&lt;/p&gt;
&lt;p&gt;直接在控制台执行，一次成功。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://cdn.1900.live/assets/2024/05/13d4b4de8ce12fa61042611f74f3528f3.png&quot; alt=&quot;Image 6&quot; /&gt;&lt;/p&gt;
&lt;h3&gt;动画效果&lt;/h3&gt;
&lt;p&gt;网页端能接受到数据后基本上整个数据的流转已经完全没有问题了，现在要做的就是以何种方式对数据进行渲染、展示，我目前实现了近似 Innei 大佬博客的淡出淡入效果，这里大概说一下我对动画效果的处理。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;&amp;lt;&lt;/span&gt;&lt;span&gt;div&lt;/span&gt;&lt;span&gt; class&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;actives&quot;&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  &amp;lt;&lt;/span&gt;&lt;span&gt;img&lt;/span&gt;&lt;span&gt; src&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNkYAAAAAYAAjCB0C8AAAAASUVORK5CYII=&quot;&lt;/span&gt;&lt;span&gt; data-tippy-content&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;&quot;&lt;/span&gt;&lt;span&gt; data-tippy-placement&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;bottom-start&quot;&lt;/span&gt;&lt;span&gt; /&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;lt;/&lt;/span&gt;&lt;span&gt;div&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;页面上的actives元素&lt;/p&gt;
&lt;p&gt;展开&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;/* 定义出场动画，元素向x轴方向负距离移动，并提高透明度&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;   实现一个往左渐变消失的动画&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt; */&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;@keyframes&lt;/span&gt;&lt;span&gt; activesOutToLeft&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    from&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        transform&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;translateX&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;0&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        opacity&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;1&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      to&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        transform&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;translateX&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;-100&lt;/span&gt;&lt;span&gt;%&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        opacity&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;0&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;/* 定义进场动画，元素向x轴方向正距离移动，并降低透明度&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;   实现一个往左渐显的入场动画&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt; */&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;@keyframes&lt;/span&gt;&lt;span&gt; activesInFromRight&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    from&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        transform&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;translateX&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;100&lt;/span&gt;&lt;span&gt;%&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        opacity&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;0&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      to&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        transform&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;translateX&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;0&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        opacity&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;1&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;.book-page&lt;/span&gt;&lt;span&gt;{  &lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  .&lt;/span&gt;&lt;span&gt;actives&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        position&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;absolute&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        right&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;3&lt;/span&gt;&lt;span&gt;px&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        bottom&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;-5&lt;/span&gt;&lt;span&gt;px&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        width&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;20&lt;/span&gt;&lt;span&gt;px&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        height&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;20&lt;/span&gt;&lt;span&gt;px&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        display&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;none&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        /* 使用定义的动画，执行时间0.5s */&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        animation&lt;/span&gt;&lt;span&gt;: activesInFromRight &lt;/span&gt;&lt;span&gt;0.5&lt;/span&gt;&lt;span&gt;s&lt;/span&gt;&lt;span&gt; ease-in-out&lt;/span&gt;&lt;span&gt; forwards&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        img&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;            width&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;20&lt;/span&gt;&lt;span&gt;px&lt;/span&gt;&lt;span&gt; !important&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;            height&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;20&lt;/span&gt;&lt;span&gt;px&lt;/span&gt;&lt;span&gt; !important&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;            margin-inline-end&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;0&lt;/span&gt;&lt;span&gt;px&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        /* 单独增加一个exit类，设置后元素执行出场动画&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;           在js可以通过添加和删除这个类达到一个循环动画的效果&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        */&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        &amp;amp;&lt;/span&gt;&lt;span&gt;.exit&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;            animation&lt;/span&gt;&lt;span&gt;: activesOutToLeft &lt;/span&gt;&lt;span&gt;0.5&lt;/span&gt;&lt;span&gt;s&lt;/span&gt;&lt;span&gt; ease-in-out&lt;/span&gt;&lt;span&gt; forwards&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;css动画部分处理&lt;/p&gt;
&lt;p&gt;展开&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;// ...其他代码&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;// 处理接收到的消息，如果是新程序，并且在名单内的时候执行&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;if&lt;/span&gt;&lt;span&gt; (activs.dataset.app &lt;/span&gt;&lt;span&gt;!=&lt;/span&gt;&lt;span&gt; data.process &lt;/span&gt;&lt;span&gt;&amp;amp;&amp;amp;&lt;/span&gt;&lt;span&gt; processName &lt;/span&gt;&lt;span&gt;in&lt;/span&gt;&lt;span&gt; app) {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    fetch&lt;/span&gt;&lt;span&gt;(cdn &lt;/span&gt;&lt;span&gt;+&lt;/span&gt;&lt;span&gt; app[processName].url &lt;/span&gt;&lt;span&gt;+&lt;/span&gt;&lt;span&gt; &quot;!20w&quot;&lt;/span&gt;&lt;span&gt;).&lt;/span&gt;&lt;span&gt;then&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;function&lt;/span&gt;&lt;span&gt; () {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        activs.style.display &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &quot;block&quot;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        // 先给原有元素添加出场动画&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        activs.classList.&lt;/span&gt;&lt;span&gt;add&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;exit&quot;&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        // 等待0.5s后&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        setTimeout&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;function&lt;/span&gt;&lt;span&gt;() {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;            document.&lt;/span&gt;&lt;span&gt;querySelector&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;.actives img&quot;&lt;/span&gt;&lt;span&gt;).src &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;                cdn &lt;/span&gt;&lt;span&gt;+&lt;/span&gt;&lt;span&gt; app[processName].url &lt;/span&gt;&lt;span&gt;+&lt;/span&gt;&lt;span&gt; &quot;!20w&quot;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;            // 删除exit类，删除后会自动执行进场动画&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;            activs.classList.&lt;/span&gt;&lt;span&gt;remove&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;exit&quot;&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;            activs.dataset.app &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; processName;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;            // 同步鼠标悬浮的提示内容&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;            activeTippy.&lt;/span&gt;&lt;span&gt;forEach&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;function&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;e&lt;/span&gt;&lt;span&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;                e.&lt;/span&gt;&lt;span&gt;setContent&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;                    &quot;@1900 在使用 &quot;&lt;/span&gt;&lt;span&gt; +&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;                        app[processName].title &lt;/span&gt;&lt;span&gt;+&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;                        &quot; &quot;&lt;/span&gt;&lt;span&gt; +&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;                        app[processName].action&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;                );&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;            });&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;            counter &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; 0&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        }, &lt;/span&gt;&lt;span&gt;500&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    });&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;} &lt;/span&gt;&lt;span&gt;else&lt;/span&gt;&lt;span&gt; if&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;!&lt;/span&gt;&lt;span&gt;(processName &lt;/span&gt;&lt;span&gt;in&lt;/span&gt;&lt;span&gt; app)) {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    activs.classList.&lt;/span&gt;&lt;span&gt;add&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;exit&quot;&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;// ...其他代码&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;javascript中对元素动画的处理&lt;/p&gt;
&lt;h3&gt;其他处理&lt;/h3&gt;
&lt;p&gt;一、 APP白名单&lt;/p&gt;
&lt;p&gt;我这里用了一个独立的 &lt;code&gt;app.json&lt;/code&gt; 文件维护一个白名单，方便之后在不更新代码的情况下更新名单数据。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    &quot;wechat&quot;&lt;/span&gt;&lt;span&gt;: {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        &quot;title&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;微信&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        &quot;url&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;wechat.png&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        &quot;action&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;摸鱼&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    },&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    &quot;chrome&quot;&lt;/span&gt;&lt;span&gt;: {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        &quot;title&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;Chrome&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        &quot;url&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;chrome.png&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        &quot;action&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;冲浪&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    },&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;二、 VPS的服务搭建&lt;/p&gt;
&lt;p&gt;我这里用的是express的docker镜像，将程序代码上传到绑定的目录中即可。&lt;/p&gt;
&lt;p&gt;另外直接启动项目可能会提示npm包没安装，所以项目的 &lt;code&gt;package.json&lt;/code&gt; 需要加上 &lt;code&gt;&quot;start&quot;: &quot;npm install &amp;amp;&amp;amp; node index.js&quot;&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;展开&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;# Copyright VMware, Inc.&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;# SPDX-License-Identifier: APACHE-2.0&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;services&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  express&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    container_name&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;express-api&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    image&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;docker.io/bitnami/express:4&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    ports&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      - &lt;/span&gt;&lt;span&gt;&apos;3000:3000&apos;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      - &lt;/span&gt;&lt;span&gt;&apos;8080:8080&apos;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    volumes&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      - &lt;/span&gt;&lt;span&gt;&apos;/home/rebron1900/data/express-data:/app&apos;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    logging&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      driver&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;json-file&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      options&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;       max-file&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;5&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;       max-size&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;10m&quot;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;express的docker-compose配置&lt;/p&gt;
&lt;p&gt;然后使用nginx进行了反代。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;server&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    listen &lt;/span&gt;&lt;span&gt;443&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    server_name &lt;/span&gt;&lt;span&gt;api.yourdomain.com; &lt;/span&gt;&lt;span&gt;# 换成你绑定的域名&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    include &lt;/span&gt;&lt;span&gt;snippets/yourssl.conf; &lt;/span&gt;&lt;span&gt;# 换成你的ssl配置&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    location&lt;/span&gt;&lt;span&gt; /actives &lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        proxy_set_header &lt;/span&gt;&lt;span&gt;X-Forwarded-For $proxy_add_x_forwarded_for;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        proxy_set_header &lt;/span&gt;&lt;span&gt;X-Forwarded-Proto $scheme;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        proxy_set_header &lt;/span&gt;&lt;span&gt;X-Real-IP $remote_addr;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        proxy_set_header &lt;/span&gt;&lt;span&gt;Host $http_host;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        proxy_pass &lt;/span&gt;&lt;span&gt;http://127.0.0.1:3000/update; &lt;/span&gt;&lt;span&gt;# docker上websocket公开的 3000 端口&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    location&lt;/span&gt;&lt;span&gt; /actives_ws &lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        proxy_pass &lt;/span&gt;&lt;span&gt;http://127.0.0.1:8080; &lt;/span&gt;&lt;span&gt;# docker上websocket公开的 8080 端口&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        proxy_http_version &lt;/span&gt;&lt;span&gt;1.1&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        proxy_set_header &lt;/span&gt;&lt;span&gt;Upgrade $http_upgrade;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        proxy_set_header &lt;/span&gt;&lt;span&gt;Connection &lt;/span&gt;&lt;span&gt;&quot;upgrade&quot;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        proxy_set_header &lt;/span&gt;&lt;span&gt;Host $host;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        proxy_cache_bypass &lt;/span&gt;&lt;span&gt;$http_upgrade;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    	proxy_buffering &lt;/span&gt;&lt;span&gt;off&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    location&lt;/span&gt;&lt;span&gt; /update &lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    	proxy_set_header &lt;/span&gt;&lt;span&gt;X-Forwarded-For $proxy_add_x_forwarded_for;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        proxy_set_header &lt;/span&gt;&lt;span&gt;X-Forwarded-Proto $scheme;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        proxy_set_header &lt;/span&gt;&lt;span&gt;X-Real-IP $remote_addr;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        proxy_set_header &lt;/span&gt;&lt;span&gt;Host $http_host;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        proxy_pass &lt;/span&gt;&lt;span&gt;http://127.0.0.1:3000/listUpdate;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    location&lt;/span&gt;&lt;span&gt; ~&lt;/span&gt;&lt;span&gt; /.well-known &lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        allow &lt;/span&gt;&lt;span&gt;all&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    client_max_body_size &lt;/span&gt;&lt;span&gt;50m&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;反代配置&lt;/p&gt;
&lt;h3&gt;服务端&lt;/h3&gt;
&lt;a href=&quot;https://github.com/rebron1900/ProcessReporterServer&quot; target=&quot;_blank&quot;&gt;
  &lt;i&gt;&lt;/i&gt; 完整服务端仓库
&lt;/a&gt;
&lt;h3&gt;前端js&lt;/h3&gt;
&lt;p&gt;修改 &lt;code&gt;wsUrl&lt;/code&gt; 、&lt;code&gt;appListUrl&lt;/code&gt; 、&lt;code&gt;cdn&lt;/code&gt; 为你自己的服务地址。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;// 定义测试用的URL&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; wsUrl&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; &quot;ws://localhost:8081/update&quot;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; appListUrl&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; &quot;http://localhost:8080/assets/app.json&quot;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;// 定义正式环境的url&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; cdn&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; &quot;https://cdn.1900.live/apps/&quot;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;// app白名单&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;let&lt;/span&gt;&lt;span&gt; appList &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; {};&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;// 保存WebSocket实例的变量&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;let&lt;/span&gt;&lt;span&gt; ws2;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;// 初始化WebSocket连接&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;export&lt;/span&gt;&lt;span&gt; default&lt;/span&gt;&lt;span&gt; function&lt;/span&gt;&lt;span&gt; initWebSocket&lt;/span&gt;&lt;span&gt;() {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    // 获取远端的app清单&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    fetch&lt;/span&gt;&lt;span&gt;(appListUrl).&lt;/span&gt;&lt;span&gt;then&lt;/span&gt;&lt;span&gt;((&lt;/span&gt;&lt;span&gt;rep&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;span&gt;=&amp;gt;&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        rep.&lt;/span&gt;&lt;span&gt;json&lt;/span&gt;&lt;span&gt;().&lt;/span&gt;&lt;span&gt;then&lt;/span&gt;&lt;span&gt;((&lt;/span&gt;&lt;span&gt;data&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;span&gt;=&amp;gt;&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;            ws2 &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; new&lt;/span&gt;&lt;span&gt; WebSocket&lt;/span&gt;&lt;span&gt;(wsUrl);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;            // 初始化app列表&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;            appList &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; data;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;            // 绑定事件处理函数&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;            ws2.onopen &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; onOpen;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;            ws2.onmessage &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; onMessage;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;            ws2.onclose &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; onClose;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;            ws2.onerror &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; onError;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        });&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    });&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;// 连接成功的处理函数&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;function&lt;/span&gt;&lt;span&gt; onOpen&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;event&lt;/span&gt;&lt;span&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    // console.log(&quot;WebSocket connection opened:&quot;, event);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    // 可以在这里发送消息等操作&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;// 接收到消息的处理函数&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;function&lt;/span&gt;&lt;span&gt; onMessage&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;event&lt;/span&gt;&lt;span&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    // 接受服务端下发的程序数据&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    var&lt;/span&gt;&lt;span&gt; data &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; JSON&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;parse&lt;/span&gt;&lt;span&gt;(event.data);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    // 获取页面上actives dom元素&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    var&lt;/span&gt;&lt;span&gt; activs &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; document.&lt;/span&gt;&lt;span&gt;querySelector&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;.actives&quot;&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    // 之后用来判断的进程名称统一小写，方便比对&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    const&lt;/span&gt;&lt;span&gt; processName&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; data.process.&lt;/span&gt;&lt;span&gt;toLowerCase&lt;/span&gt;&lt;span&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    // 处理接收到的消息&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    // 条件为：当前页面显示的app和服务器下发的app要不一样（说明是新程序），并且程序在app清单中。&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    if&lt;/span&gt;&lt;span&gt; (activs.dataset.app &lt;/span&gt;&lt;span&gt;!=&lt;/span&gt;&lt;span&gt; data.process &lt;/span&gt;&lt;span&gt;&amp;amp;&amp;amp;&lt;/span&gt;&lt;span&gt; processName &lt;/span&gt;&lt;span&gt;in&lt;/span&gt;&lt;span&gt; appList) {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        // 提前缓存图片（我发现大佬博客图片加载有颜值，不过不知道这个有用没有）&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        fetch&lt;/span&gt;&lt;span&gt;(cdn &lt;/span&gt;&lt;span&gt;+&lt;/span&gt;&lt;span&gt; appList[processName].url &lt;/span&gt;&lt;span&gt;+&lt;/span&gt;&lt;span&gt; &quot;!20w&quot;&lt;/span&gt;&lt;span&gt;).&lt;/span&gt;&lt;span&gt;then&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;function&lt;/span&gt;&lt;span&gt; () {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;            // 先将旧的actives执行退场动画&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;            activs.style.display &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &quot;block&quot;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;            activs.classList.&lt;/span&gt;&lt;span&gt;add&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;exit&quot;&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;            // 0.5s后执行更新操作&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;            setTimeout&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;function&lt;/span&gt;&lt;span&gt;() {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;                // 重新设置icon&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;                document.&lt;/span&gt;&lt;span&gt;querySelector&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;.actives img&quot;&lt;/span&gt;&lt;span&gt;).src &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;                    cdn &lt;/span&gt;&lt;span&gt;+&lt;/span&gt;&lt;span&gt; appList[processName].url &lt;/span&gt;&lt;span&gt;+&lt;/span&gt;&lt;span&gt; &quot;!20w&quot;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;                // 执行进场动画&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;                activs.classList.&lt;/span&gt;&lt;span&gt;remove&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;exit&quot;&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;                // 更新dom上app的信息&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;                activs.dataset.app &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; processName;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;                // 这里我用Tippy.js做鼠标悬浮提示，更新悬浮提示内容&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;                activeTippy.&lt;/span&gt;&lt;span&gt;forEach&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;function&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;e&lt;/span&gt;&lt;span&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;                    e.&lt;/span&gt;&lt;span&gt;setContent&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;                        &quot;@1900 在使用 &quot;&lt;/span&gt;&lt;span&gt; +&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;                            appList[processName].title &lt;/span&gt;&lt;span&gt;+&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;                            &quot; &quot;&lt;/span&gt;&lt;span&gt; +&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;                            appList[processName].action&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;                    );&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;                });&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;            }, &lt;/span&gt;&lt;span&gt;500&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        });&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    // 如果是不在白名单里的应用就不显示actives元素了&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    } &lt;/span&gt;&lt;span&gt;else&lt;/span&gt;&lt;span&gt; if&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;!&lt;/span&gt;&lt;span&gt;(processName &lt;/span&gt;&lt;span&gt;in&lt;/span&gt;&lt;span&gt; appList)) {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        activs.classList.&lt;/span&gt;&lt;span&gt;add&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;exit&quot;&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;// 连接关闭的处理函数&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;function&lt;/span&gt;&lt;span&gt; onClose&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;event&lt;/span&gt;&lt;span&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    // 尝试重新连接&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    document.&lt;/span&gt;&lt;span&gt;querySelector&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;.actives&quot;&lt;/span&gt;&lt;span&gt;).classList.&lt;/span&gt;&lt;span&gt;add&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;exit&quot;&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    setTimeout&lt;/span&gt;&lt;span&gt;(initWebSocket, &lt;/span&gt;&lt;span&gt;5000&lt;/span&gt;&lt;span&gt;); &lt;/span&gt;&lt;span&gt;// 5秒后尝试重新连接&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;// 连接错误的处理函数&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;function&lt;/span&gt;&lt;span&gt; onError&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;event&lt;/span&gt;&lt;span&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    // 尝试重新连接&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    document.&lt;/span&gt;&lt;span&gt;querySelector&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;.actives&quot;&lt;/span&gt;&lt;span&gt;).classList.&lt;/span&gt;&lt;span&gt;add&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;exit&quot;&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;HTML和CSS部分&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;&amp;lt;&lt;/span&gt;&lt;span&gt;div&lt;/span&gt;&lt;span&gt; class&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;actives&quot;&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  &amp;lt;&lt;/span&gt;&lt;span&gt;img&lt;/span&gt;&lt;span&gt; src&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNkYAAAAAYAAjCB0C8AAAAASUVORK5CYII=&quot;&lt;/span&gt;&lt;span&gt; data-tippy-content&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;&quot;&lt;/span&gt;&lt;span&gt; data-tippy-placement&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;bottom-start&quot;&lt;/span&gt;&lt;span&gt; /&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;lt;/&lt;/span&gt;&lt;span&gt;div&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;页面结构&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;/* 定义出场动画，元素向x轴方向负距离移动，并提高透明度&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;   实现一个往左渐变消失的动画&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt; */&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;@keyframes&lt;/span&gt;&lt;span&gt; activesOutToLeft&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    from&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        transform&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;translateX&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;0&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        opacity&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;1&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      to&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        transform&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;translateX&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;-100&lt;/span&gt;&lt;span&gt;%&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        opacity&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;0&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;/* 定义进场动画，元素向x轴方向正距离移动，并降低透明度&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;   实现一个往左渐显的入场动画&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt; */&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;@keyframes&lt;/span&gt;&lt;span&gt; activesInFromRight&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    from&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        transform&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;translateX&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;100&lt;/span&gt;&lt;span&gt;%&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        opacity&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;0&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      to&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        transform&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;translateX&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;0&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        opacity&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;1&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;.actives&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    position&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;absolute&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    right&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;3&lt;/span&gt;&lt;span&gt;px&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    bottom&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;-5&lt;/span&gt;&lt;span&gt;px&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    width&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;20&lt;/span&gt;&lt;span&gt;px&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    height&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;20&lt;/span&gt;&lt;span&gt;px&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    display&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;none&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    /* 使用定义的动画，执行时间0.5s */&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    animation&lt;/span&gt;&lt;span&gt;: activesInFromRight &lt;/span&gt;&lt;span&gt;0.5&lt;/span&gt;&lt;span&gt;s&lt;/span&gt;&lt;span&gt; ease-in-out&lt;/span&gt;&lt;span&gt; forwards&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;.actives&lt;/span&gt;&lt;span&gt; img&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    width&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;20&lt;/span&gt;&lt;span&gt;px&lt;/span&gt;&lt;span&gt; !important&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    height&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;20&lt;/span&gt;&lt;span&gt;px&lt;/span&gt;&lt;span&gt; !important&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    margin-inline-end&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;0&lt;/span&gt;&lt;span&gt;px&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;/* 单独增加一个exit类，设置后元素执行出场动画&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;   在js可以通过添加和删除这个类达到一个循环动画的效果&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;*/&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;.actives.exit&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    animation&lt;/span&gt;&lt;span&gt;: activesOutToLeft &lt;/span&gt;&lt;span&gt;0.5&lt;/span&gt;&lt;span&gt;s&lt;/span&gt;&lt;span&gt; ease-in-out&lt;/span&gt;&lt;span&gt; forwards&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;css部分&lt;/p&gt;</content:encoded><category>tech</category><category>WebSocket</category><category>博客</category></item><item><title>收集的API</title><link>https://yourdomain.com/blog/202311140945/</link><guid isPermaLink="true">https://yourdomain.com/blog/202311140945/</guid><description>分类：tech | 标签：API, 工具</description><pubDate>Tue, 14 Nov 2023 01:45:19 GMT</pubDate><content:encoded>&lt;p&gt;在此收集一些不需要Token认证的API。&lt;/p&gt;
&lt;h4&gt;&lt;strong&gt;推荐的API&lt;/strong&gt;&lt;/h4&gt;
&lt;p&gt;在此推荐一些比较好用的 API ，皆为从网上收集，其中的 &lt;code&gt;{url}&lt;/code&gt; 部分替换成你自己的网址，&lt;code&gt;{height}&lt;/code&gt; 为高度，&lt;code&gt;{width}&lt;/code&gt; 为宽度。顺便我会放上预览图，实时检测该API是否失效。&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;自动生成缩略图的API&lt;/strong&gt; - WordPress&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;https://s0.wp.com/mshots/v1/{url}?w={width}&amp;amp;h={height}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;https://s1.wp.com/mshots/v1/{url}?w={width}&amp;amp;h={height}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;https://s2.wp.com/mshots/v1/{url}?w={width}&amp;amp;h={height}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;https://s3.wp.com/mshots/v1/{url}?w={width}&amp;amp;h={height}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;https://s4.wp.com/mshots/v1/{url}?w={width}&amp;amp;h={height}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;https://s5.wp.com/mshots/v1/{url}?w={width}&amp;amp;h={height}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&quot;https://s1.wp.com/mshots/v1/https://suus.me?w=500&amp;amp;h=500&quot; alt=&quot;示例&quot; /&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;图片加速接口API&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;ul&gt;
&lt;li&gt;WordPress&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;https://i0.wp.com/{url}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;https://i1.wp.com/{url}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;https://i2.wp.com/{url}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;https://i3.wp.com/{url}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;其中url部分是不带协议头的，即http://、https://&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;百度加速&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;https://image.baidu.com/search/down?url={url}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;url填完整url，注意该url需要是直链，重定向链接会失效。&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;CDNjson&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;(旧)https://cdn.cdnjson.com/{url}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;(新)https://cdn.cdnjson.com/pic.html?url={url}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;其中url部分是不带协议头的，即https://&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;注意该url需要是直链，重定向链接会失效。&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Favicon图片获取接口API&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;ul&gt;
&lt;li&gt;Favicon.im&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;&amp;lt;img src=&quot;https://favicon.im/hey.com&quot; alt=&quot;hey.com favicon&quot; /&amp;gt;   // 小尺寸&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;lt;img src=&quot;https://favicon.im/hey.com?larger=true&quot; alt=&quot;hey.com favicon (large)&quot; /&amp;gt;   // 大尺寸&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&quot;https://favicon.im/hey.com&quot; alt=&quot;示例&quot; /&gt;&lt;/p&gt;</content:encoded><category>tech</category><category>API</category><category>工具</category></item><item><title>《超级马里奥大电影》有感</title><link>https://yourdomain.com/blog/202304122251/</link><guid isPermaLink="true">https://yourdomain.com/blog/202304122251/</guid><description>分类：life | 标签：电影</description><pubDate>Wed, 12 Apr 2023 14:51:28 GMT</pubDate><content:encoded>&lt;p&gt;许多游戏公司都纷纷将自家新一代的热门作改编成影集，如顽皮狗就已经释出经典游戏《最后生还者》（The Last of Us）、《秘境探险》（Uncharted）系列。随着各电玩公司的更近，给人守旧印象但又屡屡革新的老牌游戏公司任天堂也推出了自己的当家游戏改编《超级马力欧大电影》（The Super Mario Bros. Movie）。&lt;/p&gt;
&lt;p&gt;周末，趁着家里小朋友生病请假，所以就假借小朋友的名义去回味下我那已经逝去很久的童年了。&lt;/p&gt;
&lt;p&gt;《超级马里奥》这款游戏相信是绝大多数 70、80 后童年的美好回忆，除了陪伴你玩耍的小伙伴之外，还有一个在水管中活蹦乱跳、顶箱子、靠吃蘑菇长大，他有着大大鼻子、头戴着帽子、身穿着背带工作服，他长年担任着任天堂的招牌角的的马里奥呢？在游戏中，操纵马力欧跋山涉水、闯过一关又一关，最终救出被酷霸王绑架的桃花公主。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://bu.dusays.com/2023/04/12/6436d097b3e1b.webp&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;那时候的 FC 红白机，还是金手指的游戏卡带，游戏大小就只有几十 K，与现在动辄上几十个 G 的游戏比起来真的是难以想象，放现在，画面粗糙、音乐单调，就算是那样，那也撑起了我们整个童年。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://bu.dusays.com/2023/04/13/6436d59c53351.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;还是说回电影本身吧，整体来说还是很欢乐的，配乐也很还原，小朋友大笑着看完了整场。影片里有个缩小蘑菇，回忆起来貌似在游戏里没有见过这个效果，只是记得吃过长大蘑菇后碰到乌龟、板栗或者其它怪物后会变回原来大小，欣许是蘑菇踩的太少，只顾着过关了。&lt;/p&gt;
&lt;p&gt;想想，小的时候怕挨骂，总是背着父母玩儿的胆战心惊的。现在不用担心了，可惜没有那个时间和精力玩了，时间啊，总是在我们不经意间擦身而过。&lt;/p&gt;
&lt;p&gt;今天，家里小朋友问我：“世界有尽头吗？是不是天涯海角就是尽头？”我顿了顿，思考片刻，竟然发现这个问题我也很迷茫啊，无奈，只有告诉小朋友：“世界没有尽头，就和时间一样……”&lt;/p&gt;</content:encoded><category>life</category><category>电影</category></item><item><title>Web 中使用 3D 图形</title><link>https://yourdomain.com/blog/202304022016/</link><guid isPermaLink="true">https://yourdomain.com/blog/202304022016/</guid><description>分类：tech | 标签：Web</description><pubDate>Sun, 02 Apr 2023 12:16:28 GMT</pubDate><content:encoded>&lt;h3&gt;引言&lt;/h3&gt;
&lt;p&gt;随着硬件水平和软件环境的改善，Web 即将迎来自己的全新时代——一个 Web 3.0 时代！在 Web 这个世界窗口，我们将依旧开放地看到一个美好的未来世界。今天，我们走出第一步，使用 3D 图形作为我们在未来世界的自己。&lt;/p&gt;

&lt;p&gt;

&lt;/p&gt;
&lt;h3&gt;视野&lt;/h3&gt;
&lt;p&gt;随着分布式区块链应用的发展，Web 3.0 变得越来越火热，从全球化的数字加密货币，到公开透明不可篡改的智能合约，数字计算而来的真实唯一和去中心化的自由向往，带给人们对未来交易和未来政治无限遐想。我不知道去中心化和真实性是否会是 Web 3.0 的关键因素，然而随着世界平面上缓缓升起的一座山峰，随数字计算而来的学习机器正在迅速让 3D 图形成为 Web 3.0 的不可或缺。&lt;/p&gt;
&lt;p&gt;对比二十年前的照片，你可能觉得技术大有进展；对比十年前的照片，你可能觉得技术小有进展；对比两年前的照片，你可能会觉得技术没有进展——你甚至会相信未来不会有什么改变了。对于 Web，也是如此，然而当你在 Web 体验了 3D 图形后，你看着眼前的 Web 就如看着二十年前的网页——将二维图片作为头像是多么原始，我们每天所触碰的世界难道是平面吗？3D 图形是未来！&lt;/p&gt;
&lt;p&gt;得益于硬件技术的发展，我们能传输和计算的信息量越来越大，我们在互联网交流文字、图片、音频、视频，我们的生活甚至人类的文明逐渐融入互联网。每种介质所能承载和传递的价值各有不同，3D 图形会有什么不同呢？对比现在十分流行的视频，视频只可以观看，3D 图形却可以交互浸玩，更有趣。在相同二维屏幕上，已能感受到 3D 图形的魅力，何况独有的 AR 体验和 VR 设备？&lt;/p&gt;
&lt;p&gt;若与神经相接，那现在很酷的各种设备，是否也很原始呢？更不用说内容带给我们的体验感受。游戏和互联网已经让很多人沉迷，而当 3D 未来到来时，我们的时间维是在现实三维空间还是虚拟 3D 世界？那时候的虚拟又是否为现实？毕竟时间维所在即是真实。到那时，我们还需要物质实物吗？我们外出活动是否只为保持身体健康？至于物质工作，则通过控制机器实现。若医疗继续发展，我们终于可以无需运动保持健康，那我们仅需的物质实物不就只是一个囊舱吗？&lt;/p&gt;
&lt;p&gt;又或我们应该直接放弃身体，仅仅需要保证大脑运转？又或我们可以选择离开生物体，迁移到硅基网络，成为机器里面的不朽灵魂？我们将明白智慧生命的本质？我们将推崇新的自由平等？还是理性的阶级等级？我们将通过电磁遨游这个宇宙？成为这个宇宙的长子？我们是否还会怀恋祖先的碳基身体呢？我们意识中的时间尺度会从行星环绕周期膨胀等同到恒星生命周期吗？还是坍缩等同到粒子周期呢？我们甚至还会怀恋死亡？我们是否真能摆脱死亡走向终极自由呢？&lt;/p&gt;
&lt;hr /&gt;
&lt;h3&gt;建模&lt;/h3&gt;
&lt;p&gt;我们使用 &lt;a href=&quot;https://readyplayer.me/&quot;&gt;Ready Player Me&lt;/a&gt; 的服务，它可以根据二维照片生成可定制的 3D 图形，也就是我们需要的 Web 3.0 头像！&lt;/p&gt;
&lt;h3&gt;渲染&lt;/h3&gt;
&lt;p&gt;我们使用封装好的 &lt;a href=&quot;https://modelviewer.dev/&quot;&gt;model-viewer&lt;/a&gt; 组件实现 3D 图形在 Web 上的渲染，且支持调用设备的 AR 交互——全新体验！&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;&amp;lt;&lt;/span&gt;&lt;span&gt;script&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  type&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;module&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  src&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;https://unpkg.com/@google/model-viewer/dist/model-viewer.min.js&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;gt;&amp;lt;/&lt;/span&gt;&lt;span&gt;script&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;lt;&lt;/span&gt;&lt;span&gt;model-viewer&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  src&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;/3d-graphics/reuixiy.glb&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  ar&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  ar-modes&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;webxr scene-viewer quick-look&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  camera-controls&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  camera-orbit&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;-18.9deg 85.8deg auto&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  autoplay&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  animation-name&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;idle_eyes_2&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  environment-image&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;neutral&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  shadow-intensity&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;1&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  style&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;width: 100%; height: 420px&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;gt;&amp;lt;/&lt;/span&gt;&lt;span&gt;model-viewer&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;hr /&gt;
&lt;p&gt;&lt;strong&gt;扩展阅读&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://poly.cam/&quot;&gt;Polycam&lt;/a&gt; — 使用手机的 LiDAR 传感器建模&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.blender.org/&quot;&gt;Blender&lt;/a&gt; — The Freedom to Create&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://wolf3d.io/&quot;&gt;Wolf3D&lt;/a&gt; — Powering the next generation of online identities&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://hubs.mozilla.com/&quot;&gt;Hubs&lt;/a&gt; — Meet, share and collaborate together in private 3D virtual spaces&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://decentraland.org/&quot;&gt;Decentraland&lt;/a&gt; — Determine the future of the virtual world&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.oculus.com/&quot;&gt;Oculus&lt;/a&gt; — LIVE THE UNBELIEVABLE&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://arvr.google.com/&quot;&gt;Google AR &amp;amp; VR&lt;/a&gt; — Do more with what you see&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://mixedreality.mozilla.org/&quot;&gt;Mozilla Mixed Reality&lt;/a&gt; — Mixed Reality for the Open Web&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.apple.com/augmented-reality/&quot;&gt;Apple Augmented Reality&lt;/a&gt; — Dive into the world of augmented reality&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://tech.fb.com/ar-vr/&quot;&gt;https://tech.fb.com/ar-vr/&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.microsoft.com/en-us/mixed-reality/windows-mixed-reality&quot;&gt;https://www.microsoft.com/en-us/mixed-reality/windows-mixed-reality&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://threejs.org&quot;&gt;https://threejs.org&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://johwska.com&quot;&gt;https://johwska.com&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/BKcore/HexGL&quot;&gt;https://github.com/BKcore/HexGL&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</content:encoded><category>tech</category><category>Web</category></item><item><title>看《深海》有感</title><link>https://yourdomain.com/blog/202303282358/</link><guid isPermaLink="true">https://yourdomain.com/blog/202303282358/</guid><description>分类：life | 标签：电影, 观后感</description><pubDate>Tue, 28 Mar 2023 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;春节档的时候，由于身体原因没办法陪小朋友去影院看《深海》，最近在线上有了，所以就有了机会陪小朋友在家里欣赏此片。&lt;/p&gt;
&lt;p&gt;看《深海》之前也未曾在网上搜索过本片的任何信息，只是抱着刚好有一部动画片可以和小朋友看就拿来全家齐分享了。&lt;/p&gt;
&lt;p&gt;开篇的油画风格说实话挺吸引眼球的，其中那种重颗粒感非常符合我个人有时候头晕眼冒金星的样子，所以对于导演想要表达的内涵还算是可以 get 到 (类似病人视角嘛不难理解)。全程各种福瑞的时候我当时就理解为类似《奇巧计程车》的概念，虽然翻底的时候不完全一样但是还算不难懂。&lt;/p&gt;
&lt;p&gt;其实到这里作为原创动画的新鲜感其实已经不太好了，我喜欢原创动画的主因还是在于你会想知道剧情的发展。这个片子全片观感更多的像是《千与千寻》+《少年派》的杂糅版，故事完成度很高但既视感太过于强烈，这也是我评价下跌的主要原因。&lt;/p&gt;
&lt;p&gt;其次观感最差的地方在于，导演太想讲一个走出内心绝境的故事了，但作为故事一部分的“小丑先生”用生命去救赎另一个人的方式实在是表达上有点割裂。&lt;/p&gt;
&lt;p&gt;我们常说一部好的剧本我们要看到的是人物的成长与人物的弧光，现在作为成人的“小丑”展现了大人们的弧光，那么作为主角的女主表现出的成长又在哪呢？&lt;/p&gt;
&lt;p&gt;全片的呈现给观众的感觉，更像是一个离异家庭的小孩在不停的用一种撒娇的方式去给他人制造负担。甚至在最后翻底的时候依然处于，啊我和自己和解了，啊别人为我而死但我收获了希望。&lt;/p&gt;
&lt;p&gt;我不想苛责谁的不对，相反作为单亲家庭长大的小孩我很能理解希望独立、人前成熟的感受，但你得有正确的三观啊。&lt;/p&gt;
&lt;p&gt;如果因为我的抑郁也好，我的其他什么问题也好，他人为我而失去生命，我怎么可能会简简单单的去收获希望从此获得开朗活泼。哪怕最后主角变得背负更强烈的责任感，从而去负重前行我都觉得比现在这个结局更有可信度。&lt;/p&gt;
&lt;p&gt;几首 BGM 的插入总体感觉不错。&lt;/p&gt;
&lt;p&gt;不过以上是我作为成年人的主观感受，对小朋友来讲都不重要，小朋友在参宿找妈妈的场景中哭的稀里哗啦的。我和妈妈也甚是感动。&lt;/p&gt;</content:encoded><category>life</category><category>电影</category><category>观后感</category></item><item><title>结石、高热、住院，打卡人生第一次手术</title><link>https://yourdomain.com/blog/202302152120/</link><guid isPermaLink="true">https://yourdomain.com/blog/202302152120/</guid><description>分类：life | 标签：生活, 思考</description><pubDate>Wed, 15 Feb 2023 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;年前因为结石发热住了院，并进行了手术，四个小时的手术也很顺利。就这样，成功打卡了一次人生中第一次手术的经历✅，并从此在身上留下了疤痕，也是记忆、教训。&lt;/p&gt;
&lt;p&gt;本来和医生约好在年前进行第二次手术的，可人算不如天算，疫情来势汹汹，把所有的医疗资源都挤兑掉了，对于非新冠疾病人员以及非急诊人员都将延期推后。无奈，只得等到过完年再次进行二期手术。&lt;/p&gt;
&lt;h3&gt;开年住院&lt;/h3&gt;
&lt;p&gt;第一天入院，被安排在了一个双人间，来院治疗的病人真的比想象中多，在我住院期间，每天都有病人在等待治疗，等待入院，由于没有病房安排，就只能等待或者择期。医生和护士每天都在忙碌中奔跑，对，你没有看错，是奔跑，一个个忙的脚不沾地，医生基本上每日都在加班，真的是难以想象。&lt;/p&gt;
&lt;p&gt;住院期间，每日住院部都很早就关灯休息了，整层一篇漆黑，由于我习惯晚睡，手术后的前期，不能走动，我只能整宿看着病房的天花板，手机在病房竟然没有信号，也没有网上说的 WiFi😒，就更不要说快不快了。&lt;/p&gt;
&lt;p&gt;隔壁床住着一位大叔，比我早一日住院，和我一样的手术，我的手术被安排在了入院后的第三天，同样，这次也很顺利，也成功的在身上又加上了一根管子（原来已经有两根了），由于是全身麻醉，进了病房之后，我才开始有了知觉，开始感觉到痛，感觉到冷。&lt;/p&gt;
&lt;p&gt;总之，手术后开始的几天总是难熬的，无法入睡，身体无法动，唯一能动的除了眼镜嘴巴就是手了，身上插满了各种管子，总感觉自己像是一部机器或是机器人，只是比它们多了一口气而已。&lt;/p&gt;
&lt;p&gt;术后，很虚弱，每日只能进食一些流食，比如粥汤一些好消化的食物，第三天的时候终于可以坐起来了，但依旧无法下地，在床上躺的很难受了，哪儿哪儿都疼，真是不知筋力衰多少，但觉新来懒上楼。&lt;/p&gt;
&lt;h3&gt;出院&lt;/h3&gt;
&lt;p&gt;终于到了出院的时间了，我的主治医生，只是跟我交待了要出院以及定时复查之类的话就匆匆要去手术室了。病区的医生护士依旧在忙碌，依旧还有病人在排队等待入院。真心希望医院里可以少一些病人，我们的身体保持以往的活力，康健有力。&lt;/p&gt;
&lt;p&gt;虽说第二次手术也如期进行并顺利结束了，可现在身体里还有一个支架和管子，在未来的一个月后还需要再次入院进行去管手术。&lt;/p&gt;
&lt;p&gt;这次身体抱恙，让我深切体会有一个健康的体魄是多么重要，尤其前三年的疫情，一个好的生活习惯，一个好的健康的身体，就是我们每一个人的资本，老话讲“有啥别有病，没啥别没钱”。&lt;/p&gt;
&lt;p&gt;此文就是流水账。祝君健康可爱，幸福平安！与君共勉。&lt;/p&gt;</content:encoded><category>life</category><category>生活</category><category>思考</category></item><item><title>暮色如墨</title><link>https://yourdomain.com/blog/202211152320/</link><guid isPermaLink="true">https://yourdomain.com/blog/202211152320/</guid><description>分类：thoughts | 标签：散文, 自然</description><pubDate>Tue, 15 Nov 2022 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;今年的冬天，似乎比往年来的晚了一些，十一月的西安终于在上周末迎来了第一波寒潮，山区下了初冬的第一场雪。&lt;/p&gt;
&lt;p&gt;初冬的暮色，像一首苍老的歌。萧瑟的风景，像邂逅了一场疼痛的初恋，落叶别树，飘零随风。顿觉些许凉意袭身，不禁想起孟浩然的诗句，“不觉初冬夜渐长，清风习习重凄凉。”&lt;/p&gt;
&lt;p&gt;落日的余晖洒下万道光芒，高楼、树木交相辉映。&lt;/p&gt;
&lt;p&gt;暮色越来越重，此时，已是华灯初上、灯火阑珊，灿烂的灯光虽不及白日那么透亮，却也在初冬的暮色中驱走些许的黑暗和寒意。&lt;/p&gt;
&lt;p&gt;路上车水马龙，行人匆忙，每个人都在朝着家的方向奔赴，千家灯火万家难，看着他人的幸福，徒生向往；注目他人的难处，油然怜悯。他们时而欢笑，时而落泪。因为小事而倍感幸福，因为小事而万分感伤。&lt;/p&gt;
&lt;p&gt;想起那首&lt;a href=&quot;https://zh.wikipedia.org/wiki/%E7%8B%84%E5%85%B0%C2%B7%E6%89%98%E9%A9%AC%E6%96%AF&quot;&gt;Dylan Thomas&lt;/a&gt;的诗&lt;a href=&quot;https://poets.org/poem/do-not-go-gentle-good-night&quot;&gt;《Do not go gentle into that good night》&lt;/a&gt;：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;[!NOTE]- 《Do not go gentle into that good night》
Do not go gentle into that good night,&lt;/p&gt;
&lt;p&gt;不要温和地走进那个良夜，&lt;/p&gt;
&lt;p&gt;Old age should burn and rave at close of day;&lt;/p&gt;
&lt;p&gt;白昼将尽，暮年仍应燃烧咆哮;&lt;/p&gt;
&lt;p&gt;Rage, rage against the dying of the light.&lt;/p&gt;
&lt;p&gt;怒斥吧，怒斥光的消逝。&lt;/p&gt;
&lt;p&gt;……&lt;/p&gt;
&lt;/blockquote&gt;</content:encoded><category>thoughts</category><category>散文</category><category>自然</category></item><item><title>在虚拟与现实的边界寻找人性</title><link>https://yourdomain.com/blog/202211121203/</link><guid isPermaLink="true">https://yourdomain.com/blog/202211121203/</guid><description>分类：life | 标签：科幻, 影视</description><pubDate>Sat, 12 Nov 2022 04:03:00 GMT</pubDate><content:encoded>&lt;p&gt;《边缘世界》改编自威廉吉布森的同名小说，由《绝地计划》编剧史考特史密斯执笔剧本并担任节目统筹，《西部世界》导演文森佐纳塔利执导。&lt;/p&gt;
&lt;p&gt;这部科幻剧以其独特的未来视角和深刻的人性思考，为我们展现了一个既熟悉又陌生的世界。&lt;/p&gt;
&lt;p&gt;看之前没做功课 单纯就是看到首页推播卡片 就看了&lt;/p&gt;
&lt;p&gt;目前播到第四集 前两集挖坑 然后时序 场景 人物 跳来跳去 会让人看不太懂&lt;/p&gt;
&lt;p&gt;我看到第三集 又回头看了一次前两集，感觉第四集开始有在收线了 所有的人物关系都能凑在一起。&lt;/p&gt;
&lt;p&gt;故事剧情就是围绕著女主角戴了 VR 装备后，意识透过量子纠缠 会跳到好几十年后的世界 操控著未来世界的人物载体。&lt;/p&gt;
&lt;p&gt;原来世界再过几年就会因污染逐渐毁灭 感觉女主角一行人有掌握解救世界的能力&lt;/p&gt;
&lt;p&gt;反派要阻止女主角穿越到未来 正派要请女主角到反派基地找一个机密&lt;/p&gt;
&lt;p&gt;但是反派是一个科研组织 正派是一个财阀&lt;/p&gt;
&lt;p&gt;目前演到这里 之后到底会不会正邪反转也不知道。&lt;/p&gt;
&lt;p&gt;整体来说动作场面不错 美术也很漂亮，科幻设定虽然不算新颖 (头号玩家 猎杀代理人…)，但是还算是很有看头 而且不像《西部世界》一样过于卖弄玄虚，应该是近期值得一看的科幻美剧。&lt;/p&gt;
&lt;h2&gt;思考&lt;/h2&gt;
&lt;p&gt;《边缘世界》不仅是一部优秀的科幻剧，更是一面镜子，映照出我们对未来的期待与忧虑。它提醒我们，在拥抱科技进步的同时，不要忘记守护人性的温度。在虚拟与现实的边界上，找到属于自己的平衡点。&lt;/p&gt;
&lt;p&gt;这部剧给我们的最大启示或许是：无论科技如何发展，人性的光辉才是照亮未来的明灯。在这个日益数字化的世界里，我们更需要保持清醒的头脑和温暖的心。&lt;/p&gt;</content:encoded><category>life</category><category>科幻</category><category>影视</category></item></channel></rss>