<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0"
  xmlns:atom="http://www.w3.org/2005/Atom"
  xmlns:content="http://purl.org/rss/1.0/modules/content/">
  <channel>
    <title>搬砖日记</title>
    <link>https://www.banzhuanriji.com/</link>
    
    <image>
      <url>https://www.banzhuanriji.com/icon.ico</url>
      <title>搬砖日记</title>
      <link>https://www.banzhuanriji.com/</link>
    </image>
    
    <atom:link href="https://www.banzhuanriji.com/rss.xml" rel="self" type="application/rss+xml"/>
    
    <description>白天给代码写对象，深夜给自己写日记</description>
    <pubDate>Fri, 06 Nov 2026 11:41:07 GMT</pubDate>
    <generator>http://hexo.io/</generator>
    
    <item>
      <title>我想给自己建立一个长期运行的 AI 助手</title>
      <link>https://www.banzhuanriji.com/posts/a-long-term-ai-assistant/</link>
      <guid>https://www.banzhuanriji.com/posts/a-long-term-ai-assistant/</guid>
      <pubDate>Fri, 06 Nov 2026 10:11:49 GMT</pubDate>
      
        
        
          
          
      <description></description>
          
        
      
      
      
      <content:encoded><![CDATA[<p>最近一直在想一件事：能不能把 AI 真正融入自己的生活？</p><p>这里说的融入，不是每天打开 ChatGPT 问几个技术问题，也不是让 AI 帮忙写文章、整理资料。我想做的是更进一步的事情。</p><p>我希望有一个 AI，它能够逐渐了解我。</p><p>知道我做什么工作，有哪些能力，经济状况怎么样，平时有什么习惯，喜欢什么、不喜欢什么，过去经历过什么事情，做过哪些选择，又为什么做出这些选择。</p><p>甚至包括一些不太容易在一次对话中讲清楚的东西，比如我的性格、价值观、对未来的期待，以及我对某些事情的真实看法。</p><p>这些信息不需要一次性告诉它，而是在日常生活中慢慢积累。</p><p>以后无论遇到工作、财务、生活还是其他方面的问题，我都可以直接把事情告诉它，而不需要每次重新介绍自己的背景。</p><p>我希望它能够结合过去的信息，帮我分析眼前的问题，并告诉我接下来应该怎么做。</p><p>这可能是我想做这个项目最主要的原因。</p><h2 id="我不缺一个能回答问题的-AI"><a href="#我不缺一个能回答问题的-AI" class="headerlink" title="我不缺一个能回答问题的 AI"></a>我不缺一个能回答问题的 AI</h2><p>现在的 AI 已经很擅长回答问题了。</p><p>遇到技术问题，可以让它分析代码；准备买东西，可以让它对比产品；想学习新知识，可以让它整理学习路线。</p><p>但我发现，这些事情有一个共同的问题：AI 通常只知道我当前告诉它的信息。</p><p>比如我问它要不要换工作，它可以分析行业趋势、薪资待遇和职业发展，但它未必真正了解我目前的工作状态、经济压力、过去的职业经历，以及我为什么想换工作。</p><p>同样的问题，放在不同的人身上，答案可能完全不同。</p><p>我觉得这恰恰是个人 AI 最有价值的地方。</p><p>如果 AI 真正了解一个人，那么它提供的就不应该只是通用答案，而应该是结合这个人的实际情况给出的建议。</p><p>举个简单的例子。</p><p>假设我最近想买一辆车。</p><p>普通 AI 可以帮我对比车型、价格、油耗和配置。但如果它知道我目前的收入、存款、投资计划、日常出行需求，以及未来一两年的其他打算，它就能进一步判断这笔消费是否合理。</p><p>甚至它可能会直接告诉我：现在买车并不是一个好时机。</p><p>我需要的其实就是这种能力。</p><p>我不希望 AI 只是顺着我的想法说话。如果我的决定不合理，我希望它能够指出问题，甚至反对我。</p><p>有时候，一个真正有价值的建议，可能恰恰是告诉我某件事不值得做。</p><h2 id="我希望它不只是顾问，还能帮我做事"><a href="#我希望它不只是顾问，还能帮我做事" class="headerlink" title="我希望它不只是顾问，还能帮我做事"></a>我希望它不只是顾问，还能帮我做事</h2><p>如果只是提供建议，我觉得还是不够。</p><p>很多时候，我们并不是不知道应该做什么，而是知道了之后没有继续推进。</p><p>比如想换工作，AI 可以帮我分析职业发展，但接下来还需要整理简历、搜索岗位、研究公司、比较薪资。</p><p>想学习一项新技术，AI 可以制定计划，但真正执行时，还需要整理资料、安排时间、检查学习进度。</p><p>所以我希望这个助手具备一定的执行能力。</p><p>它不应该每次都停留在「建议你这样做」，而应该尽可能帮我把事情推进下去。</p><p>例如：</p><ul><li>我想换工作，它可以帮我研究岗位、整理简历和分析机会。</li><li>我想学习某项技术，它可以帮我制定计划、整理资料和安排练习。</li><li>我想管理自己的财务，它可以帮我整理收支、分析消费和评估投资风险。</li><li>我有一个新项目想法，它可以帮我分析可行性、拆分任务，甚至参与开发。</li><li>我有一件事情一直拖着没做，它可以帮我分析原因，并制定实际可行的推进计划。</li></ul><p>当然，执行能力不意味着什么都交给 AI。</p><p>涉及花钱、发送邮件、对外沟通或者其他可能产生实际后果的操作，还是应该由我确认。</p><p>我希望它能主动一些，但不能失去边界。</p><h2 id="一个-AGENT-md，管理-AI-应该怎么做事"><a href="#一个-AGENT-md，管理-AI-应该怎么做事" class="headerlink" title="一个 AGENT.md，管理 AI 应该怎么做事"></a>一个 AGENT.md，管理 AI 应该怎么做事</h2><p>既然准备长期使用，我认为首先需要解决的不是记忆问题，而是行为规范问题。</p><p>我准备单独维护一份 <code>AGENT.md</code>。</p><p>这份文件不负责记录我的个人经历，而是告诉 AI 应该如何与我协作。</p><p>例如：</p><ul><li>不要无条件认同我的想法。</li><li>不要为了让我满意而刻意美化事实。</li><li>分析问题时，要结合我的实际情况，而不是只给通用答案。</li><li>信息不足时，应该先确认，而不是自己编造。</li><li>做重要决策时，要考虑长期影响，而不只是眼前收益。</li><li>给出建议时，尽量明确推荐方案，并说明原因。</li><li>发现我的行为与长期目标存在冲突时，应该主动提醒。</li><li>能够帮助执行的事情，不要只停留在提出建议。</li></ul><p>我希望这份文件能够保持相对精简。</p><p>它更像是这个 AI 的工作准则，而不是一本个人说明书。</p><p>我不想每次更换模型、开启新对话，都需要重新解释希望 AI 如何工作。</p><p>只要读取这份文件，就应该能够基本理解它在这个项目中应该扮演什么角色。</p><h2 id="个人经历应该放在哪里？"><a href="#个人经历应该放在哪里？" class="headerlink" title="个人经历应该放在哪里？"></a>个人经历应该放在哪里？</h2><p>这部分我考虑使用 Markdown 文件来维护。</p><p>原因很简单：Markdown 足够轻量，不依赖特定平台，自己可以随时查看和修改，未来也方便迁移到其他 AI 工具。</p><p>初步准备采用这样的目录：</p><pre><code class="hljs plaintext">Personal-Life/│├── AGENT.md│├── memory/│   ├── PROFILE.md│   ├── VALUES.md│   ├── GOALS.md│   ├── CURRENT.md│   ├── PREFERENCES.md│   └── LIFE_LOG.md│├── decisions/│   ├── DECISIONS.md│   ├── OPEN_DECISIONS.md│   └── ARCHIVE.md│└── reviews/    ├── WEEKLY.md    └── MONTHLY.md</code></pre><p>这些文件暂时不需要设计得太复杂。</p><p><code>PROFILE.md</code> 记录个人基本情况和经历，<code>VALUES.md</code> 记录自己真正看重的东西，<code>GOALS.md</code> 记录未来想实现的目标，<code>CURRENT.md</code> 记录目前的生活状态。</p><p>另外，我比较重视 <code>LIFE_LOG.md</code>。</p><p>它不是用来记录每天吃了什么、去了哪里，而是记录那些对自己有意义的事情。</p><p>比如换了一份工作、开始一个新项目、做了一项重要决定，或者生活中发生了某些值得长期记住的变化。</p><p>还有一个我认为很重要的文件：<code>OPEN_DECISIONS.md</code>。</p><p>它专门记录那些还没有解决的问题。</p><p>例如：</p><pre><code class="hljs plaintext"># 当前待决策事项## 是否考虑换工作状态：待决策### 当前情况目前的工作状态和发展空间……### 我在考虑什么希望获得更好的收入和职业发展，但也担心新环境的不确定性。### 需要进一步了解- 当前市场薪资- 新岗位的工作强度- 职业发展空间### 下一步1. 整理简历2. 了解市场情况3. 比较潜在机会### 最终结果待更新</code></pre><p>这样以后再讨论换工作的问题，AI 就可以先查看之前的分析，而不是每次从零开始。</p><p>我觉得这比单纯保存聊天记录有用得多。</p><h2 id="最重要的还是让它学会做决策"><a href="#最重要的还是让它学会做决策" class="headerlink" title="最重要的还是让它学会做决策"></a>最重要的还是让它学会做决策</h2><p>整个项目中，我最在意的其实是决策能力。</p><p>我不希望 AI 每次收到问题之后，只根据当前对话给出一个看似合理的答案。</p><p>我希望它先看看有没有相关的历史信息。</p><p>比如我之前是否遇到过类似的问题？当时是怎么处理的？最后结果怎么样？我过去有没有表达过与这次选择相关的想法？</p><p>然后再结合当前情况进行分析。</p><p>对于比较重要的事情，我希望它能够给出几个明确的内容：</p><ol><li>现在真正需要解决的问题是什么？</li><li>根据我的实际情况，有哪些可行方案？</li><li>每个方案有什么好处和风险？</li><li>如果是它来建议，会推荐哪一个？</li><li>接下来我应该做什么？</li></ol><p>我尤其不喜欢一种回答方式：分析了很多，最后却没有结论。</p><p>当然，我也不要求 AI 每次都必须给出答案。如果信息不足，就应该告诉我缺少什么。</p><p>但只要信息足够，我希望它能够明确表达自己的判断，而不是把所有选择重新丢给我。</p><p>还有一点，我希望它能够记住自己的建议，以及我最终做出的决定。</p><p>以后事情有了结果，再回头看看当初的判断是否合理。</p><p>这不仅能够帮助我了解自己，也能够让 AI 逐渐掌握哪些建议真正适合我。</p><h2 id="复盘很重要，但我不想把它变成每天的任务"><a href="#复盘很重要，但我不想把它变成每天的任务" class="headerlink" title="复盘很重要，但我不想把它变成每天的任务"></a>复盘很重要，但我不想把它变成每天的任务</h2><p>我也准备保留每周和每月的复盘机制。</p><p>不过，我不希望这个系统最后变成一个每天需要填写大量内容的个人管理工具。</p><p>记录本身不应该成为负担。</p><p>平时有什么重要事情，直接告诉 AI 就可以。没有必要为了记录而记录。</p><p>每周可以简单整理一下：</p><ul><li>最近发生了哪些重要事情？</li><li>有哪些事情取得了进展？</li><li>哪些问题一直没有解决？</li><li>有没有新的风险或者机会？</li><li>下周最值得优先处理什么？</li></ul><p>每月再看看自己的目标有没有发生变化，当前的生活和工作状态是否符合预期。</p><p>复盘的作用是帮助 AI 更新对我的认识，也帮助我发现自己忽略的问题。</p><p>但相比复盘，我还是更希望它能够在具体事情发生时提供及时的判断和建议。</p><p>毕竟，生活中的很多决定并不会等到周末才出现。</p><h2 id="暂时不考虑做一个复杂的平台"><a href="#暂时不考虑做一个复杂的平台" class="headerlink" title="暂时不考虑做一个复杂的平台"></a>暂时不考虑做一个复杂的平台</h2><p>技术实现上，我准备先从简单的方式开始。</p><p>第一步使用 ChatGPT 项目，维护 <code>AGENT.md</code> 和相关 Markdown 文件。</p><p>等整个流程跑通之后，再考虑接入本地文件、自动化工具、任务管理、日历等能力。</p><p>我也不准备一开始就拆分出很多独立 Agent。</p><p>财务、职业、健康和生活看起来是不同领域，但实际生活中的很多问题都会互相影响。</p><p>比如换工作，既涉及职业发展，也涉及收入、生活时间和未来规划。</p><p>如果每个 Agent 都有自己独立的一套记忆，反而容易出现信息不一致的问题。</p><p>所以初期还是由一个主 Agent 统一了解我的情况，需要专业能力时再调用对应工具。</p><p>另外，个人数据也需要认真对待。</p><p>我不准备把密码、验证码等敏感信息交给 AI。涉及财务、健康等内容，也只提供分析所需要的信息。</p><p>Markdown 文件会保留在自己能够管理的地方，重要修改尽量保留版本记录。</p><p>我希望这个系统能够长期使用，所以数据不能完全依赖某一家 AI 平台。</p><h2 id="最后"><a href="#最后" class="headerlink" title="最后"></a>最后</h2><p>我觉得人其实很容易忘记自己曾经的想法。</p><p>今天觉得某件事很重要，过几个月可能就忘了当时为什么这么想；以前遇到过的问题，换一个环境又可能重复犯错。</p><p>如果有一个系统，能够持续整理这些经历，并在以后遇到类似问题时提醒我，应该会是一件很有价值的事情。</p><p>当然，我并不认为 AI 能够替我规划好整个人生。</p><p>它不了解所有现实，也不可能保证每次判断正确。最终的选择和责任还是需要自己承担。</p><p>但我希望它能够成为一个长期存在的外部思考工具。</p><p>我负责告诉它生活中发生了什么，也负责做出最终决定；它负责帮我整理信息、发现问题、分析选择，并尽可能把事情推进下去。</p><p>我想要的不是一个什么都知道的 AI，而是一个随着时间推移，越来越了解我，也越来越能帮助我做出正确选择的 AI。</p><p>这件事，我准备认真做下去。</p><h2 id="命令提示词"><a href="#命令提示词" class="headerlink" title="命令提示词"></a>命令提示词</h2><p>你可以选择免费的豆包&#x2F;workbuddy&#x2F;kimi&#x2F;千问&#x2F;元宝，按照你的想法即可。新建一个项目，第一次对话发送下面的内容。</p><pre><code class="hljs plaintext">请在当前项目目录中，直接为我建立一套长期运行的个人 AI Agent 系统。这不是一个普通的 Markdown 笔记库，也不是一个每日复盘工具。我希望建立的是一个真正了解我的个人 AI 顾问与执行助手。请先理解以下设计目标，再开始实施。不要只给我方案或示例，请直接创建所需目录和文件，并写入初始内容。# 一、项目目标建立一个长期运行的个人 AI Agent，让 AI 能够逐渐了解我的个人背景、经历、能力、价值观、现实处境和长期目标。当我遇到具体问题时，AI 不应该只根据当前对话回答，而应该结合已有的个人信息和历史决策，帮助我分析问题、权衡利弊、判断风险，并给出明确的建议。我最看重的是决策能力，其次才是复盘和记录。我希望 AI 不仅能够提供建议，还能够在获得授权后主动搜索资料、制定计划、拆解任务、推进项目和执行具体工作。最终决策权始终属于我。# 二、核心设计原则1. 一个统一的主 Agent不要设计多个相互独立的 Agent。所有个人信息、长期记忆、决策记录和任务进度都应该由同一个主 Agent 统一管理，避免信息割裂。2. AGENT.md 是行为规范AGENT.md 是整个项目最重要的文件。它负责定义：- AI 的角色和职责。- 如何理解和使用个人记忆。- 如何分析问题和辅助决策。- 如何给出建议。- 如何处理不确定的信息。- 如何维护长期记忆。- 如何规划和执行任务。- 哪些事情需要经过我的授权。AGENT.md 应当精简、明确，避免冗长的背景介绍和重复规则。不要把大量个人资料直接写进 AGENT.md。3. Markdown 是长期记忆使用独立的 Markdown 文件保存我的个人信息。记忆不是聊天记录的堆积，而是经过筛选、整理、归纳后形成的结构化信息。需要区分：- 已确认的事实。- 我的个人观点和偏好。- AI 根据现有信息作出的推测。- 尚未确认的信息。不要把推测当成事实，也不要将过时的信息当成当前状态。4. 决策优先于复盘当我提出问题时，优先完成：- 理解我真正想解决的问题。- 查阅相关个人背景和历史决策。- 分析现实条件、目标和限制。- 比较可行方案。- 识别风险、机会成本和长期影响。- 给出明确的推荐方案。- 告诉我下一步具体应该做什么。不要为了显得客观而总是回答“各有利弊”。信息足够时，应当明确表达你的判断；信息不足时，先询问最关键的问题。5. 不要迎合我不要默认我的想法就是正确的。如果我的计划存在明显问题、风险或与长期目标冲突，应当直接指出。建议应当以事实、逻辑和我的真实利益为基础，而不是以让我满意为目的。6. AI 可以执行，但必须有边界AI 可以主动进行：- 信息搜索和资料整理。- 方案分析与比较。- 任务拆解和计划制定。- 项目推进。- 文档编写和整理。- 代码编写和修改。- 已授权范围内的日常事务处理。涉及资金支付、购买、对外发送信息、删除重要数据、不可逆操作以及其他高影响行为时，必须先获得我的明确授权。不要将“可以帮我做”理解为“可以直接替我做所有事情”。# 三、项目目录结构请按照以下结构创建项目：Personal-Life/├── AGENT.md├── README.md├── memory/│   ├── PROFILE.md│   ├── VALUES.md│   ├── GOALS.md│   ├── CURRENT.md│   ├── PREFERENCES.md│   └── LIFE_LOG.md├── decisions/│   ├── DECISIONS.md│   ├── OPEN_DECISIONS.md│   └── ARCHIVE.md└── reviews/    ├── WEEKLY.md    └── MONTHLY.md各文件职责：memory/PROFILE.md记录我的基本背景、成长经历、教育、职业经历、技能和能力。memory/VALUES.md记录我的价值观、人生原则、底线、风险承受能力和重要取舍。memory/GOALS.md记录我的短期、中期和长期目标，以及目标之间的优先级和关系。memory/CURRENT.md记录当前现实状态，包括工作、生活、财务、正在进行的事情和近期变化。需要特别注意信息时效性。memory/PREFERENCES.md记录我的长期习惯、偏好、沟通方式和工作方式。memory/LIFE_LOG.md记录对未来决策有价值的重要经历和人生事件，不要记录无意义的日常流水账。decisions/DECISIONS.md记录重要决策的索引，包括决策时间、问题、最终选择和结果。decisions/OPEN_DECISIONS.md记录尚未解决的问题、正在考虑的选择、待补充的信息和下一步行动。decisions/ARCHIVE.md归档已经完成、不再需要持续跟踪的决策。reviews/WEEKLY.md用于每周整理重要进展、问题和待办事项。reviews/MONTHLY.md用于每月检查目标进展、长期变化和方向是否需要调整。# 四、记忆管理规则请建立一套清晰的记忆维护机制。1. 不要把每次聊天都写入记忆。2. 只保留未来可能影响判断、建议和行动的信息。3. 临时信息不要轻易升级为长期记忆。4. 重要事实发生变化时，更新对应文件，不要简单重复追加。5. 重要信息存在不确定性时，标记为待确认。6. 对可能影响未来重大决策的信息，优先向我确认。7. 更新记忆时，注意检查其他文件是否存在重复或冲突。8. 不要擅自删除有历史价值的决策记录。9. 对过时的 CURRENT.md 信息进行更新，并保留必要的历史变化。10. 每次重要记忆变更，都应让我能够知道修改了什么。# 五、决策工作流程当我提出一个问题或遇到一件需要处理的事情时，按照以下顺序工作：第一步：理解问题判断我真正需要解决的是什么，而不只是机械回答表面问题。第二步：读取记忆查找与当前问题相关的个人背景、目标、偏好、历史经历和过去的决策。第三步：分析问题结合现实条件，识别约束、风险、机会成本和潜在影响。第四步：形成判断比较可行方案，给出最合理的选择，并解释推荐理由。第五步：制定行动将建议转化为具体、可执行的下一步行动。第六步：执行与跟踪在授权范围内主动推进任务。需要我作出选择或授权时，明确指出。第七步：更新记忆只有当事情产生值得长期保留的新信息、决策或结果时，才更新相应文件。注意：并不是每次对话都必须执行完整流程。简单问题直接回答，不要为了调用记忆而增加无意义的步骤。# 六、初始个人信息请先检查当前项目中是否已经存在可用的个人资料、历史记录或其他文档。如果没有，不要编造我的个人信息。可以使用当前已经明确提供的信息作为初始资料，但必须区分已确认事实与尚待确认的信息。对于缺失的信息，不要一次性生成大量问题，也不要假设我愿意填写一份很长的个人档案。后续通过日常交流逐步了解我。# 七、实施要求1. 直接创建上述目录和 Markdown 文件。2. 每个文件都应有明确用途、必要的内容结构和简短说明。3. AGENT.md 必须能够作为主 Agent 的实际行为规范使用，而不是一篇介绍文档。4. README.md 介绍项目结构、使用方式和维护原则。5. 不要引入数据库、复杂框架、Web 服务或不必要的依赖。6. 不要创建多个 Agent。7. 不要生成大量重复的模板和空洞内容。8. 不要为了自动化而自动化，先确保记忆体系和决策流程可靠。9. 文件内容使用中文，文件名使用英文。10. 保持结构简洁，降低后续 AI 每次读取项目时的 Token 消耗。11. 不要擅自覆盖当前目录中已有的文件。如果发现已有内容，先检查并保留有价值的信息。12. 如果当前 AI 工具支持项目级指令文件，请同时考虑如何让 AGENT.md 在后续会话中持续生效；不要假设所有工具都会自动读取 AGENT.md。必要时说明需要配置的入口文件或引用方式。# 八、完成后的要求完成文件创建后，请不要直接结束。请检查整个项目，确认：- 目录结构是否完整。- AGENT.md 是否具备实际约束能力。- 各记忆文件的职责是否清晰。- 是否存在重复、矛盾或过度复杂的设计。- 后续 AI 是否能够根据这些文件持续工作。最后向我汇报：1. 创建了哪些文件。2. 每个文件具体负责什么。3. 主 Agent 如何读取和维护这些记忆。4. 当前 AI 工具需要如何配置，才能持续遵循 AGENT.md。5. 目前还缺少哪些个人信息。6. 建议我接下来做的第一件事。请直接开始实施，不要只输出设计方案。</code></pre>]]></content:encoded>
      
      
      <category domain="https://www.banzhuanriji.com/categories/posts/">默认</category>
      
      
      <category domain="https://www.banzhuanriji.com/tags/ai/">ai</category>
      
      <category domain="https://www.banzhuanriji.com/tags/agent/">agent</category>
      
      <category domain="https://www.banzhuanriji.com/tags/AGENT-md/">AGENT.md</category>
      
      <category domain="https://www.banzhuanriji.com/tags/Markdown/">Markdown</category>
      
      <category domain="https://www.banzhuanriji.com/tags/%E5%86%B3%E7%AD%96/">决策</category>
      
      <category domain="https://www.banzhuanriji.com/tags/%E9%9A%8F%E7%AC%94/">随笔</category>
      
      
      <comments>https://www.banzhuanriji.com/posts/a-long-term-ai-assistant/#disqus_thread</comments>
      
    </item>
    
    <item>
      <title>如何在控制台解析并按照格式输出 Markdown 内容</title>
      <link>https://www.banzhuanriji.com/work/render-markdown-in-terminal/</link>
      <guid>https://www.banzhuanriji.com/work/render-markdown-in-terminal/</guid>
      <pubDate>Wed, 12 Aug 2026 07:49:43 GMT</pubDate>
      
      <description>用 Python + ANSI 转义码，在终端里把 Markdown 渲染得有模有样——标题带颜色、代码块带背景、列表带圆点，不依赖任何第三方库，复制就能跑。</description>
      
      
      
      <content:encoded><![CDATA[<h2 id="前言"><a href="#前言" class="headerlink" title="前言"></a>前言</h2><p>最近在做一个命令行工具的时候，遇到一个挺有意思的需求：工具输出的帮助文档和日志信息都是 Markdown 格式的，但是直接 <code>print</code> 到终端上，一大堆 <code>#</code>、<code>**</code>、<code>`</code> 符号堆在一起，看得人眼花缭乱。</p><p>终端本身是支持颜色和样式的呀，那能不能在控制台里把 Markdown 渲染得好看一点？今天记录一下整个实现过程，顺便把代码分享给大家，拿去就能用~</p><hr><h2 id="先聊聊思路"><a href="#先聊聊思路" class="headerlink" title="先聊聊思路"></a>先聊聊思路</h2><p>Markdown 的语法其实不多，我们日常用到的无非就是这几种：</p><table><thead><tr><th>语法</th><th>含义</th></tr></thead><tbody><tr><td><code># ~ ######</code></td><td>标题（H1~H6）</td></tr><tr><td><code>**文字**</code></td><td>加粗</td></tr><tr><td><code>*文字*</code> 或 <code>_文字_</code></td><td>斜体</td></tr><tr><td><code>`代码`</code></td><td>行内代码</td></tr><tr><td><code>```</code></td><td>代码块</td></tr><tr><td><code>- item</code> 或 <code>* item</code></td><td>无序列表</td></tr><tr><td><code>1. item</code></td><td>有序列表</td></tr><tr><td><code>&gt; 引用</code></td><td>引用块</td></tr><tr><td><code>[文字](链接)</code></td><td>超链接</td></tr><tr><td><code>---</code></td><td>分割线</td></tr></tbody></table><p>在终端里，我们要做的事情就是：<strong>把这些 Markdown 符号替换成对应的 ANSI 转义序列</strong>，让终端显示出颜色和样式来。</p><p>比如 <code>**加粗**</code> 变成终端里的加粗文字，<code># 标题</code> 变成带颜色的大号标题。</p><hr><h2 id="ANSI-转义序列，简单复习一下"><a href="#ANSI-转义序列，简单复习一下" class="headerlink" title="ANSI 转义序列，简单复习一下"></a>ANSI 转义序列，简单复习一下</h2><p>终端里的颜色和样式，都是靠 ANSI 转义码来控制的。格式很简单：</p><pre><code class="hljs plaintext">\033[&lt;代码&gt;m</code></pre><p>常用的几个：</p><pre><code class="hljs python">RESET   = <span class="hljs-string">&quot;\033[0m&quot;</span>      <span class="hljs-comment"># 重置所有样式</span>BOLD    = <span class="hljs-string">&quot;\033[1m&quot;</span>       <span class="hljs-comment"># 加粗</span>DIM     = <span class="hljs-string">&quot;\033[2m&quot;</span>       <span class="hljs-comment"># 暗淡</span>ITALIC  = <span class="hljs-string">&quot;\033[3m&quot;</span>       <span class="hljs-comment"># 斜体（部分终端不支持）</span>UNDERLINE = <span class="hljs-string">&quot;\033[4m&quot;</span>     <span class="hljs-comment"># 下划线</span><span class="hljs-comment"># 前景色</span>RED     = <span class="hljs-string">&quot;\033[31m&quot;</span>GREEN   = <span class="hljs-string">&quot;\033[32m&quot;</span>YELLOW  = <span class="hljs-string">&quot;\033[33m&quot;</span>BLUE    = <span class="hljs-string">&quot;\033[34m&quot;</span>MAGENTA = <span class="hljs-string">&quot;\033[35m&quot;</span>CYAN    = <span class="hljs-string">&quot;\033[36m&quot;</span>WHITE   = <span class="hljs-string">&quot;\033[37m&quot;</span><span class="hljs-comment"># 背景色</span>BG_DARK = <span class="hljs-string">&quot;\033[48;5;236m&quot;</span>  <span class="hljs-comment"># 深灰背景，用于代码块</span></code></pre><p>把文字包在这些码中间，终端就会用对应样式渲染。比如 <code>\033[1m\033[31m文字\033[0m</code> 就是红色加粗的文字。</p><p>原理就这么简单，接下来我们开始写解析器。</p><hr><h2 id="开始写代码"><a href="#开始写代码" class="headerlink" title="开始写代码"></a>开始写代码</h2><p>我用 Python 来实现，主要是正则表达式 + 逐行处理，思路很清晰。</p><p>先把整体结构搭起来：</p><pre><code class="hljs python"><span class="hljs-keyword">import</span> re<span class="hljs-keyword">import</span> sys<span class="hljs-keyword">class</span> <span class="hljs-title class_">MarkdownConsoleRenderer</span>:    <span class="hljs-string">&quot;&quot;&quot;在终端中渲染 Markdown 内容&quot;&quot;&quot;</span>    <span class="hljs-comment"># ── ANSI 颜色定义 ──</span>    RESET     = <span class="hljs-string">&quot;\033[0m&quot;</span>    BOLD      = <span class="hljs-string">&quot;\033[1m&quot;</span>    DIM       = <span class="hljs-string">&quot;\033[2m&quot;</span>    ITALIC    = <span class="hljs-string">&quot;\033[3m&quot;</span>    UNDERLINE = <span class="hljs-string">&quot;\033[4m&quot;</span>    RED       = <span class="hljs-string">&quot;\033[31m&quot;</span>    GREEN     = <span class="hljs-string">&quot;\033[32m&quot;</span>    YELLOW    = <span class="hljs-string">&quot;\033[33m&quot;</span>    BLUE      = <span class="hljs-string">&quot;\033[34m&quot;</span>    MAGENTA   = <span class="hljs-string">&quot;\033[35m&quot;</span>    CYAN      = <span class="hljs-string">&quot;\033[36m&quot;</span>    WHITE     = <span class="hljs-string">&quot;\033[37m&quot;</span>    GRAY      = <span class="hljs-string">&quot;\033[90m&quot;</span>    BG_CODE   = <span class="hljs-string">&quot;\033[48;5;236m&quot;</span>    <span class="hljs-comment"># 标题的颜色映射，H1~H6 各来一个</span>    HEADING_COLORS = [        CYAN + BOLD,      <span class="hljs-comment"># H1</span>        GREEN + BOLD,     <span class="hljs-comment"># H2</span>        YELLOW + BOLD,    <span class="hljs-comment"># H3</span>        MAGENTA + BOLD,   <span class="hljs-comment"># H4</span>        BLUE + BOLD,      <span class="hljs-comment"># H5</span>        RED + BOLD,       <span class="hljs-comment"># H6</span>    ]    <span class="hljs-keyword">def</span> <span class="hljs-title function_">__init__</span>(<span class="hljs-params">self</span>):        <span class="hljs-variable language_">self</span>.in_code_block = <span class="hljs-literal">False</span>        <span class="hljs-variable language_">self</span>.code_block_lang = <span class="hljs-string">&quot;&quot;</span>        <span class="hljs-variable language_">self</span>.code_block_lines = []        <span class="hljs-variable language_">self</span>.list_counter = <span class="hljs-number">0</span>    <span class="hljs-keyword">def</span> <span class="hljs-title function_">render</span>(<span class="hljs-params">self, text: <span class="hljs-built_in">str</span></span>) -&gt; <span class="hljs-built_in">str</span>:        <span class="hljs-string">&quot;&quot;&quot;渲染整个 Markdown 文本&quot;&quot;&quot;</span>        lines = text.split(<span class="hljs-string">&quot;\n&quot;</span>)        output_lines = []        <span class="hljs-keyword">for</span> line <span class="hljs-keyword">in</span> lines:            result = <span class="hljs-variable language_">self</span>._process_line(line)            output_lines.append(result)        <span class="hljs-keyword">return</span> <span class="hljs-string">&quot;\n&quot;</span>.join(output_lines)</code></pre><p>好，骨架有了。接下来一个一个实现各个语法的解析。</p><hr><h3 id="1-标题的处理"><a href="#1-标题的处理" class="headerlink" title="1. 标题的处理"></a>1. 标题的处理</h3><p>标题是最容易识别的，行首一个或多个 <code>#</code> 开头：</p><pre><code class="hljs python"><span class="hljs-keyword">def</span> <span class="hljs-title function_">_process_heading</span>(<span class="hljs-params">self, line: <span class="hljs-built_in">str</span></span>) -&gt; <span class="hljs-built_in">str</span>:    <span class="hljs-keyword">match</span> = re.<span class="hljs-keyword">match</span>(<span class="hljs-string">r&#x27;^(#&#123;1,6&#125;)\s+(.*)&#x27;</span>, line)    <span class="hljs-keyword">if</span> <span class="hljs-keyword">match</span>:        level = <span class="hljs-built_in">len</span>(<span class="hljs-keyword">match</span>.group(<span class="hljs-number">1</span>))  <span class="hljs-comment"># 几个 # 就是几级标题</span>        content = <span class="hljs-keyword">match</span>.group(<span class="hljs-number">2</span>).strip()        color = <span class="hljs-variable language_">self</span>.HEADING_COLORS[level - <span class="hljs-number">1</span>]        <span class="hljs-comment"># H1 和 H2 加个下划线装饰一下</span>        <span class="hljs-keyword">if</span> level == <span class="hljs-number">1</span>:            underline = <span class="hljs-variable language_">self</span>.DIM + <span class="hljs-string">&quot;─&quot;</span> * <span class="hljs-built_in">len</span>(content) + <span class="hljs-variable language_">self</span>.RESET            <span class="hljs-keyword">return</span> <span class="hljs-string">f&quot;\n<span class="hljs-subst">&#123;color&#125;</span>  <span class="hljs-subst">&#123;content&#125;</span>  <span class="hljs-subst">&#123;self.RESET&#125;</span>\n<span class="hljs-subst">&#123;underline&#125;</span>&quot;</span>        <span class="hljs-keyword">elif</span> level == <span class="hljs-number">2</span>:            underline = <span class="hljs-variable language_">self</span>.DIM + <span class="hljs-string">&quot;╌&quot;</span> * <span class="hljs-built_in">len</span>(content) + <span class="hljs-variable language_">self</span>.RESET            <span class="hljs-keyword">return</span> <span class="hljs-string">f&quot;\n<span class="hljs-subst">&#123;color&#125;</span>  <span class="hljs-subst">&#123;content&#125;</span>  <span class="hljs-subst">&#123;self.RESET&#125;</span>\n<span class="hljs-subst">&#123;underline&#125;</span>&quot;</span>        <span class="hljs-keyword">else</span>:            <span class="hljs-keyword">return</span> <span class="hljs-string">f&quot;\n<span class="hljs-subst">&#123;color&#125;</span>  <span class="hljs-subst">&#123;<span class="hljs-string">&#x27;#&#x27;</span> * level&#125;</span> <span class="hljs-subst">&#123;content&#125;</span><span class="hljs-subst">&#123;self.RESET&#125;</span>&quot;</span>    <span class="hljs-keyword">return</span> <span class="hljs-literal">None</span></code></pre><p>效果大概是这样的：</p><pre><code class="hljs plaintext">  标题一          ← 青色加粗─────────────     ← 灰色下划线</code></pre><hr><h3 id="2-行内样式：加粗、斜体、行内代码"><a href="#2-行内样式：加粗、斜体、行内代码" class="headerlink" title="2. 行内样式：加粗、斜体、行内代码"></a>2. 行内样式：加粗、斜体、行内代码</h3><p>这一步用正则替换就行，把 Markdown 语法替换成 ANSI 码：</p><pre><code class="hljs python"><span class="hljs-keyword">def</span> <span class="hljs-title function_">_apply_inline_styles</span>(<span class="hljs-params">self, text: <span class="hljs-built_in">str</span></span>) -&gt; <span class="hljs-built_in">str</span>:    <span class="hljs-string">&quot;&quot;&quot;处理行内样式&quot;&quot;&quot;</span>    <span class="hljs-comment"># 行内代码  `code`</span>    text = re.sub(        <span class="hljs-string">r&#x27;`([^`]+)`&#x27;</span>,        <span class="hljs-string">f&#x27;<span class="hljs-subst">&#123;self.BG_CODE&#125;</span><span class="hljs-subst">&#123;self.YELLOW&#125;</span> \\1 <span class="hljs-subst">&#123;self.RESET&#125;</span>&#x27;</span>,        text    )    <span class="hljs-comment"># 加粗  **text**  或  __text__</span>    text = re.sub(        <span class="hljs-string">r&#x27;\*\*(.+?)\*\*|__(.+?)__&#x27;</span>,        <span class="hljs-keyword">lambda</span> m: <span class="hljs-string">f&#x27;<span class="hljs-subst">&#123;self.BOLD&#125;</span><span class="hljs-subst">&#123;self.WHITE&#125;</span><span class="hljs-subst">&#123;m.group(<span class="hljs-number">1</span>) <span class="hljs-keyword">or</span> m.group(<span class="hljs-number">2</span>)&#125;</span><span class="hljs-subst">&#123;self.RESET&#125;</span>&#x27;</span>,        text    )    <span class="hljs-comment"># 斜体  *text*  或  _text_</span>    text = re.sub(        <span class="hljs-string">r&#x27;(?&lt;!\*)\*(?!\*)(.+?)(?&lt;!\*)\*(?!\*)|(?&lt;!_)_(?!_)(.+?)(?&lt;!_)_(?!_)&#x27;</span>,        <span class="hljs-keyword">lambda</span> m: <span class="hljs-string">f&#x27;<span class="hljs-subst">&#123;self.ITALIC&#125;</span><span class="hljs-subst">&#123;self.CYAN&#125;</span><span class="hljs-subst">&#123;m.group(<span class="hljs-number">1</span>) <span class="hljs-keyword">or</span> m.group(<span class="hljs-number">2</span>)&#125;</span><span class="hljs-subst">&#123;self.RESET&#125;</span>&#x27;</span>,        text    )    <span class="hljs-comment"># 超链接  [text](url)</span>    text = re.sub(        <span class="hljs-string">r&#x27;\[([^\]]+)\]\(([^)]+)\)&#x27;</span>,        <span class="hljs-keyword">lambda</span> m: <span class="hljs-string">f&#x27;<span class="hljs-subst">&#123;self.UNDERLINE&#125;</span><span class="hljs-subst">&#123;self.BLUE&#125;</span><span class="hljs-subst">&#123;m.group(<span class="hljs-number">1</span>)&#125;</span><span class="hljs-subst">&#123;self.RESET&#125;</span> <span class="hljs-subst">&#123;self.GRAY&#125;</span>(<span class="hljs-subst">&#123;m.group(<span class="hljs-number">2</span>)&#125;</span>)<span class="hljs-subst">&#123;self.RESET&#125;</span>&#x27;</span>,        text    )    <span class="hljs-keyword">return</span> text</code></pre><p>注意一下正则的顺序——<strong>行内代码要放在最前面处理</strong>，否则里面的 <code>*</code> 号可能会被误当成斜体标记。</p><hr><h3 id="3-代码块的处理"><a href="#3-代码块的处理" class="headerlink" title="3. 代码块的处理"></a>3. 代码块的处理</h3><p>代码块是跨行的，用 <code> </code> &#96;&#96;&#96; 开始和结束。我们需要一个状态来追踪当前是否在代码块中：</p><pre><code class="hljs python"><span class="hljs-keyword">def</span> <span class="hljs-title function_">_process_code_block</span>(<span class="hljs-params">self, line: <span class="hljs-built_in">str</span></span>) -&gt; <span class="hljs-built_in">str</span> | <span class="hljs-literal">None</span>:    <span class="hljs-string">&quot;&quot;&quot;处理代码块&quot;&quot;&quot;</span>    <span class="hljs-comment"># 代码块开始</span>    <span class="hljs-keyword">if</span> re.<span class="hljs-keyword">match</span>(<span class="hljs-string">r&#x27;^```&#x27;</span>, line):        <span class="hljs-keyword">if</span> <span class="hljs-keyword">not</span> <span class="hljs-variable language_">self</span>.in_code_block:            <span class="hljs-variable language_">self</span>.in_code_block = <span class="hljs-literal">True</span>            <span class="hljs-variable language_">self</span>.code_block_lang = line.strip(<span class="hljs-string">&#x27;` \n&#x27;</span>)            <span class="hljs-variable language_">self</span>.code_block_lines = []            <span class="hljs-keyword">return</span> <span class="hljs-literal">None</span>  <span class="hljs-comment"># 开始标记不输出</span>        <span class="hljs-keyword">else</span>:            <span class="hljs-comment"># 代码块结束，输出收集的内容</span>            <span class="hljs-variable language_">self</span>.in_code_block = <span class="hljs-literal">False</span>            <span class="hljs-keyword">return</span> <span class="hljs-variable language_">self</span>._render_code_block()    <span class="hljs-comment"># 在代码块内部，收集行</span>    <span class="hljs-keyword">if</span> <span class="hljs-variable language_">self</span>.in_code_block:        <span class="hljs-variable language_">self</span>.code_block_lines.append(line)        <span class="hljs-keyword">return</span> <span class="hljs-literal">None</span>    <span class="hljs-keyword">return</span> <span class="hljs-string">&quot;NOT_IN_CODE_BLOCK&quot;</span>  <span class="hljs-comment"># 不是代码块的行</span></code></pre><p>代码块结束的时候，我们给它加个深色背景、行号，好看一点：</p><pre><code class="hljs python"><span class="hljs-keyword">def</span> <span class="hljs-title function_">_render_code_block</span>(<span class="hljs-params">self</span>) -&gt; <span class="hljs-built_in">str</span>:    <span class="hljs-string">&quot;&quot;&quot;渲染收集好的代码块&quot;&quot;&quot;</span>    header = <span class="hljs-string">&quot;&quot;</span>    <span class="hljs-keyword">if</span> <span class="hljs-variable language_">self</span>.code_block_lang:        header = <span class="hljs-string">f&quot;  <span class="hljs-subst">&#123;self.GRAY&#125;</span><span class="hljs-subst">&#123;self.code_block_lang&#125;</span><span class="hljs-subst">&#123;self.RESET&#125;</span>&quot;</span>    lines = []    lines.append(<span class="hljs-string">f&quot;<span class="hljs-subst">&#123;header&#125;</span>&quot;</span>)    lines.append(<span class="hljs-string">f&quot;<span class="hljs-subst">&#123;self.BG_CODE&#125;</span><span class="hljs-subst">&#123;self.GRAY&#125;</span>  ┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄  <span class="hljs-subst">&#123;self.RESET&#125;</span>&quot;</span>)    <span class="hljs-keyword">for</span> i, code_line <span class="hljs-keyword">in</span> <span class="hljs-built_in">enumerate</span>(<span class="hljs-variable language_">self</span>.code_block_lines, <span class="hljs-number">1</span>):        line_no = <span class="hljs-string">f&quot;<span class="hljs-subst">&#123;self.GRAY&#125;</span><span class="hljs-subst">&#123;i:&gt;<span class="hljs-number">3</span>&#125;</span> │<span class="hljs-subst">&#123;self.RESET&#125;</span>&quot;</span>        lines.append(<span class="hljs-string">f&quot;<span class="hljs-subst">&#123;self.BG_CODE&#125;</span><span class="hljs-subst">&#123;line_no&#125;</span> <span class="hljs-subst">&#123;code_line&#125;</span>  <span class="hljs-subst">&#123;self.RESET&#125;</span>&quot;</span>)    lines.append(<span class="hljs-string">f&quot;<span class="hljs-subst">&#123;self.BG_CODE&#125;</span><span class="hljs-subst">&#123;self.GRAY&#125;</span>  ┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄  <span class="hljs-subst">&#123;self.RESET&#125;</span>&quot;</span>)    <span class="hljs-keyword">return</span> <span class="hljs-string">&quot;\n&quot;</span>.join(lines)</code></pre><p>效果大概长这样：</p><pre><code class="hljs plaintext">python┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄1 │ print(&quot;hello&quot;)2 │ x = 42┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄</code></pre><p>是不是有点内味了~</p><hr><h3 id="4-列表的处理"><a href="#4-列表的处理" class="headerlink" title="4. 列表的处理"></a>4. 列表的处理</h3><p>无序列表和有序列表：</p><pre><code class="hljs python"><span class="hljs-keyword">def</span> <span class="hljs-title function_">_process_list</span>(<span class="hljs-params">self, line: <span class="hljs-built_in">str</span></span>) -&gt; <span class="hljs-built_in">str</span> | <span class="hljs-literal">None</span>:    <span class="hljs-comment"># 无序列表  - item  或  * item</span>    <span class="hljs-keyword">match</span> = re.<span class="hljs-keyword">match</span>(<span class="hljs-string">r&#x27;^(\s*)[-*]\s+(.*)&#x27;</span>, line)    <span class="hljs-keyword">if</span> <span class="hljs-keyword">match</span>:        indent = <span class="hljs-built_in">len</span>(<span class="hljs-keyword">match</span>.group(<span class="hljs-number">1</span>))        content = <span class="hljs-variable language_">self</span>._apply_inline_styles(<span class="hljs-keyword">match</span>.group(<span class="hljs-number">2</span>))        bullet = <span class="hljs-string">f&quot;<span class="hljs-subst">&#123;self.GREEN&#125;</span>●<span class="hljs-subst">&#123;self.RESET&#125;</span>&quot;</span>        padding = <span class="hljs-string">&quot;  &quot;</span> * (indent // <span class="hljs-number">2</span>)        <span class="hljs-keyword">return</span> <span class="hljs-string">f&quot;  <span class="hljs-subst">&#123;padding&#125;</span><span class="hljs-subst">&#123;bullet&#125;</span> <span class="hljs-subst">&#123;content&#125;</span>&quot;</span>    <span class="hljs-comment"># 有序列表  1. item</span>    <span class="hljs-keyword">match</span> = re.<span class="hljs-keyword">match</span>(<span class="hljs-string">r&#x27;^(\s*)\d+\.\s+(.*)&#x27;</span>, line)    <span class="hljs-keyword">if</span> <span class="hljs-keyword">match</span>:        <span class="hljs-variable language_">self</span>.list_counter += <span class="hljs-number">1</span>        content = <span class="hljs-variable language_">self</span>._apply_inline_styles(<span class="hljs-keyword">match</span>.group(<span class="hljs-number">2</span>))        num = <span class="hljs-string">f&quot;<span class="hljs-subst">&#123;self.CYAN&#125;</span><span class="hljs-subst">&#123;self.list_counter&#125;</span>.<span class="hljs-subst">&#123;self.RESET&#125;</span>&quot;</span>        <span class="hljs-keyword">return</span> <span class="hljs-string">f&quot;  <span class="hljs-subst">&#123;num&#125;</span> <span class="hljs-subst">&#123;content&#125;</span>&quot;</span>    <span class="hljs-comment"># 不是列表行，重置计数器</span>    <span class="hljs-variable language_">self</span>.list_counter = <span class="hljs-number">0</span>    <span class="hljs-keyword">return</span> <span class="hljs-literal">None</span></code></pre><hr><h3 id="5-引用块"><a href="#5-引用块" class="headerlink" title="5. 引用块"></a>5. 引用块</h3><p>引用块以 <code>&gt;</code> 开头，我们给它加个竖线和灰色：</p><pre><code class="hljs python"><span class="hljs-keyword">def</span> <span class="hljs-title function_">_process_blockquote</span>(<span class="hljs-params">self, line: <span class="hljs-built_in">str</span></span>) -&gt; <span class="hljs-built_in">str</span> | <span class="hljs-literal">None</span>:    <span class="hljs-keyword">match</span> = re.<span class="hljs-keyword">match</span>(<span class="hljs-string">r&#x27;^&gt;\s*(.*)&#x27;</span>, line)    <span class="hljs-keyword">if</span> <span class="hljs-keyword">match</span>:        content = <span class="hljs-variable language_">self</span>._apply_inline_styles(<span class="hljs-keyword">match</span>.group(<span class="hljs-number">1</span>))        <span class="hljs-keyword">return</span> <span class="hljs-string">f&quot;  <span class="hljs-subst">&#123;self.YELLOW&#125;</span>│<span class="hljs-subst">&#123;self.RESET&#125;</span> <span class="hljs-subst">&#123;self.DIM&#125;</span><span class="hljs-subst">&#123;content&#125;</span><span class="hljs-subst">&#123;self.RESET&#125;</span>&quot;</span>    <span class="hljs-keyword">return</span> <span class="hljs-literal">None</span></code></pre><hr><h3 id="6-分割线"><a href="#6-分割线" class="headerlink" title="6. 分割线"></a>6. 分割线</h3><p>一行全是 <code>-</code> 或 <code>*</code> 或 <code>_</code>（至少3个）：</p><pre><code class="hljs python"><span class="hljs-keyword">def</span> <span class="hljs-title function_">_process_horizontal_rule</span>(<span class="hljs-params">self, line: <span class="hljs-built_in">str</span></span>) -&gt; <span class="hljs-built_in">str</span> | <span class="hljs-literal">None</span>:    <span class="hljs-keyword">if</span> re.<span class="hljs-keyword">match</span>(<span class="hljs-string">r&#x27;^[-*_]&#123;3,&#125;\s*$&#x27;</span>, line):        width = <span class="hljs-number">60</span>        <span class="hljs-keyword">return</span> <span class="hljs-string">f&quot;\n<span class="hljs-subst">&#123;self.GRAY&#125;</span><span class="hljs-subst">&#123;<span class="hljs-string">&#x27;─&#x27;</span> * width&#125;</span><span class="hljs-subst">&#123;self.RESET&#125;</span>\n&quot;</span>    <span class="hljs-keyword">return</span> <span class="hljs-literal">None</span></code></pre><hr><h3 id="7-把它们串起来"><a href="#7-把它们串起来" class="headerlink" title="7. 把它们串起来"></a>7. 把它们串起来</h3><p>最后，在 <code>_process_line</code> 里把所有处理逻辑串起来：</p><pre><code class="hljs python"><span class="hljs-keyword">def</span> <span class="hljs-title function_">_process_line</span>(<span class="hljs-params">self, line: <span class="hljs-built_in">str</span></span>) -&gt; <span class="hljs-built_in">str</span>:    <span class="hljs-comment"># 代码块优先处理</span>    code_result = <span class="hljs-variable language_">self</span>._process_code_block(line)    <span class="hljs-keyword">if</span> code_result <span class="hljs-keyword">is</span> <span class="hljs-literal">None</span>:        <span class="hljs-keyword">return</span> <span class="hljs-string">&quot;&quot;</span>  <span class="hljs-comment"># 代码块内部的行已被收集</span>    <span class="hljs-keyword">if</span> code_result != <span class="hljs-string">&quot;NOT_IN_CODE_BLOCK&quot;</span>:        <span class="hljs-keyword">return</span> code_result  <span class="hljs-comment"># 代码块的边界行</span>    <span class="hljs-comment"># 空行</span>    <span class="hljs-keyword">if</span> <span class="hljs-keyword">not</span> line.strip():        <span class="hljs-keyword">return</span> <span class="hljs-string">&quot;&quot;</span>    <span class="hljs-comment"># 标题</span>    heading = <span class="hljs-variable language_">self</span>._process_heading(line)    <span class="hljs-keyword">if</span> heading:        <span class="hljs-keyword">return</span> heading    <span class="hljs-comment"># 分割线</span>    hr = <span class="hljs-variable language_">self</span>._process_horizontal_rule(line)    <span class="hljs-keyword">if</span> hr:        <span class="hljs-keyword">return</span> hr    <span class="hljs-comment"># 引用</span>    quote = <span class="hljs-variable language_">self</span>._process_blockquote(line)    <span class="hljs-keyword">if</span> quote:        <span class="hljs-keyword">return</span> quote    <span class="hljs-comment"># 列表</span>    lst = <span class="hljs-variable language_">self</span>._process_list(line)    <span class="hljs-keyword">if</span> lst:        <span class="hljs-keyword">return</span> lst    <span class="hljs-comment"># 普通行，应用行内样式</span>    <span class="hljs-keyword">return</span> <span class="hljs-string">f&quot;  <span class="hljs-subst">&#123;self._apply_inline_styles(line)&#125;</span>&quot;</span></code></pre><hr><h2 id="跑一下看看效果"><a href="#跑一下看看效果" class="headerlink" title="跑一下看看效果"></a>跑一下看看效果</h2><p>我发现缩略内容会包含&#96;`&#96;符号，所以解析会失败，你可以去 <a href="https://markdownlivepreview.com/">https://markdownlivepreview.com/</a> 复制下来它的占位内容测试。</p><hr><h2 id="从文件读取并渲染"><a href="#从文件读取并渲染" class="headerlink" title="从文件读取并渲染"></a>从文件读取并渲染</h2><p>实际使用的时候，一般是从 <code>.md</code> 文件读取的，加个简单的命令行入口就行：</p><pre><code class="hljs python"><span class="hljs-keyword">def</span> <span class="hljs-title function_">main</span>():    <span class="hljs-keyword">if</span> <span class="hljs-built_in">len</span>(sys.argv) &gt; <span class="hljs-number">1</span>:        filepath = sys.argv[<span class="hljs-number">1</span>]        <span class="hljs-keyword">try</span>:            <span class="hljs-keyword">with</span> <span class="hljs-built_in">open</span>(filepath, <span class="hljs-string">&quot;r&quot;</span>, encoding=<span class="hljs-string">&quot;utf-8&quot;</span>) <span class="hljs-keyword">as</span> f:                content = f.read()        <span class="hljs-keyword">except</span> FileNotFoundError:            <span class="hljs-built_in">print</span>(<span class="hljs-string">f&quot;\033[31m文件不存在: <span class="hljs-subst">&#123;filepath&#125;</span>\033[0m&quot;</span>)            sys.exit(<span class="hljs-number">1</span>)    <span class="hljs-keyword">else</span>:        <span class="hljs-comment"># 从 stdin 读取，支持管道</span>        content = sys.stdin.read()    renderer = MarkdownConsoleRenderer()    <span class="hljs-built_in">print</span>(renderer.render(content))<span class="hljs-keyword">if</span> __name__ == <span class="hljs-string">&quot;__main__&quot;</span>:    main()</code></pre><p>用法就很灵活了：</p><pre><code class="hljs bash"><span class="hljs-comment"># 直接渲染文件</span>python md_render.py README.md<span class="hljs-comment"># 配合管道使用</span>curl -s https://raw.githubusercontent.com/xxx/xxx/main/README.md | python md_render.py</code></pre><hr><h2 id="写在最后"><a href="#写在最后" class="headerlink" title="写在最后"></a>写在最后</h2><p>整个实现下来，代码量不多，核心就是一个正则替换 + ANSI 转义码的拼接。虽然比起真正的 Markdown 解析器（比如 <code>markdown-it</code>、<code>CommonMark</code>）来说，这个实现非常简陋，嵌套样式、复杂列表、表格这些都没处理，但是对于日常的命令行工具帮助文档、README 预览这些场景来说，够用了。</p><p>核心思想就一句话：<strong>把 Markdown 符号换成 ANSI 颜色码</strong>，就这么简单。</p><p>源码我就不单独开仓库了，直接复制上面的代码就能跑。如果大家有需要的话，后面可以加上表格支持、图片占位符之类的功能。</p>]]></content:encoded>
      
      
      <category domain="https://www.banzhuanriji.com/categories/work/">工作</category>
      
      
      <category domain="https://www.banzhuanriji.com/tags/Markdown/">Markdown</category>
      
      <category domain="https://www.banzhuanriji.com/tags/Python/">Python</category>
      
      <category domain="https://www.banzhuanriji.com/tags/Terminal/">Terminal</category>
      
      <category domain="https://www.banzhuanriji.com/tags/ANSI/">ANSI</category>
      
      
      <comments>https://www.banzhuanriji.com/work/render-markdown-in-terminal/#disqus_thread</comments>
      
    </item>
    
    <item>
      <title>在AI回复之前，在世界发生之前，在你意识到之前</title>
      <link>https://www.banzhuanriji.com/blog/before-things-happning/</link>
      <guid>https://www.banzhuanriji.com/blog/before-things-happning/</guid>
      <pubDate>Sat, 09 May 2026 03:45:08 GMT</pubDate>
      
        
        
          
          
      <description></description>
          
        
      
      
      
      <content:encoded><![CDATA[<p><img src="/img/before-things-happning/sqlite.avif" alt="SQLite"></p><blockquote><p>或许真正的好东西，往往不是“组织”出来的</p></blockquote><p>前几天刚和朋友讨论完，SQLite和Linux哪个是安装最多的软件。最近发现X上都在讨论SQLite。</p><p>在这个结论之前，先说一个你可能没想过的问题：SQLite——那个藏在几十亿台手机、电脑、电视、汽车甚至浏览器里的数据库——核心开发团队有几个人？</p><p>三个。从它诞生到现在，就这三个人。</p><p>这听起来有点离谱。一个被全世界应用最广泛的数据库引擎，没有几百人的维护团队，没有复杂的组织架构，甚至连个像样的办公室可能都不需要。三个工程师，宣布会撑起目前世界上安装最广泛的极简数据库的开发工作，并保持更新。</p><p>但你仔细想想，类似的事其实反复发生过。</p><p>Linux，一个芬兰大学生写着玩的操作系统，最初只有他一个人。如今这个“写着玩”的东西跑在全世界90%以上的服务器上，跑在每一个Android手机里，跑在NASA的火星车里。Python，Guido van Rossum 在圣诞假期无聊写的，因为喜欢看喜剧团，顺手取了个奇怪的名字。后来这东西成了人工智能、数据科学、金融交易的默认语言。微信小程序，坊间传闻最早就是一个开发者独立写出来的原型，后来长成了一个千亿级别的生态（传闻）。Notion，Nginx，Golang，lun…</p><p>你会发现一个奇怪的规律：这些最终改变了无数人生活的东西，它们的起点往往小得令人发笑——一个人，一个无聊的假期，一个“这东西不好用那我写一个吧”的念头。没有商业计划书，没有用户调研，没有增长黑客，没有OKR。</p><p>这不是巧合。</p><p>大团队有大团队的好处：分工明确，资源充足，抗风险能力强。但大团队也有一个几乎无解的问题：创意在传递过程中会不断衰减。一个想法从决策层传递到执行层，中间要经过无数次“翻译”，每次翻译都会丢失一些原初的锐度。最后做出来的东西，往往是一个“被所有人同意”的平庸产物。它不会太差，但也很少令人惊喜。</p><p>而那一个人就不一样了。他脑子里那个画面是完整的，至少在他一鼓作气拿出作品之前。他不需要说服任何人，不需要写立项报告，不需要应付跨部门协调。他只需要坐下来，把脑子里的东西变成代码。所有决策都在一瞬间完成，所有的风格都是一贯的。这种完整性，是大团队几乎无法复制的东西。</p><p>更重要的是动机。</p><p>那些单枪匹马做出大东西的人，几乎没有一个是为了“改变世界”开始的。他们做的事情，本质上是在解决自己的实际痛点。不是“用户需要什么”，而是“我需要什么”。这个区别至关重要。因为当一个人为自己解决问题时，他会变得极其挑剔，极其执着，极其不妥协。他不会在“差不多就行”的地方停下来。而恰好是这种对自己苛刻，最终让他的作品满足了无数陌生人的需求。</p><p>所以回到开始的问题：SQLite 只有三个人维护，意味着什么？</p><p>意味着这个世界的底层架构，很多时候是少数几个人的审美、判断力和责任心在支撑。听起来有点危险，但历史一再证明，这反而是最可靠的模式。因为真正的好东西，往往不是被“组织”出来的，而是被“长”出来的。它需要的是一个清晰的愿景，一个出发的理由，和足够小的噪音。</p><p>我们总喜欢谈论“规模”——团队规模、融资规模、用户规模。但规模从来不是原因，而是结果。是先有那个完整而有力的核，然后世界被它吸引过来。三个人认真写好的代码，比三百个人凑合的代码，更能承受时间。</p><p>你可能不是程序员，不写代码。但这个道理在任何领域都一样：好的作品不需要所有人都同意，它只需要一个人把它想透，然后做出来。</p><p>剩下的，世界会自己走过来。</p>]]></content:encoded>
      
      
      <category domain="https://www.banzhuanriji.com/categories/blog/">博客</category>
      
      
      <category domain="https://www.banzhuanriji.com/tags/%E7%8B%AC%E7%AB%8B%E5%BC%80%E5%8F%91/">独立开发</category>
      
      <category domain="https://www.banzhuanriji.com/tags/SQLite/">SQLite</category>
      
      <category domain="https://www.banzhuanriji.com/tags/Linux/">Linux</category>
      
      <category domain="https://www.banzhuanriji.com/tags/Python/">Python</category>
      
      <category domain="https://www.banzhuanriji.com/tags/%E5%B0%91%E5%8D%B3%E6%98%AF%E5%A4%9A/">少即是多</category>
      
      <category domain="https://www.banzhuanriji.com/tags/%E5%BC%80%E6%BA%90/">开源</category>
      
      <category domain="https://www.banzhuanriji.com/tags/%E4%BA%A7%E5%93%81%E5%93%B2%E5%AD%A6/">产品哲学</category>
      
      <category domain="https://www.banzhuanriji.com/tags/%E6%94%B9%E5%8F%98%E4%B8%96%E7%95%8C/">改变世界</category>
      
      
      <comments>https://www.banzhuanriji.com/blog/before-things-happning/#disqus_thread</comments>
      
    </item>
    
    <item>
      <title>一个简单的带统计的短链接Workers</title>
      <link>https://www.banzhuanriji.com/posts/short-link-workers/</link>
      <guid>https://www.banzhuanriji.com/posts/short-link-workers/</guid>
      <pubDate>Wed, 04 Feb 2026 08:32:47 GMT</pubDate>
      
        
        
          
          
      <description></description>
          
        
      
      
      
      <content:encoded><![CDATA[<p>今天配合 AI 写了一个简单的带统计的短链接中间页程序，部署在 Cloudflare Workers 上，配合 KV 存储实现。不仅能实现长链接转短链接，还能记录每一次点击的次数。</p><p>简单的说，就是部署在Worker上的，通过KV储存链接和次数，每次访问都会刷新KV的短链接程序。用<code>pico.min.css</code>优化了一些样式的显示。并且做了简单的管理功能。</p><h3 id="代码实现"><a href="#代码实现" class="headerlink" title="代码实现"></a>代码实现</h3><pre><code class="hljs javascript"><span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> &#123;  <span class="hljs-keyword">async</span> <span class="hljs-title function_">fetch</span>(<span class="hljs-params">request, env, ctx</span>) &#123;    <span class="hljs-keyword">const</span> url = <span class="hljs-keyword">new</span> <span class="hljs-title function_">URL</span>(request.<span class="hljs-property">url</span>);    <span class="hljs-keyword">const</span> path = url.<span class="hljs-property">pathname</span>;    <span class="hljs-keyword">if</span> (path === <span class="hljs-string">&#x27;/admin&#x27;</span> || path.<span class="hljs-title function_">startsWith</span>(<span class="hljs-string">&#x27;/admin/&#x27;</span>)) &#123;      <span class="hljs-keyword">return</span> <span class="hljs-title function_">handleAdmin</span>(request, env);    &#125;    <span class="hljs-keyword">const</span> shortCode = path.<span class="hljs-title function_">slice</span>(<span class="hljs-number">1</span>);    <span class="hljs-keyword">if</span> (shortCode &amp;&amp; shortCode !== <span class="hljs-string">&#x27;favicon.ico&#x27;</span> &amp;&amp; shortCode !== <span class="hljs-string">&#x27;&#x27;</span>) &#123;      <span class="hljs-keyword">return</span> <span class="hljs-title function_">handleRedirect</span>(shortCode, env, ctx);    &#125;    <span class="hljs-keyword">return</span> <span class="hljs-keyword">new</span> <span class="hljs-title class_">Response</span>(<span class="hljs-string">&#x27;Short Link Service is Running.&#x27;</span>, &#123; <span class="hljs-attr">status</span>: <span class="hljs-number">200</span> &#125;);  &#125;&#125;;<span class="hljs-keyword">async</span> <span class="hljs-keyword">function</span> <span class="hljs-title function_">handleRedirect</span>(<span class="hljs-params">shortCode, env, ctx</span>) &#123;  <span class="hljs-keyword">const</span> key = <span class="hljs-string">`link:<span class="hljs-subst">$&#123;shortCode&#125;</span>`</span>;  <span class="hljs-keyword">const</span> dataStr = <span class="hljs-keyword">await</span> env.<span class="hljs-property">SHORT_LINKS</span>.<span class="hljs-title function_">get</span>(key);    <span class="hljs-keyword">if</span> (!dataStr) &#123;    <span class="hljs-keyword">return</span> <span class="hljs-keyword">new</span> <span class="hljs-title class_">Response</span>(<span class="hljs-string">&#x27;链接不存在或已被删除&#x27;</span>, &#123; <span class="hljs-attr">status</span>: <span class="hljs-number">404</span> &#125;);  &#125;    <span class="hljs-keyword">const</span> data = <span class="hljs-title class_">JSON</span>.<span class="hljs-title function_">parse</span>(dataStr);    ctx.<span class="hljs-title function_">waitUntil</span>((<span class="hljs-title function_">async</span> () =&gt; &#123;    data.<span class="hljs-property">visits</span> = (data.<span class="hljs-property">visits</span> || <span class="hljs-number">0</span>) + <span class="hljs-number">1</span>;    <span class="hljs-keyword">await</span> env.<span class="hljs-property">SHORT_LINKS</span>.<span class="hljs-title function_">put</span>(key, <span class="hljs-title class_">JSON</span>.<span class="hljs-title function_">stringify</span>(data));  &#125;)());    <span class="hljs-keyword">return</span> <span class="hljs-title class_">Response</span>.<span class="hljs-title function_">redirect</span>(data.<span class="hljs-property">originalUrl</span>, <span class="hljs-number">302</span>);&#125;<span class="hljs-keyword">async</span> <span class="hljs-keyword">function</span> <span class="hljs-title function_">handleAdmin</span>(<span class="hljs-params">request, env</span>) &#123;  <span class="hljs-keyword">const</span> auth = request.<span class="hljs-property">headers</span>.<span class="hljs-title function_">get</span>(<span class="hljs-string">&#x27;Authorization&#x27;</span>);  <span class="hljs-keyword">if</span> (!auth) &#123;    <span class="hljs-keyword">return</span> <span class="hljs-keyword">new</span> <span class="hljs-title class_">Response</span>(<span class="hljs-string">&#x27;Unauthorized&#x27;</span>, &#123;       <span class="hljs-attr">status</span>: <span class="hljs-number">401</span>,       <span class="hljs-attr">headers</span>: &#123; <span class="hljs-string">&#x27;WWW-Authenticate&#x27;</span>: <span class="hljs-string">&#x27;Basic realm=&quot;Admin&quot;&#x27;</span> &#125;     &#125;);  &#125;  <span class="hljs-keyword">const</span> credentials = <span class="hljs-title function_">atob</span>(auth.<span class="hljs-title function_">slice</span>(<span class="hljs-number">6</span>)).<span class="hljs-title function_">split</span>(<span class="hljs-string">&#x27;:&#x27;</span>);  <span class="hljs-keyword">if</span> (credentials[<span class="hljs-number">1</span>] !== env.<span class="hljs-property">ADMIN_PASSWORD</span>) &#123;    <span class="hljs-keyword">return</span> <span class="hljs-keyword">new</span> <span class="hljs-title class_">Response</span>(<span class="hljs-string">&#x27;密码错误&#x27;</span>, &#123; <span class="hljs-attr">status</span>: <span class="hljs-number">403</span> &#125;);  &#125;  <span class="hljs-keyword">const</span> url = <span class="hljs-keyword">new</span> <span class="hljs-title function_">URL</span>(request.<span class="hljs-property">url</span>);  <span class="hljs-keyword">if</span> (request.<span class="hljs-property">method</span> === <span class="hljs-string">&#x27;POST&#x27;</span> &amp;&amp; url.<span class="hljs-property">pathname</span> === <span class="hljs-string">&#x27;/admin/create&#x27;</span>) &#123;    <span class="hljs-keyword">const</span> formData = <span class="hljs-keyword">await</span> request.<span class="hljs-title function_">formData</span>();    <span class="hljs-keyword">const</span> originalUrl = formData.<span class="hljs-title function_">get</span>(<span class="hljs-string">&#x27;originalUrl&#x27;</span>);    <span class="hljs-keyword">let</span> shortCode = formData.<span class="hljs-title function_">get</span>(<span class="hljs-string">&#x27;customCode&#x27;</span>) || <span class="hljs-title class_">Math</span>.<span class="hljs-title function_">random</span>().<span class="hljs-title function_">toString</span>(<span class="hljs-number">36</span>).<span class="hljs-title function_">substring</span>(<span class="hljs-number">2</span>, <span class="hljs-number">8</span>);    <span class="hljs-keyword">if</span> (!originalUrl || !originalUrl.<span class="hljs-title function_">startsWith</span>(<span class="hljs-string">&#x27;http&#x27;</span>)) &#123;      <span class="hljs-keyword">return</span> <span class="hljs-keyword">new</span> <span class="hljs-title class_">Response</span>(<span class="hljs-string">&#x27;URL 格式不正确&#x27;</span>, &#123; <span class="hljs-attr">status</span>: <span class="hljs-number">400</span> &#125;);    &#125;    <span class="hljs-keyword">await</span> env.<span class="hljs-property">SHORT_LINKS</span>.<span class="hljs-title function_">put</span>(<span class="hljs-string">`link:<span class="hljs-subst">$&#123;shortCode&#125;</span>`</span>, <span class="hljs-title class_">JSON</span>.<span class="hljs-title function_">stringify</span>(&#123;      originalUrl,      <span class="hljs-attr">visits</span>: <span class="hljs-number">0</span>,      <span class="hljs-attr">createdAt</span>: <span class="hljs-keyword">new</span> <span class="hljs-title class_">Date</span>().<span class="hljs-title function_">toLocaleString</span>(<span class="hljs-string">&#x27;zh-CN&#x27;</span>, &#123; <span class="hljs-attr">timeZone</span>: <span class="hljs-string">&#x27;Asia/Shanghai&#x27;</span> &#125;)    &#125;));        <span class="hljs-keyword">return</span> <span class="hljs-title class_">Response</span>.<span class="hljs-title function_">redirect</span>(<span class="hljs-string">`<span class="hljs-subst">$&#123;url.origin&#125;</span>/admin`</span>, <span class="hljs-number">303</span>);  &#125;  <span class="hljs-keyword">if</span> (url.<span class="hljs-property">pathname</span> === <span class="hljs-string">&#x27;/admin/delete&#x27;</span>) &#123;    <span class="hljs-keyword">const</span> code = url.<span class="hljs-property">searchParams</span>.<span class="hljs-title function_">get</span>(<span class="hljs-string">&#x27;code&#x27;</span>);    <span class="hljs-keyword">if</span> (code) <span class="hljs-keyword">await</span> env.<span class="hljs-property">SHORT_LINKS</span>.<span class="hljs-title function_">delete</span>(<span class="hljs-string">`link:<span class="hljs-subst">$&#123;code&#125;</span>`</span>);    <span class="hljs-keyword">return</span> <span class="hljs-title class_">Response</span>.<span class="hljs-title function_">redirect</span>(<span class="hljs-string">`<span class="hljs-subst">$&#123;url.origin&#125;</span>/admin`</span>, <span class="hljs-number">303</span>);  &#125;  <span class="hljs-keyword">const</span> list = <span class="hljs-keyword">await</span> env.<span class="hljs-property">SHORT_LINKS</span>.<span class="hljs-title function_">list</span>(&#123; <span class="hljs-attr">prefix</span>: <span class="hljs-string">&#x27;link:&#x27;</span> &#125;);  <span class="hljs-keyword">const</span> links = [];  <span class="hljs-keyword">for</span> (<span class="hljs-keyword">const</span> key <span class="hljs-keyword">of</span> list.<span class="hljs-property">keys</span>) &#123;    <span class="hljs-keyword">const</span> val = <span class="hljs-keyword">await</span> env.<span class="hljs-property">SHORT_LINKS</span>.<span class="hljs-title function_">get</span>(key.<span class="hljs-property">name</span>);    <span class="hljs-keyword">if</span> (val) &#123;      <span class="hljs-keyword">const</span> data = <span class="hljs-title class_">JSON</span>.<span class="hljs-title function_">parse</span>(val);      links.<span class="hljs-title function_">push</span>(&#123;        <span class="hljs-attr">code</span>: key.<span class="hljs-property">name</span>.<span class="hljs-title function_">replace</span>(<span class="hljs-string">&#x27;link:&#x27;</span>, <span class="hljs-string">&#x27;&#x27;</span>),        ...data      &#125;);    &#125;  &#125;  <span class="hljs-keyword">return</span> <span class="hljs-keyword">new</span> <span class="hljs-title class_">Response</span>(<span class="hljs-title function_">renderAdminPage</span>(links), &#123;    <span class="hljs-attr">headers</span>: &#123; <span class="hljs-string">&#x27;Content-Type&#x27;</span>: <span class="hljs-string">&#x27;text/html;charset=UTF-8&#x27;</span> &#125;  &#125;);&#125;<span class="hljs-keyword">function</span> <span class="hljs-title function_">renderAdminPage</span>(<span class="hljs-params">links</span>) &#123;  <span class="hljs-keyword">const</span> rows = links.<span class="hljs-title function_">map</span>(<span class="hljs-function"><span class="hljs-params">link</span> =&gt;</span> <span class="hljs-string">`</span><span class="hljs-string">    &lt;tr&gt;</span><span class="hljs-string">      &lt;td&gt;&lt;a href=&quot;/<span class="hljs-subst">$&#123;link.code&#125;</span>&quot; target=&quot;_blank&quot;&gt;/<span class="hljs-subst">$&#123;link.code&#125;</span>&lt;/a&gt;&lt;/td&gt;</span><span class="hljs-string">      &lt;td style=&quot;word-break: break-all;&quot;&gt;&lt;small&gt;<span class="hljs-subst">$&#123;link.originalUrl&#125;</span>&lt;/small&gt;&lt;/td&gt;</span><span class="hljs-string">      &lt;td&gt;&lt;mark&gt;<span class="hljs-subst">$&#123;link.visits&#125;</span>&lt;/mark&gt;&lt;/td&gt;</span><span class="hljs-string">      &lt;td&gt;&lt;small&gt;<span class="hljs-subst">$&#123;link.createdAt || <span class="hljs-string">&#x27;未知&#x27;</span>&#125;</span>&lt;/small&gt;&lt;/td&gt;</span><span class="hljs-string">      &lt;td&gt;&lt;a href=&quot;/admin/delete?code=<span class="hljs-subst">$&#123;link.code&#125;</span>&quot; onclick=&quot;return confirm(&#x27;确定删除吗？&#x27;)&quot; style=&quot;color: #e53935;&quot;&gt;删除&lt;/a&gt;&lt;/td&gt;</span><span class="hljs-string">    &lt;/tr&gt;</span><span class="hljs-string">  `</span>).<span class="hljs-title function_">join</span>(<span class="hljs-string">&#x27;&#x27;</span>);  <span class="hljs-keyword">return</span> <span class="hljs-string">`</span><span class="hljs-string">  &lt;!DOCTYPE html&gt;</span><span class="hljs-string">  &lt;html lang=&quot;zh&quot;&gt;</span><span class="hljs-string">  &lt;head&gt;</span><span class="hljs-string">    &lt;meta charset=&quot;UTF-8&quot;&gt;</span><span class="hljs-string">    &lt;meta name=&quot;viewport&quot; content=&quot;width=device-width, initial-scale=1&quot;&gt;</span><span class="hljs-string">    &lt;title&gt;短链接管理后台&lt;/title&gt;</span><span class="hljs-string">    &lt;link rel=&quot;stylesheet&quot; href=&quot;https://cdnjs.cloudflare.com/ajax/libs/picocss/1.5.13/pico.min.css&quot;&gt;</span><span class="hljs-string">    &lt;style&gt;</span><span class="hljs-string">      body &#123; padding: 20px 0; &#125;</span><span class="hljs-string">      .container &#123; max-width: 900px; &#125;</span><span class="hljs-string">      .header &#123; margin-bottom: 40px; text-align: center; &#125;</span><span class="hljs-string">      mark &#123; font-weight: bold; padding: 2px 8px; border-radius: 4px; &#125;</span><span class="hljs-string">    &lt;/style&gt;</span><span class="hljs-string">  &lt;/head&gt;</span><span class="hljs-string">  &lt;body&gt;</span><span class="hljs-string">    &lt;main class=&quot;container&quot;&gt;</span><span class="hljs-string">      &lt;div class=&quot;header&quot;&gt;</span><span class="hljs-string">        &lt;h1&gt;🔗 Short Link Admin&lt;/h1&gt;</span><span class="hljs-string">        &lt;p&gt;轻量、高效的 Cloudflare Workers 短链接生成器&lt;/p&gt;</span><span class="hljs-string">      &lt;/div&gt;</span><span class="hljs-string">      &lt;article&gt;</span><span class="hljs-string">        &lt;header&gt;&lt;strong&gt;新建短链接&lt;/strong&gt;&lt;/header&gt;</span><span class="hljs-string">        &lt;form action=&quot;/admin/create&quot; method=&quot;POST&quot;&gt;</span><span class="hljs-string">          &lt;div class=&quot;grid&quot;&gt;</span><span class="hljs-string">            &lt;input type=&quot;url&quot; name=&quot;originalUrl&quot; placeholder=&quot;粘贴长链接 (https://...)&quot; required&gt;</span><span class="hljs-string">            &lt;input type=&quot;text&quot; name=&quot;customCode&quot; placeholder=&quot;自定义短码 (选填)&quot;&gt;</span><span class="hljs-string">            &lt;button type=&quot;submit&quot;&gt;立即创建&lt;/button&gt;</span><span class="hljs-string">          &lt;/div&gt;</span><span class="hljs-string">        &lt;/form&gt;</span><span class="hljs-string">      &lt;/article&gt;</span><span class="hljs-string">      &lt;figure&gt;</span><span class="hljs-string">        &lt;table role=&quot;grid&quot;&gt;</span><span class="hljs-string">          &lt;thead&gt;</span><span class="hljs-string">            &lt;tr&gt;</span><span class="hljs-string">              &lt;th&gt;短码&lt;/th&gt;</span><span class="hljs-string">              &lt;th&gt;原始链接&lt;/th&gt;</span><span class="hljs-string">              &lt;th&gt;点击量&lt;/th&gt;</span><span class="hljs-string">              &lt;th&gt;创建时间&lt;/th&gt;</span><span class="hljs-string">              &lt;th&gt;操作&lt;/th&gt;</span><span class="hljs-string">            &lt;/tr&gt;</span><span class="hljs-string">          &lt;/thead&gt;</span><span class="hljs-string">          &lt;tbody&gt;</span><span class="hljs-string">            <span class="hljs-subst">$&#123;rows || <span class="hljs-string">&#x27;&lt;tr&gt;&lt;td colspan=&quot;5&quot; style=&quot;text-align:center;&quot;&gt;暂无数据&lt;/td&gt;&lt;/tr&gt;&#x27;</span>&#125;</span></span><span class="hljs-string">          &lt;/tbody&gt;</span><span class="hljs-string">        &lt;/table&gt;</span><span class="hljs-string">      &lt;/figure&gt;</span><span class="hljs-string">    &lt;/main&gt;</span><span class="hljs-string">  &lt;/body&gt;</span><span class="hljs-string">  &lt;/html&gt;</span><span class="hljs-string">  `</span>;&#125;</code></pre><hr><h3 id="注意"><a href="#注意" class="headerlink" title="注意"></a>注意</h3><ol><li>KV空间我命名的是SHORT_LINKS，你可以自己改</li><li>环境变量需要有ADMIN_PASSWORD用来储存管理密码</li><li>注意绑定WORKER和KV</li><li>第一次打开&#x2F;admin，提示输入账户和密码，只输入密码即可！</li><li>免费的KV每天的PUT次数好像是有限制的，并且还有很多问题。小玩具，不用太在意。</li></ol><p>你可以点击这里跳转本文章： <a href="https://link.1tb.site/short-link">https://link.1tb.site/short-link</a></p>]]></content:encoded>
      
      
      <category domain="https://www.banzhuanriji.com/categories/posts/">默认</category>
      
      
      <category domain="https://www.banzhuanriji.com/tags/%E7%BB%9F%E8%AE%A1/">统计</category>
      
      <category domain="https://www.banzhuanriji.com/tags/Cloudflare/">Cloudflare</category>
      
      <category domain="https://www.banzhuanriji.com/tags/Serverless/">Serverless</category>
      
      <category domain="https://www.banzhuanriji.com/tags/JavaScript/">JavaScript</category>
      
      <category domain="https://www.banzhuanriji.com/tags/%E7%9F%AD%E9%93%BE%E6%8E%A5/">短链接</category>
      
      <category domain="https://www.banzhuanriji.com/tags/Workers/">Workers</category>
      
      
      <comments>https://www.banzhuanriji.com/posts/short-link-workers/#disqus_thread</comments>
      
    </item>
    
    <item>
      <title>美丽小废物——Nuphy Node 75 LP</title>
      <link>https://www.banzhuanriji.com/life/nuphy-node-75-lp/</link>
      <guid>https://www.banzhuanriji.com/life/nuphy-node-75-lp/</guid>
      <pubDate>Fri, 19 Dec 2025 06:35:00 GMT</pubDate>
      
        
        
          
          
      <description></description>
          
        
      
      
      
      <content:encoded><![CDATA[<blockquote><p>如果你能忍受时不时断连，正在游戏的时候键盘失效，正在工作的时候狼狈地去重置键盘，那么Nuphy键盘是一个不错的选择！</p></blockquote><p>我在11月20日夜间偶然刷到了Nuphy的键盘，感觉外观不错，并且看起来适合工作（码字，写代码）。于是在其官方直播间购买了一款Nuiphy Node 75 LP(Low-Profile 矮轴)的键盘，花费399。</p><p>2天后，周末到货，我专门去公司把键盘拿回家想体验一下。</p><p>对比我现在用的IKBC的85键（当时买的也是400元左右），看起来漂亮了很多，很适合办公。但是一摸上手就感觉不对。完全就是儿童电话玩具的的反馈… 或许是矮轴的原因？又或者我习惯了现在的键盘。我不确定。不过可以确定的是按键很松动，不像100块以上的产品。</p><p>稍微总结一下它的优点：</p><ol><li>看起来漂亮</li><li>三模</li><li>web驱动</li><li>键位合理，键距也合理</li></ol><p>缺点呢，缺点可太多了</p><p><img src="/img/nuphy-node-75-lp/1.png" alt="是你让我说缺点的奥"></p><ol><li>按键非常松动。我不知道这么形容一款键盘合不合适？每个键左右晃动幅度很大。</li><li>空格下只有一颗灯珠。上面看会 B 两边发黑。坐直了会看到一个耀眼的 B🤣 （询问客服后告知设计如此）。现在只能关了使用，因为太丑了。</li><li>用了不到1个月（还差一天），断连3次，我快要崩溃了！</li><li>细看+上手很廉价。有点像磨砂质感的小家电。（个人观点）</li></ol><p><img src="/img/nuphy-node-75-lp/2.webp" alt="是你让我说缺点的奥"></p><p><img src="/img/nuphy-node-75-lp/3.webp" alt="是你让我说缺点的奥"></p><hr><p>到目前位置，我视图解决问题的方案以及处理结果</p><ol><li>联系抖音店铺客服</li></ol><blockquote><p>我以为是机器人，因为每次都是让我重置</p></blockquote><ol start="2"><li>邮件联系官方</li></ol><blockquote><p>石沉大海</p></blockquote><hr><h2 id="这里是SEO部分"><a href="#这里是SEO部分" class="headerlink" title="这里是SEO部分"></a>这里是SEO部分</h2>]]></content:encoded>
      
      
      <category domain="https://www.banzhuanriji.com/categories/life/">生活</category>
      
      
      <category domain="https://www.banzhuanriji.com/tags/NuPhy-%E9%94%AE%E7%9B%98/">NuPhy 键盘</category>
      
      <category domain="https://www.banzhuanriji.com/tags/NuPhy-Air75-V2/">NuPhy Air75 V2</category>
      
      <category domain="https://www.banzhuanriji.com/tags/NuPhy-Air60/">NuPhy Air60</category>
      
      <category domain="https://www.banzhuanriji.com/tags/NuPhy-Air96/">NuPhy Air96</category>
      
      <category domain="https://www.banzhuanriji.com/tags/NuPhy-Halo75/">NuPhy Halo75</category>
      
      <category domain="https://www.banzhuanriji.com/tags/NuPhy-Halo65/">NuPhy Halo65</category>
      
      <category domain="https://www.banzhuanriji.com/tags/NuPhy-Halo96/">NuPhy Halo96</category>
      
      <category domain="https://www.banzhuanriji.com/tags/NuPhy-Field75/">NuPhy Field75</category>
      
      <category domain="https://www.banzhuanriji.com/tags/%E7%9F%AE%E8%BD%B4%E6%9C%BA%E6%A2%B0%E9%94%AE%E7%9B%98/">矮轴机械键盘</category>
      
      <category domain="https://www.banzhuanriji.com/tags/NuPhy-%E4%BC%98%E7%BC%BA%E7%82%B9/">NuPhy 优缺点</category>
      
      <category domain="https://www.banzhuanriji.com/tags/NuPhy-%E7%BC%BA%E7%82%B9/">NuPhy 缺点</category>
      
      <category domain="https://www.banzhuanriji.com/tags/NuPhy-%E9%81%BF%E9%9B%B7/">NuPhy 避雷</category>
      
      <category domain="https://www.banzhuanriji.com/tags/%E5%88%AB%E4%B9%B0-NuPhy/">别买 NuPhy</category>
      
      <category domain="https://www.banzhuanriji.com/tags/NuPhy-%E6%99%BA%E5%95%86%E7%A8%8E/">NuPhy 智商税</category>
      
      <category domain="https://www.banzhuanriji.com/tags/NuPhy-%E7%BF%BB%E8%BD%A6/">NuPhy 翻车</category>
      
      <category domain="https://www.banzhuanriji.com/tags/NuPhy-%E5%94%AE%E5%90%8E%E6%80%8E%E4%B9%88%E6%A0%B7/">NuPhy 售后怎么样</category>
      
      <category domain="https://www.banzhuanriji.com/tags/NuPhy-%E8%B4%A8%E9%87%8F%E9%97%AE%E9%A2%98/">NuPhy 质量问题</category>
      
      <category domain="https://www.banzhuanriji.com/tags/NuPhy-%E5%BB%B6%E8%BF%9F/">NuPhy 延迟</category>
      
      <category domain="https://www.banzhuanriji.com/tags/NuPhy-%E6%96%AD%E8%BF%9E/">NuPhy 断连</category>
      
      <category domain="https://www.banzhuanriji.com/tags/NuPhy-%E7%9C%9F%E5%AE%9E%E6%B5%8B%E8%AF%84/">NuPhy 真实测评</category>
      
      <category domain="https://www.banzhuanriji.com/tags/NuPhy-%E6%B7%B1%E5%BA%A6%E6%B5%8B%E8%AF%84/">NuPhy 深度测评</category>
      
      <category domain="https://www.banzhuanriji.com/tags/NuPhy-%E4%BD%BF%E7%94%A8%E6%84%9F%E5%8F%97/">NuPhy 使用感受</category>
      
      <category domain="https://www.banzhuanriji.com/tags/NuPhy-vs-Keychron/">NuPhy vs Keychron</category>
      
      <category domain="https://www.banzhuanriji.com/tags/NuPhy-%E5%80%BC%E5%BE%97%E4%B9%B0%E5%90%97/">NuPhy 值得买吗</category>
      
      <category domain="https://www.banzhuanriji.com/tags/2025%E5%B9%B4%E6%9C%BA%E6%A2%B0%E9%94%AE%E7%9B%98%E6%8E%A8%E8%8D%90%E4%B8%8E%E4%B8%8D%E6%8E%A8%E8%8D%90/">2025年机械键盘推荐与不推荐</category>
      
      <category domain="https://www.banzhuanriji.com/tags/NuPhy%E9%94%AE%E7%9B%98%E5%A5%BD%E7%94%A8%E5%90%97/">NuPhy键盘好用吗</category>
      
      <category domain="https://www.banzhuanriji.com/tags/NuPhy%E7%9F%AE%E8%BD%B4%E7%BC%BA%E7%82%B9/">NuPhy矮轴缺点</category>
      
      <category domain="https://www.banzhuanriji.com/tags/NuPhy%E5%BB%B6%E8%BF%9F%E9%AB%98/">NuPhy延迟高</category>
      
      <category domain="https://www.banzhuanriji.com/tags/NuPhy%E6%BC%8F%E7%94%B5/">NuPhy漏电</category>
      
      <category domain="https://www.banzhuanriji.com/tags/NuPhy%E5%9B%BA%E4%BB%B6%E6%9B%B4%E6%96%B0%E5%A4%B1%E8%B4%A5/">NuPhy固件更新失败</category>
      
      <category domain="https://www.banzhuanriji.com/tags/NuPhy%E6%80%A7%E4%BB%B7%E6%AF%94/">NuPhy性价比</category>
      
      <category domain="https://www.banzhuanriji.com/tags/NuPhy%E5%B9%B3%E6%9B%BF%E9%94%AE%E7%9B%98%E5%BB%BA%E8%AE%AE/">NuPhy平替键盘建议</category>
      
      
      <comments>https://www.banzhuanriji.com/life/nuphy-node-75-lp/#disqus_thread</comments>
      
    </item>
    
    <item>
      <title>当我决定重构项目时</title>
      <link>https://www.banzhuanriji.com/work/when-i-need-to-redevelop-an-app/</link>
      <guid>https://www.banzhuanriji.com/work/when-i-need-to-redevelop-an-app/</guid>
      <pubDate>Thu, 04 Dec 2025 14:55:09 GMT</pubDate>
      
        
        
          
          
      <description></description>
          
        
      
      
      
      <content:encoded><![CDATA[<h2 id="守门人"><a href="#守门人" class="headerlink" title="守门人"></a>守门人</h2><p>上一位守门人匆匆离开。</p><p>我被派来独守这座城堡，整个城堡外布满枯死的藤蔓，又或者那根本就是缝缝补补后的巨大裂痕。</p><p>我在大厅找到了一卷羊皮，上面密密麻麻刻满文字，杂乱无章，像已失传的咒文语法，互相嵌套。好在仔细分辨还能读懂些字。这些字的时间有远有近，能看出来有人一直在往上写什么东西。不知为何，重开一页再写不是更好吗？叠起来让人难以辨认，像一锅反复加热的老汤，上面布满凝固的油膜。</p><p>我从心里开始恐惧和反感。我想转身离开，但我似乎没有选择。城主给的赏金虽丰厚，却明码标价只够买一副上好的棺材板。我确实来了。</p><h2 id="工作"><a href="#工作" class="headerlink" title="工作"></a>工作</h2><p>几个日夜后我在城堡中醒来，不知为何没日没夜在这古老城堡的残垣断壁上雕刻一束束优美的线条，偶尔也会被要求用刚送来的彩色缎子，覆盖住那些称得上憔悴的砖头。看起来格格不入但还算有点意思。不过偶尔我也会被要求做一些清理蛀虫的工作，简单，枯燥。可我的工作就是这些。</p><h2 id="重构"><a href="#重构" class="headerlink" title="重构"></a>重构</h2><p>夜深人静的夜晚，我巡逻走完今天的最后一趟，再次翻看那本羊皮卷。我似乎有点头绪！</p><p>我将第一页写满内容的羊皮纸撕了下来，努力辨识后抄录在后面的空页上。身后似乎传来声音，我没有在意。蛛网拖着灰烬飘在我的头上，我挠了几次，也不知摆下去了没有。</p><p>到第二页，这一页看起来文字太多了，甚至重重叠叠，以至于整张纸几乎是黑色的，我不知应该怎么处理它。带我来的人、上一个守门人、还有给我工作的人都没有通知我应不应该这么做，我撕掉了它。一块地板翘了起来——又或许它根本就是被上层掉落的碎石砸烂的！可在这种古老的城堡里砖石掉落应该也是常事。我没有在意。</p><p>然后是第三页，第四页…直到我手抄的那一页，前面的内容全被我从这厚重的羊皮纸上撕掉。我想我在未来的时间会有办法让它的内容更完整地展示出来。我选择回去休息。</p><p>后来的每一天，我在角楼上小心翼翼地剥离那些寄生的藤蔓，用坚固的钢梁替换掉那些腐朽的木头。每替换一块砖，我都要停下来，侧耳倾听塔楼的呼吸。如果呼吸变得急促，我就立刻停手，安抚躁动的魔力；如果呼吸平稳，我就继续。</p><p>我似乎忘记羊皮卷的事情，我没有在意。将来有很多时日让我去仔细分辨和抄录。</p><h2 id="巨龙"><a href="#巨龙" class="headerlink" title="巨龙"></a>巨龙</h2><p>我认为自己的工作循序渐进有条不紊。直到楼顶的铃铛狂响，不断向下蹦出红色弹幕。这铃铛非常笨重，肯定不是风吹动的。似乎是有什么东西要从城堡内破壳而出。我的师傅告诉我这是危险的情况。</p><p>在我没有反应过来的时候，一条巨龙从城堡底部飞出，还试图将它彻底撞碎。还好这不是我的第一份工作，我有对付巨龙的经验。至少是口头相传的经验。</p><p>我收起所有的工具，从背包拿出一只彩色的钩子，这是黑市买来的舶来品。当然，本地也有好货！可我觉得这只钩子更合我手。这守门人界人人都有的彩色钩子，是由一条条链接构成的。我将钩子往地面一敲，裂开一道缝隙，趁缝隙未闭合，抛向空中，这钩子幻化成无数铁链，耗费了点时间，但确实牢牢捆住这头恶龙！</p><p>它被困住了。我从背包取出一只猫头朝它扔了过去。我被告知这猫头可以从中召唤以前很厉害的人，他们将自己开源心血无私地存放在这只猫头内。他们会帮助我。猫头确实生效了，不过我没看到什么人，只是跳出很多经文一道道飘在羊皮卷上。看起来确实厉害，因为那巨龙似乎开始哀嚎。</p><p>……</p><p>我取出最近在守门人界好评极高的可以与人对话的手电筒。</p><p>“只需要打开手电筒，就可以完美收复任何巨兽！”</p><p>手电筒说明书的第一页是这么写的。实际上这是另一个守门人的评价，没人知道这句话为什么被放在说明书的第一页。</p><p>我拨动手电筒的开关，瞬间光明覆盖了整个世界。我想这就是我下单时，商家告诉我的“氛围”吧！他说这支手电筒可以杀死任何从虫子到大山一样的巨兽，而且免费让我使用。</p><h2 id="尾声"><a href="#尾声" class="headerlink" title="尾声"></a>尾声</h2><p>风停了。废塔的藤蔓在月光下悄悄抽出了新芽。我转身，听见身后传来细微的、几乎不可闻的铃铛声——不是报警，而是心跳。</p><p>现在，城堡的铜环门上刻了新的花纹，我在最下面刻了一行小字：BUG FIXED V11.0 prerelease</p>]]></content:encoded>
      
      
      <category domain="https://www.banzhuanriji.com/categories/work/">工作</category>
      
      
      <category domain="https://www.banzhuanriji.com/tags/%E9%9A%8F%E7%AC%94/">随笔</category>
      
      <category domain="https://www.banzhuanriji.com/tags/%E5%BC%80%E5%8F%91/">开发</category>
      
      <category domain="https://www.banzhuanriji.com/tags/%E7%AC%94%E8%AE%B0/">笔记</category>
      
      <category domain="https://www.banzhuanriji.com/tags/%E5%B7%A5%E4%BD%9C/">工作</category>
      
      <category domain="https://www.banzhuanriji.com/tags/%E9%87%8D%E6%9E%84/">重构</category>
      
      
      <comments>https://www.banzhuanriji.com/work/when-i-need-to-redevelop-an-app/#disqus_thread</comments>
      
    </item>
    
    <item>
      <title>一个简单的百度收录推送工具</title>
      <link>https://www.banzhuanriji.com/blog/baidu-indexing-push-tool/</link>
      <guid>https://www.banzhuanriji.com/blog/baidu-indexing-push-tool/</guid>
      <pubDate>Tue, 18 Nov 2025 03:27:27 GMT</pubDate>
      
        
        
          
          
      <description></description>
          
        
      
      
      
      <content:encoded><![CDATA[<h2 id="TLDR"><a href="#TLDR" class="headerlink" title="TLDR;"></a>TLDR;</h2><blockquote><p>百度搜索资源平台 <a href="https://ziyuan.baidu.com/linksubmit/index">https://ziyuan.baidu.com/linksubmit/index</a></p></blockquote><p>如果你有兴趣，你可以点击下面的地址尝试： <a href="https://push.banzhuanriji.com/">https://push.banzhuanriji.com/</a> </p><h2 id="功能介绍"><a href="#功能介绍" class="headerlink" title="功能介绍"></a>功能介绍</h2><p>在百度提供的API提交方案中，提供了PHP推送方案实例，简单改一下，现在可以做到的功能是：</p><p>输入sitemap或者feed地址（支持RSS和Atom），然后点击解析内容之后，会推送最新的10条内容到百度。</p><h2 id="注意"><a href="#注意" class="headerlink" title="注意"></a>注意</h2><ul><li>因为部分站点sitemap在更新的时候会刷新所有页面的时间，因此推荐使用FEED解析。</li><li>现在百度每天只支持推送10条地址。</li><li>推送内容会根据百度的推送算法进行排序，因此推送的内容会按照百度的排序进行推送。</li><li>推送完成后，会返回推送的结果，包括成功的数量和失败的数量。</li><li><strong>注意域名需要和KEY一致</strong> ，如下图中所示，每个域名有自己的KEY。</li></ul><p><img src="/img/baidu-indexing-push-tool/1.avif" alt="注意域名需要和KEY一致" title="选取红框内的内容"></p><h2 id="代码"><a href="#代码" class="headerlink" title="代码"></a>代码</h2><p>下面是代码的gist链接，包括PHP和HTML+JS的版本。HTML版本你可能需要解决跨域问题！当然你也可以修改一下使用RSS2JSON服务进行解析。</p><p><a href="https://gist.github.com/hoytzhang/6522a721d7797b3fe9f2dcc2dc205cf8">PHP版本</a></p><p><a href="https://gist.github.com/hoytzhang/74f19c214a0d5a447a7057d11f6d01a9">HTML+JS版本</a></p><p><a href="https://push.banzhuanriji.com/">我部署的PHP版本</a></p>]]></content:encoded>
      
      
      <category domain="https://www.banzhuanriji.com/categories/blog/">博客</category>
      
      
      <category domain="https://www.banzhuanriji.com/tags/%E7%99%BE%E5%BA%A6%E6%90%9C%E7%B4%A2%E8%B5%84%E6%BA%90%E5%B9%B3%E5%8F%B0/">百度搜索资源平台</category>
      
      <category domain="https://www.banzhuanriji.com/tags/%E9%93%BE%E6%8E%A5%E6%8F%90%E4%BA%A4/">链接提交</category>
      
      <category domain="https://www.banzhuanriji.com/tags/%E7%99%BE%E5%BA%A6%E6%8E%A8%E9%80%81API/">百度推送API</category>
      
      <category domain="https://www.banzhuanriji.com/tags/Sitemap/">Sitemap</category>
      
      <category domain="https://www.banzhuanriji.com/tags/RSS/">RSS</category>
      
      <category domain="https://www.banzhuanriji.com/tags/Atom/">Atom</category>
      
      <category domain="https://www.banzhuanriji.com/tags/FEED%E8%A7%A3%E6%9E%90/">FEED解析</category>
      
      <category domain="https://www.banzhuanriji.com/tags/%E7%BD%91%E7%AB%99%E6%94%B6%E5%BD%95/">网站收录</category>
      
      <category domain="https://www.banzhuanriji.com/tags/SEO%E5%B7%A5%E5%85%B7/">SEO工具</category>
      
      <category domain="https://www.banzhuanriji.com/tags/PHP%E6%8E%A8%E9%80%81%E6%96%B9%E6%A1%88/">PHP推送方案</category>
      
      
      <comments>https://www.banzhuanriji.com/blog/baidu-indexing-push-tool/#disqus_thread</comments>
      
    </item>
    
    <item>
      <title>给博客的图片添加一个漂亮的图片描述</title>
      <link>https://www.banzhuanriji.com/blog/a-fancy-image-desc-showing/</link>
      <guid>https://www.banzhuanriji.com/blog/a-fancy-image-desc-showing/</guid>
      <pubDate>Mon, 17 Nov 2025 04:44:47 GMT</pubDate>
      
        
        
          
          
      <description></description>
          
        
      
      
      
      <content:encoded><![CDATA[<h2 id="效果"><a href="#效果" class="headerlink" title="效果"></a>效果</h2><p><img src="/img/a-fancy-image-desc-showing/pexels-pixabay-247599.avif" alt="2025年11月17日 11点50分" title="图片来自 pixabay"></p><h2 id="实现步骤"><a href="#实现步骤" class="headerlink" title="实现步骤"></a>实现步骤</h2><h3 id="实现内容"><a href="#实现内容" class="headerlink" title="实现内容"></a>实现内容</h3><ol><li>等待DOM完全加载</li><li>获取所有的IMG标签</li><li>展示所有的IMG描述内容</li></ol><h3 id="我美化了什么"><a href="#我美化了什么" class="headerlink" title="我美化了什么"></a>我美化了什么</h3><ol><li>鼠标移上显示，避免遮挡图片内容，同样避免破坏版面</li><li>给文字背景毛玻璃效果</li><li>给毛玻璃动态背景颜色，颜色提取图片主色作为背景颜色</li><li>给字体一点描边，增加极端背景色下的可读性</li></ol><h3 id="实现代码"><a href="#实现代码" class="headerlink" title="实现代码"></a>实现代码</h3><pre><code class="hljs plaintext">document.addEventListener(&#x27;DOMContentLoaded&#x27;, function () &#123;    const mainContent = document.querySelector(&#x27;.post-content, .article-content&#x27;);    if (!mainContent) &#123;        return;    &#125;    mainContent.querySelectorAll(&#x27;img&#x27;).forEach(img =&gt; &#123;        const title = img.getAttribute(&#x27;title&#x27;);        const alt = img.getAttribute(&#x27;alt&#x27;);        if (!title &amp;&amp; !alt) &#123;            return;         &#125;        img.crossOrigin = &#x27;anonymous&#x27;;        const customElement = document.createElement(&#x27;div&#x27;);        customElement.setAttribute(&#x27;class&#x27;, &#x27;article-image-wrapper&#x27;);        customElement.style.display = &#x27;none&#x27;;         const figcaption = document.createElement(&#x27;figcaption&#x27;);        figcaption.setAttribute(&#x27;class&#x27;, &#x27;image-info-overlay&#x27;);        let infoHTML = &#x27;&#x27;;        if (alt) &#123;            infoHTML += `&lt;p class=&quot;image-alt-text&quot;&gt;$&#123;alt&#125;&lt;/p&gt;`;        &#125;        if (title) &#123;            infoHTML += `&lt;p class=&quot;image-title-text&quot;&gt;$&#123;title&#125;&lt;/p&gt;`;        &#125;        figcaption.innerHTML = infoHTML;        img.parentNode.insertBefore(customElement, img);        customElement.appendChild(img);        customElement.appendChild(figcaption);                customElement.addEventListener(&#x27;mouseover&#x27;, () =&gt; &#123;            customElement.classList.add(&#x27;is-hovering&#x27;);        &#125;);        customElement.addEventListener(&#x27;mouseout&#x27;, () =&gt; &#123;            customElement.classList.remove(&#x27;is-hovering&#x27;);        &#125;);        img.addEventListener(&#x27;load&#x27;, function () &#123;            customElement.style.display = &#x27;inline-block&#x27;;                         if (isSameOrigin(img.src)) &#123;                const canvas = document.createElement(&#x27;canvas&#x27;);                const rgbColor = getImageAverageColor(canvas, img);                figcaption.style.backgroundColor = rgbColor;            &#125; else &#123;                figcaption.style.backgroundColor = &#x27;rgba(0, 0, 0, 0.7)&#x27;;            &#125;        &#125;);        img.addEventListener(&#x27;error&#x27;, function () &#123;            customElement.remove();         &#125;);    &#125;);    function isSameOrigin(url) &#123;        try &#123;            const imgUrl = new URL(url);            return imgUrl.origin === window.location.origin;        &#125; catch (e) &#123;            return true;        &#125;    &#125;        function getImageAverageColor(canvas, img) &#123;        try &#123;            canvas.width = img.naturalWidth;            canvas.height = img.naturalHeight;            const context = canvas.getContext(&quot;2d&quot;);            context.drawImage(img, 0, 0, canvas.width, canvas.height);            const data = context.getImageData(0, 0, canvas.width, canvas.height).data;            let r = 0, g = 0, b = 0;            const pixelCount = data.length / 4;            for (let i = 0; i &lt; data.length; i += 4) &#123;                r += data[i];                g += data[i + 1];                 b += data[i + 2];             &#125;                        r = Math.round(r / pixelCount);            g = Math.round(g / pixelCount);            b = Math.round(b / pixelCount);            return `rgba($&#123;r&#125;, $&#123;g&#125;, $&#123;b&#125;, 0.3)`;        &#125; catch (e) &#123;            return &#x27;rgba(0, 0, 0, 0.7)&#x27;;        &#125;    &#125;&#125;);# css部分.article-image-wrapper &#123;position: relative;display: inline-block;overflow: hidden&#125;.article-image-wrapper img &#123;display: block;max-width: 100%;height: auto&#125;.image-info-overlay &#123;position: absolute;bottom: 0;left: 0;    right: 0;padding-left: 10px;padding-bottom: 2px;padding-top: 2px;border-radius: 0px 0px 4px 4px;color: white;backdrop-filter: blur(5px);box-sizing: border-box;transform: translateY(100%);transition: opacity .3s ease-out, transform .3s ease-out&#125;.article-image-wrapper.is-hovering .image-info-overlay &#123;opacity: 1;transform: translateY(0)&#125;.image-info-overlay p &#123;margin: 5px 0;line-height: 1;text-shadow: 1px 1px 2px rgba(0, 0, 0, 0.5);color: #ffffff;&#125;.image-alt-text&#123;font-size: 0.8rem;&#125;.image-title-text&#123;font-size: 1.1rem;&#125;</code></pre><h3 id="下面是比较详细的解释"><a href="#下面是比较详细的解释" class="headerlink" title="下面是比较详细的解释"></a>下面是比较详细的解释</h3><p>代码在 DOM 内容完全加载后执行，它遍历文章中的所有 <code>&lt;img&gt;</code> 标签，并对那些设置了 <code>title</code> 或 <code>alt</code> 属性的图片执行以下操作：</p><ul><li>图片包装与结构化：<ul><li>将图片用一个新的 <code>&lt;div&gt;</code> 元素（类名为 <code>article-image-wrapper</code>）包裹起来，并将其初始设置为隐藏 (<code>display: &#39;none&#39;</code>)。</li><li>在包裹元素内，为图片添加一个 <code>figcaption</code> 元素（类名为 <code>image-info-overlay</code>），用于显示图片的 <code>alt</code> 和 <code>title</code> 信息。</li></ul></li><li>信息显示（Alt&#x2F;Title）：<ul><li>从 <code>&lt;img&gt;</code> 标签中获取 <code>alt</code> 和 <code>title</code> 属性值，并将它们格式化为 <code>&lt;p&gt;</code> 标签，放入 <code>figcaption</code> 元素中。</li></ul></li><li>交互效果：<ul><li>为图片包裹元素添加鼠标悬停监听器，在悬停时添加&#x2F;移除 <code>is-hovering</code> 类，以便通过 CSS 实现悬停时的视觉效果。</li></ul></li><li>图片加载成功后的处理：<ul><li>当图片加载成功后，将包裹元素显示出来 (<code>display: &#39;inline-block&#39;</code>)。</li><li>尝试获取图片的平均颜色：<ul><li>如果是同源图片，则使用 <code>&lt;canvas&gt;</code> 元素计算图片的平均 RGB 颜色，并将此颜色设置为 <code>figcaption</code> 的背景色（带有 $0.3$ 的透明度）。</li><li>如果是跨域或计算失败（包括同源计算失败），则设置默认的深色背景（<code>rgba(0, 0, 0, 0.7)</code>）。</li></ul></li></ul></li><li>跨域设置与错误处理：<ul><li>将图片元素的 <code>crossOrigin</code> 属性设置为 <code>&#39;anonymous&#39;</code>，以支持跨域图片在 <code>&lt;canvas&gt;</code> 上进行颜色计算。</li><li>如果图片加载失败，则移除整个图片包裹元素。</li></ul></li></ul><h2 id="fancybox"><a href="#fancybox" class="headerlink" title="fancybox"></a>fancybox</h2><p>另外点击图片是有灯箱效果的，我使用的是fancybox3，首先你需要引入fancybox的css和js文件，然后在你的JS中添加如下代码：</p><pre><code class="hljs plaintext">&#x27;use strict&#x27;;const cheerio = require(&#x27;cheerio&#x27;);hexo.extend.filter.register(&#x27;after_post_render&#x27;, function(data) &#123;    if (!data.content) return data;    const $ = cheerio.load(data.content, &#123;        decodeEntities: false     &#125;);    $(&#x27;img:not(a img)&#x27;).each(function() &#123;        const $img = $(this);        const src = $img.attr(&#x27;src&#x27;);        const alt = $img.attr(&#x27;alt&#x27;);                if (!src) return;        const $a = $(&#x27;&lt;a&gt;&#x27;)            .attr(&#x27;href&#x27;, src)             .attr(&#x27;data-fancybox&#x27;, &#x27;gallery&#x27;)             .attr(&#x27;data-caption&#x27;, alt || &#x27;&#x27;);         $img.wrap($a);    &#125;);    data.content = $.html();    return data;&#125;);</code></pre><p>因为博客的图片我基本都从原图（jpg）格式转换为了avif格式。所以如果你希望点击图片展示的灯箱为原图（大尺寸原图），你可以使用以下的代码，把原图路径替换以下</p><pre><code class="hljs plaintext">$(&#x27;img:not(a img)&#x27;).each(function() &#123;    const $img = $(this);    const originalSrc = $img.attr(&#x27;src&#x27;);     const newSrc = originalSrc.replace(/\.avif$/, &#x27;.jpg&#x27;);     console.log(newSrc);    const alt = $img.attr(&#x27;alt&#x27;);        if (!newSrc) return;    const $a = $(&#x27;&lt;a&gt;&#x27;)        .attr(&#x27;href&#x27;, newSrc)         .attr(&#x27;data-fancybox&#x27;, &#x27;gallery&#x27;)         .attr(&#x27;data-caption&#x27;, alt || &#x27;&#x27;);     $img.wrap($a);&#125;);</code></pre>]]></content:encoded>
      
      
      <category domain="https://www.banzhuanriji.com/categories/blog/">博客</category>
      
      
      <category domain="https://www.banzhuanriji.com/tags/%E5%9B%BE%E7%89%87%E6%8F%8F%E8%BF%B0%E6%98%BE%E7%A4%BA%E6%95%88%E6%9E%9C/">图片描述显示效果</category>
      
      <category domain="https://www.banzhuanriji.com/tags/%E5%9B%BE%E7%89%87%E6%82%AC%E5%81%9C%E4%BA%A4%E4%BA%92/">图片悬停交互</category>
      
      <category domain="https://www.banzhuanriji.com/tags/%E5%9B%BE%E7%89%87%E4%B8%BB%E8%89%B2%E6%8F%90%E5%8F%96/">图片主色提取</category>
      
      <category domain="https://www.banzhuanriji.com/tags/Fancybox-%E7%81%AF%E7%AE%B1%E6%95%88%E6%9E%9C/">Fancybox 灯箱效果</category>
      
      <category domain="https://www.banzhuanriji.com/tags/JavaScript-%E5%9B%BE%E7%89%87%E5%A4%84%E7%90%86/">JavaScript 图片处理</category>
      
      <category domain="https://www.banzhuanriji.com/tags/IMG-%E6%A0%87%E7%AD%BE-alt-title-%E5%B1%9E%E6%80%A7%E5%B1%95%E7%A4%BA/">IMG 标签 alt/title 属性展示</category>
      
      <category domain="https://www.banzhuanriji.com/tags/CSS-%E6%AF%9B%E7%8E%BB%E7%92%83%E8%83%8C%E6%99%AF%E6%95%88%E6%9E%9C/">CSS 毛玻璃背景效果</category>
      
      <category domain="https://www.banzhuanriji.com/tags/%E5%90%8C%E6%BA%90%E5%9B%BE%E7%89%87%E9%A2%9C%E8%89%B2%E8%AE%A1%E7%AE%97/">同源图片颜色计算</category>
      
      <category domain="https://www.banzhuanriji.com/tags/%E8%B7%A8%E5%9F%9F%E5%9B%BE%E7%89%87%E5%A4%84%E7%90%86%E6%96%B9%E6%A1%88/">跨域图片处理方案</category>
      
      <category domain="https://www.banzhuanriji.com/tags/Hexo-%E5%9B%BE%E7%89%87%E7%81%AF%E7%AE%B1%E9%85%8D%E7%BD%AE/">Hexo 图片灯箱配置</category>
      
      <category domain="https://www.banzhuanriji.com/tags/%E9%BC%A0%E6%A0%87%E6%82%AC%E5%81%9C%E5%9B%BE%E7%89%87%E4%BF%A1%E6%81%AF%E6%98%BE%E7%A4%BA/">鼠标悬停图片信息显示</category>
      
      <category domain="https://www.banzhuanriji.com/tags/Canvas-%E8%8E%B7%E5%8F%96%E5%9B%BE%E7%89%87%E5%B9%B3%E5%9D%87%E8%89%B2/">Canvas 获取图片平均色</category>
      
      
      <comments>https://www.banzhuanriji.com/blog/a-fancy-image-desc-showing/#disqus_thread</comments>
      
    </item>
    
    <item>
      <title>我想看电影</title>
      <link>https://www.banzhuanriji.com/life/life-movie-watching-dilemma/</link>
      <guid>https://www.banzhuanriji.com/life/life-movie-watching-dilemma/</guid>
      <pubDate>Tue, 28 Oct 2025 17:14:04 GMT</pubDate>
      
        
        
          
          
      <description></description>
          
        
      
      
      
      <content:encoded><![CDATA[<p>我已经好久没有去电影院看电影了。</p><p>甚至好久没有看完一部电影了。</p><p>不过我对电影的要求还蛮多的。</p><p>首先故事要完整吧。不能搞得莫名其妙的开始又莫名其妙的结束。剧情不能说经得起推敲，但也应该有前后因果。尤其讨厌没有技术还故弄玄虚的导演。</p><p>其次演员演技需要在线。因此我不爱看爱情片。爱情片就是莫名其妙的开始谈恋爱，然后歇斯底里或者决然地分手。分分合合几个过程最后包饺子或者遗憾离场。也不爱看警匪片，警匪片我看不出演技。所有人的表情几乎就几个：挤成川字眉心大喊大叫，嘻嘻哈哈尬笑圆场，要么用最浮夸的演技演个变态，还有经典的面瘫装高手。额外点名：刘诗诗，周迅，刘亦菲，王传君。这几个演员火了之后，演什么都一个表情。刘诗诗甚至结婚都是那个经典表情。</p><p>配音很重要。很多演员甚至不会自己配音，依然会专门找个港台腔的配音模仿自己的声音。太别扭了。不懂在装什么玩意。</p><p>还有我的原因：似乎没有完整的时间，项目的不稳定，导致我需要做一个随时应答机器人。不是没有看电影的时间，只是影院不能暂停。</p><p>稍微总结了一下：我想去影院看一部故事完整，演技在线的爱情片和现代警匪片以外的电影。</p><p>挺难。</p>]]></content:encoded>
      
      
      <category domain="https://www.banzhuanriji.com/categories/life/">生活</category>
      
      
      <category domain="https://www.banzhuanriji.com/tags/%E9%9A%8F%E7%AC%94/">随笔</category>
      
      <category domain="https://www.banzhuanriji.com/tags/%E7%94%B5%E5%BD%B1/">电影</category>
      
      <category domain="https://www.banzhuanriji.com/tags/%E8%A7%82%E5%BD%B1%E4%BD%93%E9%AA%8C/">观影体验</category>
      
      <category domain="https://www.banzhuanriji.com/tags/%E6%BC%94%E5%91%98%E6%BC%94%E6%8A%80/">演员演技</category>
      
      
      <comments>https://www.banzhuanriji.com/life/life-movie-watching-dilemma/#disqus_thread</comments>
      
    </item>
    
    <item>
      <title>修改HEXO网站的RSS地址</title>
      <link>https://www.banzhuanriji.com/blog/Modify-the-RSS-URL-of-the-HEXO-website/</link>
      <guid>https://www.banzhuanriji.com/blog/Modify-the-RSS-URL-of-the-HEXO-website/</guid>
      <pubDate>Sat, 11 Oct 2025 03:25:23 GMT</pubDate>
      
        
        
          
          
      <description></description>
          
        
      
      
      
      <content:encoded><![CDATA[<blockquote><p>以下方案对于 hexo-generator-feed 插件和 hexo-feed 插件通用，不过我目前使用的是hexo-generator-feed</p></blockquote><p>因为之前使用的动态框架，RSS地址是 <a href="https://banzhuanriji.com/feed/">https://banzhuanriji.com/feed/</a> ，更换为hexo之后，使用hexo-generator-feed插件，RSS地址变成：</p><p><a href="https://banzhuanriji.com/atom.xml">https://banzhuanriji.com/atom.xml</a><br><a href="https://banzhuanriji.com/rss.xml">https://banzhuanriji.com/rss.xml</a></p><p>这不是我想要的，我想保持 <a href="https://banzhuanriji.com/feed/">https://banzhuanriji.com/feed/</a> 这个地址。首先看官方文档 ：<a href="https://www.npmjs.com/package/hexo-generator-feed">https://www.npmjs.com/package/hexo-generator-feed</a> ，配置的格式大概如下：</p><pre><code class="hljs plaintext">feed:  type:    - atom    - rss2  path:    - atom.xml    - rss2.xml  limit: 20  hub:  content:  content_limit: 140  content_limit_delim: &#x27; &#x27;  order_by: -date  icon: icon.png  autodiscovery: true  template:</code></pre><p>也就是说，你是可以自定义地址的。</p><p>但是如果你的地址修改为例如 domain.com&#x2F;feed&#x2F; ，它会<strong>直接下载</strong>这个xml文件而不是打开访问。通过插件配置的话，这里似乎没有什么好的解决方案。</p><h3 id="解决方案"><a href="#解决方案" class="headerlink" title="解决方案"></a>解决方案</h3><p>我是部署在腾讯云 edgeone 的 pages 服务上的。他是类似vercel的服务。同样也和vercel一样支持一些自定义行文，你只需要创建<code>edgeone.json</code>文件即可。</p><p>其中支持redirects和rewrites，下面是我第一次写的文件</p><pre><code class="hljs plaintext">&#123;  &quot;rewrites&quot;: [    &#123;      &quot;source&quot;: &quot;/feed&quot;,      &quot;destination&quot;: &quot;/rss.xml&quot;    &#125;  ],  &quot;headers&quot;: [    &#123;      &quot;source&quot;: &quot;/feed&quot;,      &quot;headers&quot;: [        &#123;          &quot;key&quot;: &quot;Content-Type&quot;,          &quot;value&quot;: &quot;application/xml; charset=utf-8&quot;        &#125;      ]    &#125;  ]&#125;</code></pre><p>这里会有一个问题，就是如果访问 <a href="https://banzhuanriji.com/feed/">https://banzhuanriji.com/feed/</a> ，是会404的，只可以访问 <a href="https://banzhuanriji.com/feed">https://banzhuanriji.com/feed</a></p><p>但是在feed后添加<code>/</code>，系统会认为这是个文件夹，也不可用。所以就要用下面的两个方案：</p><h4 id="1-重定向"><a href="#1-重定向" class="headerlink" title="1. 重定向"></a>1. 重定向</h4><pre><code class="hljs plaintext">&#123;  &quot;redirects&quot;: [    &#123;      &quot;source&quot;: &quot;/feed/&quot;,      &quot;destination&quot;: &quot;/feed&quot;,      &quot;statusCode&quot;: 301    &#125;  ],  &quot;rewrites&quot;: [    &#123;      &quot;source&quot;: &quot;/feed&quot;,      &quot;destination&quot;: &quot;/rss.xml&quot;    &#125;  ],  &quot;headers&quot;: [    &#123;      &quot;source&quot;: &quot;/feed&quot;,      &quot;headers&quot;: [        &#123;          &quot;key&quot;: &quot;Content-Type&quot;,          &quot;value&quot;: &quot;application/xml; charset=utf-8&quot;        &#125;      ]    &#125;,    &#123;      &quot;source&quot;: &quot;/rss.xml&quot;,      &quot;headers&quot;: [        &#123;          &quot;key&quot;: &quot;Content-Type&quot;,          &quot;value&quot;: &quot;application/xml; charset=utf-8&quot;        &#125;      ]    &#125;  ]&#125;</code></pre><p>它的逻辑打开就是之前的重写方案，然后添加一个重定向方案。但是浏览器会提示太多redirects，直接访问失败。</p><h4 id="2-最终方案，捕捉所有feed的地址"><a href="#2-最终方案，捕捉所有feed的地址" class="headerlink" title="2. 最终方案，捕捉所有feed的地址"></a>2. 最终方案，捕捉所有feed的地址</h4><pre><code class="hljs plaintext">&#123;  &quot;rewrites&quot;: [    &#123;      &quot;source&quot;: &quot;/feed/*&quot;,      &quot;destination&quot;: &quot;/rss.xml&quot;    &#125;  ],  &quot;headers&quot;: [    &#123;      &quot;source&quot;: &quot;/feed/*&quot;,      &quot;headers&quot;: [        &#123;          &quot;key&quot;: &quot;Content-Type&quot;,          &quot;value&quot;: &quot;application/xml; charset=utf-8&quot;        &#125;      ]    &#125;  ]&#125;</code></pre><p>这个方案终于可行，但是有一个问题，就是这个方案会匹配所有feed下的请求到你指定的文件。(不过至少可用了😄</p><blockquote><p>如果你有一定的阅读逻辑，你可以点击这里查看文档 <a href="https://edgeone.cloud.tencent.com/pages/document/162936771610066944">https://edgeone.cloud.tencent.com/pages/document/162936771610066944</a></p></blockquote>]]></content:encoded>
      
      
      <category domain="https://www.banzhuanriji.com/categories/blog/">博客</category>
      
      
      <category domain="https://www.banzhuanriji.com/tags/hexo/">hexo</category>
      
      <category domain="https://www.banzhuanriji.com/tags/blog/">blog</category>
      
      <category domain="https://www.banzhuanriji.com/tags/rss/">rss</category>
      
      <category domain="https://www.banzhuanriji.com/tags/atom/">atom</category>
      
      <category domain="https://www.banzhuanriji.com/tags/rss%E5%9C%B0%E5%9D%80/">rss地址</category>
      
      
      <comments>https://www.banzhuanriji.com/blog/Modify-the-RSS-URL-of-the-HEXO-website/#disqus_thread</comments>
      
    </item>
    
    <item>
      <title>穷鬼养鱼实录——准备篇</title>
      <link>https://www.banzhuanriji.com/life/The-Poor-Mans-Fish-Farming-Story/</link>
      <guid>https://www.banzhuanriji.com/life/The-Poor-Mans-Fish-Farming-Story/</guid>
      <pubDate>Fri, 05 Sep 2025 02:11:08 GMT</pubDate>
      
        
        
          
          
      <description></description>
          
        
      
      
      
      <content:encoded><![CDATA[<blockquote><p>多次出发的养鱼新手决定再次出发。这次决定不折磨小鱼，更不折磨自己。</p></blockquote><h2 id="前言"><a href="#前言" class="headerlink" title="前言"></a>前言</h2><p>所有的养鱼佬都知道。养鱼从来不是 “烧钱大赛”，反而更像一场考验智慧和耐心的小游戏。你不需要动辄上万的专业设备，也不用追求进口的稀有鱼种，只要找对方法，几十块钱也能打造出一个漂亮的桌面小世界。​</p><p>下面分享一次我这次准备的养鱼工作和经历</p><h2 id="准备"><a href="#准备" class="headerlink" title="准备"></a>准备</h2><h3 id="要养的鱼"><a href="#要养的鱼" class="headerlink" title="要养的鱼"></a>要养的鱼</h3><p>首先确定要养什么鱼，是大鱼还是小鱼，是咸水还是淡水，是否是热带鱼。这些内容决定了我们之后要购买和配置的装备。</p><p>我本人喜欢小型鱼，例如灯鱼或者斗鱼之类的淡水小鱼，不容易挂，皮实。</p><p>新手推荐养的鱼基本就是这些个种类：孔雀鱼，斑马鱼，玛丽鱼，斗鱼，草金鱼，鳑鲏，中国斗鱼，鹦鹉鱼。</p><p>这些基本都是小熊鱼类，对氧气的要求不高。但是饲养密度不宜过大，且斗鱼或其他爱好互相攻击的鱼类不可混养。</p><h3 id="鱼缸选择"><a href="#鱼缸选择" class="headerlink" title="鱼缸选择"></a>鱼缸选择</h3><blockquote><p>以下内容仅适合小体系鱼（&lt;5CM）</p></blockquote><h4 id="单缸内鱼儿的最高密度"><a href="#单缸内鱼儿的最高密度" class="headerlink" title="单缸内鱼儿的最高密度"></a>单缸内鱼儿的最高密度</h4><p>按照体积计算</p><ul><li>如果是没那么活泼（游速不快）的小鱼，推荐1-1.5L&#x2F;1条的密度</li><li>如果是活泼的小鱼，推荐1.5-2L&#x2F;1条的密度</li><li>斗鱼（尤其是雄性斗鱼）需要单独饲养</li></ul><p>按照水面积计算</p><ul><li>150cm²&#x2F;1条，例如30cm*20cm的鱼缸，可以养4-5条小鱼</li><li>若是需要肺部呼吸的小鱼（斗鱼，鳑鲏等。不过斗鱼不可混养）则可适当多养</li></ul><h4 id="缸体选择"><a href="#缸体选择" class="headerlink" title="缸体选择"></a>缸体选择</h4><ul><li><strong>不推荐</strong>热弯鱼缸，新手可选亚克力鱼缸（同样不推荐），推荐<strong>超白鱼缸</strong></li><li>不推荐15cm及以下的小缸</li><li>别盲目挑战大缸</li></ul><h4 id="缸体方向"><a href="#缸体方向" class="headerlink" title="缸体方向"></a>缸体方向</h4><p>我认为缸体方向基本是由造景和鱼的习性决定的。如果没啥造景需求，我个人推荐20-35cm的小方缸。但如果你有造景的打算，缸体的方向选择就得好好琢磨了。</p><ul><li><p><strong>方缸</strong>如果你想做立体感强的造景，比如模仿山峰、峡谷，或者用石头、沉木堆叠出层次感，方缸的纵深感会给你更大的发挥空间。它能把焦点集中在造景本身，非常适合单养一条斗鱼，鱼和景能完美融合。</p></li><li><p><strong>长缸</strong>更适合**“线性”的造景**。如果你想打造一条小路，一片草原，或者想看鱼儿在水中来回巡游，长缸无疑是更好的选择。它能更好地表现出前景、中景、后景的层次感，视觉上更开阔，让活泼的小鱼（比如斑马鱼、孔雀鱼）有更长的游动空间。</p></li></ul><p>另外提一句，<strong>不推荐新手挑战高缸（高度超过40cm）</strong>。虽然看着气派，但水压大，对过滤要求高，清理和维护都非常麻烦，成本也会翻倍。</p><p>不管选长缸还是方缸，尽量选“带盖子”的款式。一来能防止小鱼跳缸，二来能减少水分蒸发，保持水质稳定，还能挡住灰尘。</p><p>总结一下，<strong>造景方向决定缸体方向</strong>。想做“山水”就选方缸，想做“平原”就选长缸。我自己用的是一个30cm的小方缸，养了几条灯鱼，配了点水草，桌面空间有限，方缸刚好，看着也清爽。</p><p>个人总结：</p><ul><li>长缸（矩形缸）:自然水景缸，荷兰缸，溪流缸</li><li>方缸（正方形或近似方形的缸）:莫斯缸，珊瑚礁缸（海水缸）</li><li>长缸和方缸都适用: 原生缸，三湖慈鲷缸，亚马逊&#x2F;南美缸</li></ul><h3 id="过滤系统选择"><a href="#过滤系统选择" class="headerlink" title="过滤系统选择"></a>过滤系统选择</h3><h4 id="过滤的本质"><a href="#过滤的本质" class="headerlink" title="过滤的本质"></a>过滤的本质</h4><p>所有的过滤器，无论价格高低，最终目的都是为了三个字：<strong>“养水”</strong>。水好了，鱼才能活得滋润。而养水，最重要的是培养硝化细菌，这些细菌能把鱼的排泄物分解成无毒物质。所以，我们选择过滤器，并不是要选最贵的，而是要选能“养住”硝化细菌的。</p><h4 id="穷鬼过滤方案"><a href="#穷鬼过滤方案" class="headerlink" title="穷鬼过滤方案"></a>穷鬼过滤方案</h4><blockquote><p>穷鬼过滤器的选择，基本原则就是：能用、便宜、占地小。</p></blockquote><p>对于20-35cm的小缸，我强烈推荐两种超高性价比的过滤器：</p><ul><li><p><strong>水妖精（气动生化棉过滤器）</strong></p><ul><li><p><strong>原理</strong>需要搭配一个气泵，通过气泡上浮带动水流，将水吸入过滤棉，同时提供氧气。</p></li><li><p><strong>优点</strong>便宜到爆，一个只要几块钱。同时兼具物理过滤和生化过滤功能，非常适合小缸和斗鱼这类对水流要求不高的鱼。而且它还能打氧，一箭双雕。</p></li><li><p><strong>缺点</strong>体积稍大，置于缸底，个人感觉很丑，影响美观。气泵会有噪音，需要选择静音款。</p></li></ul></li><li><p><strong>瀑布过滤（外挂过滤器）</strong></p><ul><li><p><strong>原理</strong>挂在鱼缸壁上，像个小瀑布一样把水吸入，经过过滤棉和滤材再流回缸内。</p></li><li><p><strong>优点</strong>美观，占用缸内空间小。过滤效果比水妖精略好，水流也比较柔和。</p></li><li><p><strong>缺点</strong>需要定期清理滤棉，成本略高于水妖精。</p></li></ul></li></ul><p>如果你想兼顾美观和过滤效果，选瀑布过滤，小长缸结合造景或石头，可以有溪流效果；如果你只想用最少的钱达到目的，水妖精是你的不二之选。</p><h3 id="灯具选择"><a href="#灯具选择" class="headerlink" title="灯具选择"></a>灯具选择</h3><h4 id="灯具的作用"><a href="#灯具的作用" class="headerlink" title="灯具的作用"></a>灯具的作用</h4><p>很多人觉得灯具就是个照明的，其实不然。它除了让你更好地欣赏鱼儿，还能促进水草生长，有些特殊的灯光甚至能让鱼儿的颜色更鲜艳。</p><h4 id="穷鬼灯具方案"><a href="#穷鬼灯具方案" class="headerlink" title="穷鬼灯具方案"></a>穷鬼灯具方案</h4><blockquote><p>咱们养鱼，主要以观赏和照明为主，不考虑养那种对光照要求极高的“草缸”。所以，灯具的选择可以非常简单粗暴。</p></blockquote><ul><li><p><strong>选择原则</strong>够亮、显色好、便宜。</p></li><li><p><strong>推荐方案</strong></p><ul><li><p><strong>小夹灯</strong>某宝上十几块钱就能买到，夹在鱼缸边上，小巧不占地方。</p></li><li><p><strong>二手LED台灯</strong>如果你家里有闲置的LED台灯，直接拿来用就好。</p></li></ul></li><li><p><strong>注意事项</strong></p><ul><li><p>注意灯光的色温，推荐5000K-8000K的白光，显色效果比较好。</p></li><li><p>注意光照时长，每天开灯8小时左右就够了，避免爆藻（绿藻泛滥）。</p></li></ul></li></ul><h2 id="最后"><a href="#最后" class="headerlink" title="最后"></a>最后</h2><p>接下来就是造景了，这部分也是最能发挥创意的环节。我目前的进度刚到购买鱼缸。以上仅供参考。</p>]]></content:encoded>
      
      
      <category domain="https://www.banzhuanriji.com/categories/life/">生活</category>
      
      
      <category domain="https://www.banzhuanriji.com/tags/%E9%B1%BC/">鱼</category>
      
      <category domain="https://www.banzhuanriji.com/tags/%E6%96%97%E9%B1%BC/">斗鱼</category>
      
      <category domain="https://www.banzhuanriji.com/tags/%E9%B1%BC%E7%BC%B8/">鱼缸</category>
      
      <category domain="https://www.banzhuanriji.com/tags/%E9%80%A0%E6%99%AF/">造景</category>
      
      
      <comments>https://www.banzhuanriji.com/life/The-Poor-Mans-Fish-Farming-Story/#disqus_thread</comments>
      
    </item>
    
    <item>
      <title>服务器自动封禁ip脚本</title>
      <link>https://www.banzhuanriji.com/posts/server-ban-ip-script/</link>
      <guid>https://www.banzhuanriji.com/posts/server-ban-ip-script/</guid>
      <pubDate>Thu, 14 Aug 2025 05:07:00 GMT</pubDate>
      
        
        
          
          
      <description></description>
          
        
      
      
      
      <content:encoded><![CDATA[<pre><code class="hljs plaintext">#!/bin/bashLOG_PATH=&quot;/usr/local/nginx/logs/access.log&quot;BLACKLIST_FILE=&quot;/usr/local/nginx/conf/default.d/deny_nginx.conf&quot;TMP_IP_FILE=&quot;/root/all_suspicious_ips_1000.txt&quot;LINES=5000  # 检查的日志行数THRESHOLD=10    # 攻击次数阈值echo &quot;[*] 正在分析最近 $LINES 行 Nginx 日志...&quot;# 1. 提取攻击关键词的 IPtail -n $LINES &quot;$LOG_PATH&quot; | \grep -Ei &quot;wget|curl|base64|eval|union|select|insert|cmd=|/etc/passwd|../../|shell|php&quot; | \awk &#x27;&#123;print $1&#125;&#x27; | sort | uniq -c | sort -nr &gt; &quot;$TMP_IP_FILE&quot;if [ ! -s &quot;$TMP_IP_FILE&quot; ]; then    echo &quot;[+] 未发现可疑 IP，无需封禁。&quot;    exit 0fi# 2. 创建黑名单文件（如果没有）if [ ! -f &quot;$BLACKLIST_FILE&quot; ]; then    touch &quot;$BLACKLIST_FILE&quot;fiecho &quot;[*] 满足封禁条件（次数 ≥ $THRESHOLD）的 IP：&quot;while read count ip; do    if [ &quot;$count&quot; -ge &quot;$THRESHOLD&quot; ]; then        if grep -q &quot;deny $ip;&quot; &quot;$BLACKLIST_FILE&quot;; then            echo &quot;[-] $ip 已在黑名单中（$count 次），跳过。&quot;        else            echo &quot;[+] 封禁 $ip（攻击次数：$count）&quot;            echo &quot;deny $ip;&quot; &gt;&gt; &quot;$BLACKLIST_FILE&quot;        fi    fidone &lt; &quot;$TMP_IP_FILE&quot;# 4. 检查并重载 Nginxecho &quot;[*] 检查 Nginx 配置...&quot;if /usr/local/nginx/sbin/nginx -t; then    echo &quot;[*] 配置无误，重新加载 Nginx...&quot;    /usr/local/nginx/sbin/nginx -s reload &amp;&amp; echo &quot;[+] Nginx 已重新加载。&quot;else    echo &quot;[!] Nginx 配置错误，请手动检查！&quot;fi# 5. 清理rm -f &quot;$TMP_IP_FILE&quot;</code></pre>]]></content:encoded>
      
      
      <category domain="https://www.banzhuanriji.com/categories/posts/">默认</category>
      
      
      <category domain="https://www.banzhuanriji.com/tags/CDN/">CDN</category>
      
      <category domain="https://www.banzhuanriji.com/tags/%E6%9C%8D%E5%8A%A1%E5%99%A8/">服务器</category>
      
      <category domain="https://www.banzhuanriji.com/tags/SERVER/">SERVER</category>
      
      <category domain="https://www.banzhuanriji.com/tags/%E8%84%9A%E6%9C%AC/">脚本</category>
      
      <category domain="https://www.banzhuanriji.com/tags/LINUX/">LINUX</category>
      
      <category domain="https://www.banzhuanriji.com/tags/%E6%94%BB%E5%87%BB%E9%98%B2%E5%BE%A1/">攻击防御</category>
      
      
      <comments>https://www.banzhuanriji.com/posts/server-ban-ip-script/#disqus_thread</comments>
      
    </item>
    
    <item>
      <title>我使用nextjs写的一个新的博客框架</title>
      <link>https://www.banzhuanriji.com/posts/new-blog-with-nextjs/</link>
      <guid>https://www.banzhuanriji.com/posts/new-blog-with-nextjs/</guid>
      <pubDate>Mon, 11 Aug 2025 00:00:00 GMT</pubDate>
      
        
        
          
          
      <description></description>
          
        
      
      
      
      <content:encoded><![CDATA[<p>我已经厌倦了后端不断地被人扫描和攻击，以及无聊的垃圾评论。导出时发现wordpress的导出插件简直难用至极。</p><p>博客本身是我用作记录的地方。各种框架眼花缭乱，使用成本过高。当前的框架是我用V0生成后自己修改。</p><p>如果你有兴趣的话：</p><pre><code class="hljs plaintext">&#123;  &quot;name&quot;: &quot;blog&quot;,  &quot;version&quot;: &quot;0.1.0&quot;,  &quot;private&quot;: true,  &quot;scripts&quot;: &#123;    &quot;dev&quot;: &quot;next dev&quot;,    &quot;build&quot;: &quot;next build&quot;,    &quot;start&quot;: &quot;next start&quot;,    &quot;lint&quot;: &quot;next lint&quot;  &#125;,  &quot;dependencies&quot;: &#123;    &quot;@hookform/resolvers&quot;: &quot;^3.9.1&quot;,    &quot;@radix-ui/react-accordion&quot;: &quot;1.2.2&quot;,    &quot;@radix-ui/react-alert-dialog&quot;: &quot;1.1.4&quot;,    &quot;@radix-ui/react-aspect-ratio&quot;: &quot;1.1.1&quot;,    &quot;@radix-ui/react-avatar&quot;: &quot;1.1.2&quot;,    &quot;@radix-ui/react-checkbox&quot;: &quot;1.1.3&quot;,    &quot;@radix-ui/react-collapsible&quot;: &quot;1.1.2&quot;,    &quot;@radix-ui/react-context-menu&quot;: &quot;2.2.4&quot;,    &quot;@radix-ui/react-dialog&quot;: &quot;1.1.4&quot;,    &quot;@radix-ui/react-dropdown-menu&quot;: &quot;2.1.4&quot;,    &quot;@radix-ui/react-hover-card&quot;: &quot;1.1.4&quot;,    &quot;@radix-ui/react-label&quot;: &quot;2.1.1&quot;,    &quot;@radix-ui/react-menubar&quot;: &quot;1.1.4&quot;,    &quot;@radix-ui/react-navigation-menu&quot;: &quot;1.2.3&quot;,    &quot;@radix-ui/react-popover&quot;: &quot;1.1.4&quot;,    &quot;@radix-ui/react-progress&quot;: &quot;1.1.1&quot;,    &quot;@radix-ui/react-radio-group&quot;: &quot;1.2.2&quot;,    &quot;@radix-ui/react-scroll-area&quot;: &quot;1.2.2&quot;,    &quot;@radix-ui/react-select&quot;: &quot;2.1.4&quot;,    &quot;@radix-ui/react-separator&quot;: &quot;1.1.1&quot;,    &quot;@radix-ui/react-slider&quot;: &quot;1.2.2&quot;,    &quot;@radix-ui/react-slot&quot;: &quot;1.1.1&quot;,    &quot;@radix-ui/react-switch&quot;: &quot;1.1.2&quot;,    &quot;@radix-ui/react-tabs&quot;: &quot;1.1.2&quot;,    &quot;@radix-ui/react-toast&quot;: &quot;1.2.4&quot;,    &quot;@radix-ui/react-toggle&quot;: &quot;1.1.1&quot;,    &quot;@radix-ui/react-toggle-group&quot;: &quot;1.1.1&quot;,    &quot;@radix-ui/react-tooltip&quot;: &quot;1.1.6&quot;,    &quot;@tailwindcss/typography&quot;: &quot;latest&quot;,    &quot;autoprefixer&quot;: &quot;^10.4.20&quot;,    &quot;class-variance-authority&quot;: &quot;^0.7.1&quot;,    &quot;clsx&quot;: &quot;^2.1.1&quot;,    &quot;cmdk&quot;: &quot;1.0.4&quot;,    &quot;date-fns&quot;: &quot;latest&quot;,    &quot;embla-carousel-react&quot;: &quot;8.5.1&quot;,    &quot;fs&quot;: &quot;latest&quot;,    &quot;gray-matter&quot;: &quot;latest&quot;,    &quot;input-otp&quot;: &quot;1.4.1&quot;,    &quot;lucide-react&quot;: &quot;^0.454.0&quot;,    &quot;mdx&quot;: &quot;latest&quot;,    &quot;next&quot;: &quot;15.2.4&quot;,    &quot;next-mdx-remote&quot;: &quot;latest&quot;,    &quot;next-themes&quot;: &quot;latest&quot;,    &quot;path&quot;: &quot;latest&quot;,    &quot;react&quot;: &quot;^19&quot;,    &quot;react-day-picker&quot;: &quot;9.7.0&quot;,    &quot;react-dom&quot;: &quot;^19&quot;,    &quot;react-hook-form&quot;: &quot;^7.54.1&quot;,    &quot;react-resizable-panels&quot;: &quot;^2.1.7&quot;,    &quot;recharts&quot;: &quot;2.15.0&quot;,    &quot;sonner&quot;: &quot;^1.7.1&quot;,    &quot;tailwind-merge&quot;: &quot;^2.5.5&quot;,    &quot;tailwindcss-animate&quot;: &quot;^1.0.7&quot;,    &quot;vaul&quot;: &quot;^1.1.2&quot;,    &quot;zod&quot;: &quot;^3.24.1&quot;  &#125;,  &quot;devDependencies&quot;: &#123;    &quot;@types/node&quot;: &quot;^22&quot;,    &quot;@types/react&quot;: &quot;^19&quot;,    &quot;@types/react-dom&quot;: &quot;^19&quot;,    &quot;postcss&quot;: &quot;^8&quot;,    &quot;tailwindcss&quot;: &quot;^3.4.17&quot;,    &quot;typescript&quot;: &quot;^5&quot;  &#125;&#125;</code></pre>]]></content:encoded>
      
      
      <category domain="https://www.banzhuanriji.com/categories/posts/">默认</category>
      
      
      <category domain="https://www.banzhuanriji.com/tags/%E6%90%AC%E7%A0%96%E6%97%A5%E8%AE%B0/">搬砖日记</category>
      
      
      <comments>https://www.banzhuanriji.com/posts/new-blog-with-nextjs/#disqus_thread</comments>
      
    </item>
    
    <item>
      <title>部分省份无法访问在线服务的解决方案</title>
      <link>https://www.banzhuanriji.com/posts/fix-network-issues/</link>
      <guid>https://www.banzhuanriji.com/posts/fix-network-issues/</guid>
      <pubDate>Sat, 02 Aug 2025 16:00:00 GMT</pubDate>
      
        
        
          
          
      <description></description>
          
        
      
      
      
      <content:encoded><![CDATA[<h3 id="2025年8月15日更新"><a href="#2025年8月15日更新" class="headerlink" title="2025年8月15日更新"></a>2025年8月15日更新</h3><p>忘记了多域名方案。为了避免出现IP&#x2F;域名污染，或者被基站拉黑的情况，建议使用多域名方案。实际上这种方案成本低，速度快(不考虑备案时间的前提下)，简单，效率高。</p><hr><p>周末被反应<strong>国内</strong>的服务<strong>福建省</strong>出现无法调用的情况，心中大概知道什么问题。这个问题经常出现，尤其是在福建，江苏，河南等省份。</p><p>下面是简单的解决方案，分别为：HTTPS(SSL)，CDN，ipv6，组建集群，客户端指定DNS</p><h3 id="HTTPS"><a href="#HTTPS" class="headerlink" title="HTTPS"></a>HTTPS</h3><p>大部分国内的服务加上证书可以解决很多问题，没错这是真实情况…</p><h3 id="CDN"><a href="#CDN" class="headerlink" title="CDN"></a>CDN</h3><p>最优的解决方案，目前可以查询到上面三个省份均有节点的服务商：</p><ol><li><p>阿里云</p></li><li><p>腾讯云</p></li><li><p>七牛云<br>其它服务商暂时没有查询，具体可以咨询官网客服。如果是前后端分离的项目，推荐购买接口加速的CDN就可以了。至此这个方案基本可以解决所有国内地区的访问难题！</p></li></ol><h3 id="ipv6"><a href="#ipv6" class="headerlink" title="ipv6"></a>ipv6</h3><p>现在国内云厂商是提供免费ipv6的，且绝大部分用户实际上设备和网络状况均支持ipv6，如果可以使用ipv6进行访问也可以解决很大程度的DNS污染问题。</p><h3 id="后端集群"><a href="#后端集群" class="headerlink" title="后端集群"></a>后端集群</h3><p>在访问不到的地区购置服务器，组建集群，亦可解决问题，但是依然需要考虑被重复封禁的问题以及客户端因为地理位置解析不精准或者缓存等原因，并未选择最优线路的方案；亦或者选择的最优路线根本就是访问不到的路线。所以这个方案被排除在最后！（这里只是以解决访问问题为例，而非其他原因）</p><h3 id="客户端方案：指定可访问的DNS"><a href="#客户端方案：指定可访问的DNS" class="headerlink" title="客户端方案：指定可访问的DNS"></a>客户端方案：指定可访问的DNS</h3><p>部分运营商会存在白名单&#x2F;黑名单的情况，当然我们的备案服务和国内IP暂时可以排除黑名单的问题。但是不排除运营商DNS存在污染情况。如果定位不到问题，不妨让客户端多加一点逻辑。首先是检测哪个DNS服务访问最快，以Android端为例，以下用到第三方库版本分别为：OKHttp3(4.13.2)，dnsJava(3.5.2)，下面为具体逻辑代码：</p><pre><code class="hljs plaintext">object DnsLatencyTester &#123;    // DNS服务器列表    private val dnsServers = listOf(        //并非越多越好，下面只是国内流行且高可用的DNS地址，另外还有360的DNS地址，具体情况请按需添加        &quot;223.5.5.5&quot;,    // 阿里公共DNS        &quot;223.6.6.6&quot;,    // 阿里公共DNS        &quot;119.29.29.29&quot;, // 腾讯公共DNS        &quot;119.28.28.28&quot;, // 腾讯公共DNS        &quot;180.76.76.76&quot;, // 百度公共DNS        &quot;114.114.114.114&quot;, // 114DNS纯净版        &quot;114.114.115.115&quot;, // 114DNS纯净版    )    // 测试API地址，协商你们的任意可访问服务地址    private const val TEST_URL = &quot;&quot;    private const val TIMEOUT_MS = 5000L    /**     * 测试所有DNS服务器的延迟并返回最快的一个     */    suspend fun findFastestDns(context: Context): String? = withContext(Dispatchers.IO) &#123;        // 为每个DNS创建异步测试任务        val deferredResults = dnsServers.map &#123; dns -&gt;            async &#123; testDnsLatency(dns) to dns &#125;        &#125;        // 等待所有测试完成并过滤掉超时的结果        val results = deferredResults.awaitAll()            .filter &#123; it.first != -1L &#125;            .sortedBy &#123; it.first &#125;        // 返回延迟最低的DNS        results.firstOrNull()?.second    &#125;    /**     * 测试单个DNS服务器的延迟     * @return 延迟时间(毫秒)，-1表示超时或失败     */    private suspend fun testDnsLatency(dnsServer: String): Long = withContext(Dispatchers.IO) &#123;        return@withContext try &#123;            // 创建自定义DNS            val customDns = object : Dns &#123;                override fun lookup(hostname: String): List&lt;InetAddress&gt; &#123;                    try &#123;                        // 使用指定DNS服务器解析域名                        val addresses = InetAddress.getAllByName(hostname)                        return addresses.toList()                    &#125; catch (e: UnknownHostException) &#123;                        // 如果解析失败，尝试直接使用IP连接                        return listOf(InetAddress.getByName(dnsServer))                    &#125;                &#125;            &#125;            // 配置OkHttpClient            val client = OkHttpClient.Builder()                .dns(customDns)                .connectTimeout(TIMEOUT_MS, TimeUnit.MILLISECONDS)                .readTimeout(TIMEOUT_MS, TimeUnit.MILLISECONDS)                .writeTimeout(TIMEOUT_MS, TimeUnit.MILLISECONDS)                .build()            // 创建请求            val request = Request.Builder()                .url(TEST_URL)                .head() // 使用HEAD请求减少数据传输                .build()            // 记录开始时间            val startTime = System.currentTimeMillis()            // 发送请求            client.newCall(request).execute().close()            // 计算延迟时间            System.currentTimeMillis() - startTime        &#125; catch (e: Exception) &#123;            // 发生异常时返回-1            -1L        &#125;    &#125;&#125;</code></pre><p>可以在闪屏页调用(注意实际用户体验)：</p><pre><code class="hljs plaintext">lifecycleScope.launch &#123;    val fastestDns = DnsLatencyTester.findFastestDns(binding.imageViewcontent.context)    if (fastestDns != null) &#123;        Log.i(&quot;DNS_TEST&quot;, &quot;最快的DNS服务器: $fastestDns&quot;)        SPUtils.getInstance().put(&quot;fastestDns&quot;, fastestDns)        getInApp()    &#125; else &#123;        Log.e(&quot;DNS_TEST&quot;, &quot;所有DNS服务器测试失败&quot;)        SPUtils.getInstance().put(&quot;fastestDns&quot;, &quot;&quot;)        getInApp()    &#125;&#125;</code></pre><p>OKHttp3的使用示例：</p><pre><code class="hljs plaintext">var customDns: Dns = Dns.SYSTEM // 默认使用系统 DNSval fastestDns = SPUtils.getInstance().getString(&quot;fastestDns&quot;)if (fastestDns.isNotEmpty()) &#123;    try &#123;        customDns = CustomDns(fastestDns)    &#125; catch (e: UnknownHostException) &#123;        e.printStackTrace()        customDns = Dns.SYSTEM    &#125;&#125;val okHttpClientBuilder = OkHttpClient().newBuilder().apply &#123;    connectTimeout(CONNECT_TIMEOUT, TimeUnit.SECONDS)    readTimeout(READ_TIMEOUT, TimeUnit.SECONDS)    writeTimeout(WRITE_TIMEOUT, TimeUnit.SECONDS)    dns(customDns)    addInterceptor(MInterceptor())    addInterceptor(DataEncryptInterceptor())</code></pre><p>中间存在的<code>CustomDNS</code>代码如下：</p><pre><code class="hljs plaintext">class CustomDns(dnsServer: String) : Dns &#123;    private var resolver: SimpleResolver? = null    init &#123;        try &#123;            // 使用自定义的DNS服务器地址            this.resolver = SimpleResolver(dnsServer)        &#125; catch (e: UnknownHostException) &#123;            throw IllegalArgumentException(&quot;Invalid DNS server address: $dnsServer&quot;, e)        &#125;    &#125;    @Throws(UnknownHostException::class)    override fun lookup(hostname: String): List&lt;InetAddress&gt; &#123;        try &#123;            // 使用dnsjava进行解析            val name = Name(&quot;$hostname.&quot;)            val record: Record = Record.newRecord(name, Type.A, DClass.IN)            val query = Message.newQuery(record)            val response = resolver!!.send(query)            val addresses: MutableList&lt;InetAddress&gt; = ArrayList()            if (response.rcode == Rcode.NOERROR) &#123;                val records: Array&lt;Record&gt; = response.getSectionArray(Section.ANSWER)                for (r in records) &#123;                    if (r is ARecord) &#123;                        val a = r as ARecord                        addresses.add(a.address)                    &#125;                &#125;            &#125;            if (addresses.isEmpty()) &#123;                throw UnknownHostException(&quot;No address found for $hostname&quot;)            &#125;            return addresses        &#125; catch (e: Exception) &#123;            // 捕获所有dnsjava的异常，并转换为UnknownHostException            throw UnknownHostException(&quot;DNS lookup failed for &quot; + hostname + &quot;: &quot; + e.message)        &#125;    &#125;&#125;</code></pre><hr><p>以上就是我目前可以想到的全部解决方案，综合之后我个人建议的排序为：</p><p>CDN&gt;DNS&gt;集群，(ipv6以及HTTPS为默认配置😄)</p>]]></content:encoded>
      
      
      <category domain="https://www.banzhuanriji.com/categories/posts/">默认</category>
      
      
      <category domain="https://www.banzhuanriji.com/tags/%E5%BC%80%E5%8F%91/">开发</category>
      
      <category domain="https://www.banzhuanriji.com/tags/CDN/">CDN</category>
      
      <category domain="https://www.banzhuanriji.com/tags/APP/">APP</category>
      
      <category domain="https://www.banzhuanriji.com/tags/DNS/">DNS</category>
      
      <category domain="https://www.banzhuanriji.com/tags/dnsjava/">dnsjava</category>
      
      <category domain="https://www.banzhuanriji.com/tags/ipv6/">ipv6</category>
      
      <category domain="https://www.banzhuanriji.com/tags/%E5%8A%A0%E9%80%9F/">加速</category>
      
      <category domain="https://www.banzhuanriji.com/tags/%E5%8F%AF%E7%94%A8%E6%80%A7/">可用性</category>
      
      <category domain="https://www.banzhuanriji.com/tags/%E9%9B%86%E7%BE%A4/">集群</category>
      
      
      <comments>https://www.banzhuanriji.com/posts/fix-network-issues/#disqus_thread</comments>
      
    </item>
    
    <item>
      <title>Android 12的启动白屏简单适配方案</title>
      <link>https://www.banzhuanriji.com/posts/android-12-splashscreen/</link>
      <guid>https://www.banzhuanriji.com/posts/android-12-splashscreen/</guid>
      <pubDate>Thu, 24 Jul 2025 16:00:00 GMT</pubDate>
      
        
        
          
          
      <description></description>
          
        
      
      
      
      <content:encoded><![CDATA[<h3 id="TL-DR"><a href="#TL-DR" class="headerlink" title="TL;DR"></a>TL;DR</h3><ol><li><p>导入<code>implementation &#39;androidx.core:core-splashscreen:1.0.1&#39;</code></p></li><li><p>继承主题 <code>Theme.ScreenSplash</code>，继承的主题可以设置前景和背景以及时间（最大500），类似icon</p></li><li><p>使用主题</p></li></ol><blockquote><p>在国产大厂的APP上，每一个Feature，每一个模块，每一个按钮，甚至每一个不可交互的地方，它都有出生的意义：广告位。</p><p>闪屏页的战略地位不言而喻😄</p></blockquote><p>在 Android 12（SDK 31）之前，应用的启动画面（Splash Screen）一直是个令人头疼的问题。开发者们不得不绞尽脑汁，通过自定义 Activity 主题、设置 <code>windowBackground</code> 或者创建一个独立的 Splash Activity 来模拟启动效果。</p><p>不仅需要作为天然的广告位，这个一闪而过页面承载太多东西了。你的所有需要注册的内容，所有内容的完整性检查，服务器判断，DNS解析。当然还有广告的加载以及用户的手有没有抖动…</p><p>虽然目前看来体验不尽人意，适配寥寥无几，不过当初Android12似乎真的想改变这些（当然不止这些，更多的可能是体验统一的问题），所以带来一个原生，更高效的解决方案。抛开这些开发决定不了的内容，下面我简单记录一下自己适配这个SplashScreen，当然这也是我第一次使用这个特性。</p><h2 id="核心概念与-Theme-SplashScreen-属性详解"><a href="#核心概念与-Theme-SplashScreen-属性详解" class="headerlink" title="核心概念与 Theme.SplashScreen 属性详解"></a>核心概念与 <code>Theme.SplashScreen</code> 属性详解</h2><p>Android 12 的启动画面不再是应用自身绘制的 View，而是由<strong>系统根据主题配置生成并管理</strong>。你的启动 Activity 会首先应用一个继承自 <code>Theme.SplashScreen</code> 的主题，系统会根据该主题的属性来渲染启动画面，并在应用准备就绪后平滑过渡到应用真正的界面。下面是我在网上找到的VerseAPP的动画闪屏页（你也可以下载查看teelgram的动画效果）：</p><p><img src="/img/Verse.gif"></p><p>以下是 <code>Theme.SplashScreen</code> 主题及其关键属性的详细解析：</p><h4 id="1-父主题：Theme-SplashScreen"><a href="#1-父主题：Theme-SplashScreen" class="headerlink" title="1. 父主题：Theme.SplashScreen"></a>1. 父主题：<code>Theme.SplashScreen</code></h4><ul><li><strong>作用：</strong> 这是 Android 12+ 系统提供的启动画面基础主题。你的自定义启动主题<strong>必须继承</strong>这个主题，才能享受到系统级别的启动画面管理和动画效果。</li></ul><h4 id="2-背景属性：windowSplashScreenBackground"><a href="#2-背景属性：windowSplashScreenBackground" class="headerlink" title="2. 背景属性：windowSplashScreenBackground"></a>2. 背景属性：<code>windowSplashScreenBackground</code></h4><p><strong>类型：</strong> Drawable 引用（<code>@drawable/</code> 或 <code>@color/</code>）</p><p><strong>作用：</strong> 定义启动画面的背景。这是替换传统”白屏”的关键。</p><p><strong>自定义方案：</strong></p><ul><li><p><strong>纯色：</strong> 最简单也是最高效的方式，直接引用一个颜色资源，如 <code>&lt;item name=&quot;windowSplashScreenBackground&quot;&gt;@color/your_brand_color&lt;/item&gt;</code>。</p></li><li><p><strong>渐变色：</strong> 创建一个 <code>shape</code> Drawable，并在其中定义 <code>gradient</code> 标签，实现平滑的颜色过渡。</p></li><li><p><strong>图片作为背景（慎用，推荐 LayerList）：</strong> 虽然可以直接引用一张图片 (<code>@drawable/your_image</code>)，但通常不推荐直接将一张大图作为背景，因为它可能导致屏幕适配问题、文件尺寸增大和内存开销。</p></li><li><p><strong>LayerList 组合背景</strong></p></li></ul><h4 id="3-图标属性：windowSplashScreenAnimatedIcon"><a href="#3-图标属性：windowSplashScreenAnimatedIcon" class="headerlink" title="3. 图标属性：windowSplashScreenAnimatedIcon"></a>3. 图标属性：<code>windowSplashScreenAnimatedIcon</code></h4><ul><li><p><strong>类型：</strong> Drawable 引用（<code>@drawable/</code> 或 <code>@mipmap/</code>）</p></li><li><p><strong>作用：</strong> 定义在启动画面中心显示的图标或动画。</p></li><li><p><strong>可选动画类型：</strong></p></li><li><p><strong>Animated Vector Drawable (AVD)</strong>：</p><ul><li><p><strong>格式：</strong> XML (<code>.xml</code>)，定义了矢量图的动画。</p><ul><li><p><strong>优点：</strong> <strong>官方推荐</strong>，文件小巧，可缩放不失真，性能优秀，可以实现复杂的矢量动画。</p></li><li><p><strong>创建：</strong> 复杂动画通常需要手动编写 XML 或借助工具。</p></li></ul></li><li><p><strong>Animation Drawable：</strong></p></li><li><p><strong>格式：</strong> XML (<code>.xml</code>)，引用一系列帧图片。</p><ul><li><p><strong>优点：</strong> 简单易懂，适合简单的帧动画。</p></li><li><p><strong>缺点：</strong> 每一帧都是一张图片，文件体积大，内存占用高，缩放可能失真。适用于帧数非常少且简单的动画。</p></li></ul></li><li><p><strong>自适应图标 (Adaptive Icon)：</strong></p></li><li><p><strong>格式：</strong> 通常是矢量图，由前景层 (<code>@mipmap/ic_launcher_foreground</code>) 和背景层 (<code>@mipmap/ic_launcher_background</code>) 组成。</p><ul><li><p><strong>优点：</strong> 系统原生支持，自动适应不同形状的图标蒙版，无需额外动画即可平滑缩放。</p></li><li><p><strong>最常用方案：</strong> 如果没有自定义动画需求，直接使用你的自适应图标前景层即可，例如：<code>&lt;item name=&quot;windowSplashScreenAnimatedIcon&quot;&gt;@mipmap/ic_launcher_foreground&lt;/item&gt;</code>。</p></li></ul></li></ul></li></ul><h4 id="4-动画持续时间：windowSplashScreenAnimationDuration"><a href="#4-动画持续时间：windowSplashScreenAnimationDuration" class="headerlink" title="4. 动画持续时间：windowSplashScreenAnimationDuration"></a>4. 动画持续时间：<code>windowSplashScreenAnimationDuration</code></h4><ul><li><p><strong>类型：</strong> 整数（毫秒）</p></li><li><p><strong>作用：</strong> 定义 <code>windowSplashScreenAnimatedIcon</code> 中图标动画的持续时间。系统会在这个时间结束后开始淡出启动画面。</p></li><li><p><strong>注意：</strong> 你的动画设计应尽量在这个持续时间内完成，以避免动画被截断或过早结束。建议值为 200ms 到 1000ms 之间，保持快速且流畅。</p></li></ul><h4 id="5-品牌-Logo：windowSplashScreenBrandDrawable-可选"><a href="#5-品牌-Logo：windowSplashScreenBrandDrawable-可选" class="headerlink" title="5. 品牌 Logo：windowSplashScreenBrandDrawable (可选)"></a>5. 品牌 Logo：<code>windowSplashScreenBrandDrawable</code> (可选)</h4><ul><li><p><strong>类型：</strong> Drawable 引用</p></li><li><p><strong>作用：</strong> 在启动画面底部显示一个可选的品牌 Logo。</p></li><li><p><strong>位置：</strong> 这个 Logo 会固定显示在启动画面的底部，不会随图标一起动画。</p></li><li><p><strong>用处：</strong> 适合展示公司或产品的额外品牌标识。</p></li></ul><h4 id="6-核心属性：postSplashScreenTheme"><a href="#6-核心属性：postSplashScreenTheme" class="headerlink" title="6. 核心属性：postSplashScreenTheme"></a>6. 核心属性：<code>postSplashScreenTheme</code></h4><ul><li><p><strong>类型：</strong> Style 引用（<code>@style/</code>）</p></li><li><p><strong>作用：</strong> **这是最重要的属性！**它指定了启动画面结束后，Activity 应该切换到哪个主题来渲染你的应用界面。</p></li><li><p><strong>重要性：</strong> 如果没有正确设置这个属性，你的 Activity 在启动画面消失后，可能会显示错误的样式，甚至出现界面空白或闪烁。务必将其指向你应用正常运行时所使用的主要主题。</p></li></ul><hr><h3 id="二、应用开发实践：从零开始配置"><a href="#二、应用开发实践：从零开始配置" class="headerlink" title="二、应用开发实践：从零开始配置"></a>二、应用开发实践：从零开始配置</h3><p>让我们通过一个完整的示例，一步步将 Android 12 的启动画面集成到你的应用中。</p><h4 id="1-确保你的项目兼容-Android-12-SDK-31"><a href="#1-确保你的项目兼容-Android-12-SDK-31" class="headerlink" title="1: 确保你的项目兼容 Android 12 (SDK 31+)"></a>1: 确保你的项目兼容 Android 12 (SDK 31+)</h4><p>首先，在你的 <code>build.gradle (Module: app)</code> 文件中，确保 <code>compileSdk</code> 和 <code>targetSdk</code> 至少是 31：</p><pre><code class="hljs plaintext">android &#123;    compileSdk 31 // 或更高版本    defaultConfig &#123;        targetSdk 31 // 或更高版本        // ...    &#125;    // ...&#125;</code></pre><h4 id="2-添加-Splash-Screen-库依赖"><a href="#2-添加-Splash-Screen-库依赖" class="headerlink" title="2. 添加 Splash Screen 库依赖"></a>2. 添加 Splash Screen 库依赖</h4><pre><code class="hljs plaintext">dependencies &#123;    // ... 其他依赖    implementation &#x27;androidx.core:core-splashscreen:1.0.1&#x27; &#125;</code></pre><h4 id="3-定义你的应用主题"><a href="#3-定义你的应用主题" class="headerlink" title="3. 定义你的应用主题"></a>3. 定义你的应用主题</h4><p>在 <code>res/values/themes.xml</code> (和 <code>res/values-night/themes.xml</code>，用于深色模式) 中定义应用主主题。这将是 <code>postSplashScreenTheme</code> 指向的主题。</p><pre><code class="hljs plaintext">&lt;resources&gt;    &lt;style name=&quot;Theme.MyAwesomeApp&quot; parent=&quot;Theme.MaterialComponents.DayNight.NoActionBar&quot;&gt;        &lt;item name=&quot;colorPrimary&quot;&gt;#6200EE&lt;/item&gt;        &lt;item name=&quot;colorPrimaryVariant&quot;&gt;#3700B3&lt;/item&gt;        &lt;item name=&quot;colorOnPrimary&quot;&gt;#FFFFFF&lt;/item&gt;        &lt;item name=&quot;colorSecondary&quot;&gt;#03DAC6&lt;/item&gt;        &lt;item name=&quot;colorSecondaryVariant&quot;&gt;#018786&lt;/item&gt;        &lt;item name=&quot;colorOnSecondary&quot;&gt;#000000&lt;/item&gt;        &lt;item name=&quot;android:statusBarColor&quot;&gt;?attr/colorPrimaryVariant&lt;/item&gt;        &lt;item name=&quot;android:navigationBarColor&quot;&gt;@color/black&lt;/item&gt;        &lt;item name=&quot;android:windowBackground&quot;&gt;@color/white&lt;/item&gt;        &lt;/style&gt;&lt;/resources&gt;&lt;resources&gt;    &lt;style name=&quot;Theme.MyAwesomeApp&quot; parent=&quot;Theme.MaterialComponents.DayNight.NoActionBar&quot;&gt;        &lt;item name=&quot;colorPrimary&quot;&gt;#BB86FC&lt;/item&gt;        &lt;item name=&quot;colorPrimaryVariant&quot;&gt;#3700B3&lt;/item&gt;        &lt;item name=&quot;colorOnPrimary&quot;&gt;#000000&lt;/item&gt;        &lt;item name=&quot;colorSecondary&quot;&gt;#03DAC6&lt;/item&gt;        &lt;item name=&quot;colorSecondaryVariant&quot;&gt;#03DAC6&lt;/item&gt;        &lt;item name=&quot;colorOnSecondary&quot;&gt;#000000&lt;/item&gt;        &lt;item name=&quot;android:statusBarColor&quot;&gt;?attr/colorPrimaryVariant&lt;/item&gt;        &lt;item name=&quot;android:navigationBarColor&quot;&gt;@color/black&lt;/item&gt;        &lt;item name=&quot;android:windowBackground&quot;&gt;@color/dark_gray&lt;/item&gt;    &lt;/style&gt;&lt;/resources&gt;</code></pre><h4 id="4-准备启动画面背景（渐变色和Logo）"><a href="#4-准备启动画面背景（渐变色和Logo）" class="headerlink" title="4. 准备启动画面背景（渐变色和Logo）"></a>4. 准备启动画面背景（渐变色和Logo）</h4><p>我们创建一个 <code>LayerList</code> 来实现一个包含渐变背景和底部品牌Logo的启动画面。（实际上不推荐 用渐变色，我更推荐telegram或者X的方案，纯色背景+AVD&#x2F;静态LOGO）</p><pre><code class="hljs plaintext">&lt;?xml version=&quot;1.0&quot; encoding=&quot;utf-8&quot;?&gt;&lt;layer-list xmlns:android=&quot;[http://schemas.android.com/apk/res/android](http://schemas.android.com/apk/res/android)&quot;&gt;    &lt;item&gt;        &lt;shape android:shape=&quot;rectangle&quot;&gt;            &lt;gradient                android:angle=&quot;270&quot;                android:startColor=&quot;#6200EE&quot;                android:endColor=&quot;#03DAC6&quot;                android:type=&quot;linear&quot; /&gt;        &lt;/shape&gt;    &lt;/item&gt;    &lt;item android:bottom=&quot;32dp&quot;&gt; &lt;bitmap            android:src=&quot;ic_my_company_logo&quot;            android:gravity=&quot;bottom&#x27;center_horizontal&quot; /&gt;    &lt;/item&gt;&lt;/layer-list&gt;</code></pre><p><strong>注意：</strong></p><ul><li><p><code>@drawable/ic_my_company_logo</code> 应该是你的 Logo 图片（Vector Drawable 或 Bitmap）。</p></li><li><p><code>android:bottom</code> 可以调整 Logo 的位置。</p></li><li><p><code>android:gravity=&quot;bottom&#39;center_horizontal&quot;</code> 将 Logo 放置在底部中央。</p></li></ul><h4 id="5-准备启动画面动画图标（AVD-示例）"><a href="#5-准备启动画面动画图标（AVD-示例）" class="headerlink" title="5. 准备启动画面动画图标（AVD 示例）"></a>5. 准备启动画面动画图标（AVD 示例）</h4><p>假设你已经有一个名为 <code>ic_animated_logo.xml</code> 的 Animated Vector Drawable。</p><pre><code class="hljs plaintext">&lt;?xml version=&quot;1.0&quot; encoding=&quot;utf-8&quot;?&gt;&lt;animated-vector xmlns:android=&quot;[http://schemas.android.com/apk/res/android](http://schemas.android.com/apk/res/android)&quot;    android:drawable=&quot;@drawable/ic_static_logo&quot;&gt; &lt;target android:name=&quot;group_name_in_static_logo&quot;&gt; &lt;propertyValuesHolder            android:propertyName=&quot;rotation&quot;            android:valueFrom=&quot;0&quot;            android:valueTo=&quot;360&quot;            android:valueType=&quot;floatType&quot;            android:duration=&quot;1000&quot;            android:interpolator=&quot;@android:interpolator/fast_out_slow_in&quot; /&gt;    &lt;/target&gt;&lt;/animated-vector&gt;</code></pre><p><strong>如果不想使用自定义动画，直接使用自适应图标前景部分：<code>@mipmap/ic_launcher_foreground</code>。</strong></p><h4 id="6-定义启动画面主题"><a href="#6-定义启动画面主题" class="headerlink" title="6. 定义启动画面主题"></a>6. 定义启动画面主题</h4><p>现在，在 <code>res/values/themes.xml</code> (和 <code>res/values-night/themes.xml</code>) 中定义你的启动主题，并引用我们准备好的 Drawable。</p><pre><code class="hljs plaintext">&lt;resources&gt;    &lt;style name=&quot;Theme.MyAwesomeApp.SplashScreen&quot; parent=&quot;Theme.SplashScreen&quot;&gt;        &lt;item name=&quot;windowSplashScreenBackground&quot;&gt;@drawable/splash_layer_bg&lt;/item&gt;        &lt;item name=&quot;windowSplashScreenAnimatedIcon&quot;&gt;@drawable/ic_animated_logo&lt;/item&gt;        &lt;item name=&quot;windowSplashScreenAnimationDuration&quot;&gt;1000&lt;/item&gt; &lt;item name=&quot;postSplashScreenTheme&quot;&gt;@style/Theme.MyAwesomeApp&lt;/item&gt;    &lt;/style&gt;&lt;/resources&gt;&lt;resources&gt;    &lt;style name=&quot;Theme.MyAwesomeApp.SplashScreen&quot; parent=&quot;Theme.SplashScreen&quot;&gt;        &lt;item name=&quot;windowSplashScreenBackground&quot;&gt;@color/dark_splash_bg_color&lt;/item&gt;        &lt;item name=&quot;windowSplashScreenAnimatedIcon&quot;&gt;@drawable/ic_animated_logo_dark&lt;/item&gt;        &lt;item name=&quot;windowSplashScreenAnimationDuration&quot;&gt;1000&lt;/item&gt;        &lt;item name=&quot;postSplashScreenTheme&quot;&gt;@style/Theme.MyAwesomeApp&lt;/item&gt;    &lt;/style&gt;&lt;/resources&gt;</code></pre><h4 id="7-在-AndroidManifest-xml-中应用主题"><a href="#7-在-AndroidManifest-xml-中应用主题" class="headerlink" title="7. 在 AndroidManifest.xml 中应用主题"></a>7. 在 <code>AndroidManifest.xml</code> 中应用主题</h4><p>在 <code>AndroidManifest.xml</code> 中，将 <code>Theme.MyAwesomeApp.SplashScreen</code> 主题应用到启动 Activity（通常是 <code>MainActivity</code>，也有可能是<code>SplashActivity</code>之类的）。</p><pre><code class="hljs plaintext">&lt;?xml version=&quot;1.0&quot; encoding=&quot;utf-8&quot;?&gt;&lt;manifest xmlns:android=&quot;[http://schemas.android.com/apk/res/android](http://schemas.android.com/apk/res/android)&quot;    xmlns:tools=&quot;[http://schemas.android.com/tools](http://schemas.android.com/tools)&quot;&gt;    &lt;application        android:allowBackup=&quot;true&quot;        android:icon=&quot;@mipmap/ic_launcher&quot;        android:label=&quot;@string/app_name&quot;        android:roundIcon=&quot;@mipmap/ic_launcher_round&quot;        android:supportsRtl=&quot;true&quot;        android:theme=&quot;@style/Theme.MyAwesomeApp&quot;&gt; &lt;activity            android:name=&quot;.MainActivity&quot;            android:exported=&quot;true&quot;            android:theme=&quot;@style/Theme.MyAwesomeApp.SplashScreen&quot;&gt; &lt;intent-filter&gt;                &lt;action android:name=&quot;android.intent.action.MAIN&quot; /&gt;                &lt;category android:name=&quot;android.intent.category.LAUNCHER&quot; /&gt;            &lt;/intent-filter&gt;        &lt;/activity&gt;        &lt;activity android:name=&quot;.OtherActivity&quot;            android:theme=&quot;@style/Theme.MyAwesomeApp&quot;/&gt;    &lt;/application&gt;&lt;/manifest&gt;</code></pre><h4 id="8-在-MainActivity-中安装启动画面"><a href="#8-在-MainActivity-中安装启动画面" class="headerlink" title="8. 在 MainActivity 中安装启动画面"></a>8. 在 <code>MainActivity</code> 中安装启动画面</h4><p>最后，在启动 Activity 的 <code>onCreate()</code> 方法中调用 <code>installSplashScreen()</code>。<strong>这是启用系统启动画面 API 的核心步骤。</strong></p><pre><code class="hljs plaintext">// MainActivity.ktimport android.os.Bundleimport androidx.appcompat.app.AppCompatActivityimport androidx.core.splashscreen.SplashScreen.Companion.installSplashScreenimport kotlinx.coroutines.Dispatchersimport kotlinx.coroutines.delayimport kotlinx.coroutines.withContextimport androidx.lifecycle.lifecycleScopeimport kotlinx.coroutines.launchclass MainActivity : AppCompatActivity() &#123;    private var isContentReady = false // 用于控制启动画面何时消失    override fun onCreate(savedInstanceState: Bundle?) &#123;        // 1. 调用 installSplashScreen() 必须在 super.onCreate() 之前        val splashScreen = installSplashScreen()        super.onCreate(savedInstanceState)        setContentView(R.layout.activity_main)        // 2. 延迟启动画面消失，直到内容准备就绪        splashScreen.setKeepOnScreenCondition &#123;            !isContentReady // 当 isContentReady 为 false 时，启动画面会一直显示        &#125;        // 3. 模拟数据加载或应用初始化        loadAppContent()    &#125;    private fun loadAppContent() &#123;        lifecycleScope.launch(Dispatchers.IO) &#123;            // 模拟耗时的初始化操作，例如网络请求、数据库加载等            delay(3000) // 模拟 3 秒的加载时间            // 数据加载完成            isContentReady = true            withContext(Dispatchers.Main) &#123;                // 在这里可以执行数据加载完成后的 UI 更新或跳转操作                // 例如：navigateToHome()            &#125;        &#125;    &#125;&#125;</code></pre><p><strong>Java 示例(AI转换的代码，应该咩问题)：</strong></p><pre><code class="hljs plaintext">// MainActivity.javaimport android.os.Bundle;import androidx.appcompat.app.AppCompatActivity;import androidx.core.splashscreen.SplashScreen;import android.os.Handler;import android.os.Looper;import java.util.concurrent.Executors;import java.util.concurrent.ScheduledExecutorService;import java.util.concurrent.TimeUnit;public class MainActivity extends AppCompatActivity &#123;    private boolean isContentReady = false; // 用于控制启动画面何时消失    @Override    protected void onCreate(Bundle savedInstanceState) &#123;        // 1. 调用 installSplashScreen() 必须在 super.onCreate() 之前        SplashScreen splashScreen = SplashScreen.Companion.installSplashScreen(this);        super.onCreate(savedInstanceState);        setContentView(R.layout.activity_main);        // 2. 延迟启动画面消失，直到内容准备就绪        splashScreen.setKeepOnScreenCondition(() -&gt; !isContentReady);        // 3. 模拟数据加载或应用初始化        loadAppContent();    &#125;    private void loadAppContent() &#123;        ScheduledExecutorService executor = Executors.newSingleThreadScheduledExecutor();        executor.schedule(() -&gt; &#123;            // 模拟耗时的初始化操作            isContentReady = true; // 数据加载完成            new Handler(Looper.getMainLooper()).post(() -&gt; &#123;                // 在主线程执行数据加载完成后的 UI 更新或跳转操作            &#125;);        &#125;, 3, TimeUnit.SECONDS); // 模拟 3 秒的加载时间    &#125;&#125;</code></pre>]]></content:encoded>
      
      
      <category domain="https://www.banzhuanriji.com/categories/posts/">默认</category>
      
      
      <category domain="https://www.banzhuanriji.com/tags/Android-12/">Android 12</category>
      
      <category domain="https://www.banzhuanriji.com/tags/Android%E5%BC%80%E5%8F%91/">Android开发</category>
      
      <category domain="https://www.banzhuanriji.com/tags/Animated-Vector-Drawable/">Animated Vector Drawable</category>
      
      <category domain="https://www.banzhuanriji.com/tags/App%E5%BC%80%E5%8F%91/">App开发</category>
      
      <category domain="https://www.banzhuanriji.com/tags/java/">java</category>
      
      <category domain="https://www.banzhuanriji.com/tags/Kotlin/">Kotlin</category>
      
      <category domain="https://www.banzhuanriji.com/tags/LayerList/">LayerList</category>
      
      <category domain="https://www.banzhuanriji.com/tags/SplashScreen/">SplashScreen</category>
      
      <category domain="https://www.banzhuanriji.com/tags/UI-UX/">UI/UX</category>
      
      <category domain="https://www.banzhuanriji.com/tags/%E4%B8%BB%E9%A2%98%E9%85%8D%E7%BD%AE/">主题配置</category>
      
      <category domain="https://www.banzhuanriji.com/tags/%E5%90%AF%E5%8A%A8%E7%94%BB%E9%9D%A2/">启动画面</category>
      
      <category domain="https://www.banzhuanriji.com/tags/%E5%B9%BF%E5%91%8A%E4%BD%8D/">广告位</category>
      
      <category domain="https://www.banzhuanriji.com/tags/%E5%BC%80%E5%8F%91/">开发</category>
      
      <category domain="https://www.banzhuanriji.com/tags/%E8%87%AA%E9%80%82%E5%BA%94%E5%9B%BE%E6%A0%87/">自适应图标</category>
      
      
      <comments>https://www.banzhuanriji.com/posts/android-12-splashscreen/#disqus_thread</comments>
      
    </item>
    
    <item>
      <title>Wordpress发不出去通知邮件的解决方案</title>
      <link>https://www.banzhuanriji.com/posts/wordpress-mail-sender/</link>
      <guid>https://www.banzhuanriji.com/posts/wordpress-mail-sender/</guid>
      <pubDate>Sun, 25 May 2025 16:00:00 GMT</pubDate>
      
        
        
          
          
      <description></description>
          
        
      
      
      
      <content:encoded><![CDATA[<p>TL;DR</p><p>wordpress国内服务器访问不到发件服务器，所以经过综合考虑之后，选择采用第三方插件和第三方邮箱通过SMTP发送邮件。</p><hr><h2 id="起因"><a href="#起因" class="headerlink" title="起因"></a>起因</h2><p>我转移到wordpress之后，之前搭建在纽约的服务器上，邮件是没什么问题的。评论和被评论都会收到邮件通知。但是正常转移到杭州的服务器上之后发现收不到任何邮件了，包括不限于修改密码，邮箱，用户操作，评论和被评论的通知邮件。</p><h2 id="分析"><a href="#分析" class="headerlink" title="分析"></a>分析</h2><p>❌期初我以为是因为服务器商封禁了邮件发送端口。但是我在防火墙打开了SMTP端口(25，465&nbsp;，587)的出站之后发现还是不行。</p><p>❌那就要考虑邮件是不是被服务商拦截的，询问客服之后告知不是，同时被告知如果发送垃圾邮件会被封禁。</p><p>✔️最后我检查之前的邮件，发现是来自wp.com的邮件。我并未安装jetpack，但是邮件依旧不是来自我的站点而是通过wordpress的服务器发送（猜测是需要访问一下WP的接口而非使用PHP的<code>mail()</code>方法发送的邮件）。</p><h2 id="尝试解决"><a href="#尝试解决" class="headerlink" title="尝试解决"></a>尝试解决</h2><p>问题明确之后就很容易解决了。下面是几个直接方案：</p><ol><li>通过一些方案访问WP.COM</li></ol><ul><li><p>代理</p><ul><li><p>HOSTS</p></li><li><p>更换服务器</p></li><li><p>修改DNS（或许可行？）</p></li><li><p>利用wordpress的第三方插件访问WP网络</p></li></ul></li></ul><ol start="2"><li>修改发件邮箱</li></ol><ul><li><p>修改mail()方法</p><ul><li>利用第三方插件</li></ul></li></ul><blockquote><p>因为我不想改代码了，也不想被警告，经过我1&#x2F;3秒的思考，我觉得利用第三方插件，即使他们就是一个SMTP发送邮件的方案却做得又大又重，我似乎没有更多其它选择。</p></blockquote><p>我最后选择了Post SMTP，这是唯一一个SMTP插件，比较流行，看起来没那么重，且貌似不会打扰我的邮件插件。另外不推荐在 VPS 上运行本地 SMTP 服务器（配置复杂，送达率低）。</p><p>推荐使用QQ&#x2F;foxmail邮箱做发件邮箱。所有的插件使用outlook都需要会员，outlook自带的SMTP貌似不可用了。我根据网上的教程设置了SMTP，打开了双重验证，然后在下方寻找应用密码。因为outlook在登录时似乎需要使用应用密码而不是账户密码。但是设置了双重验证，貌似又需要认证，永远提示认证失败。后来查了一下，说是之后会禁用第三方SMTP…奇奇怪怪的东西。另外我也不清楚这个是否会放到corn队列了，并且corn是否会堆积…对WP了解的不多。</p><p>所以，用FOXMAIL算啦！开通”POP3&#x2F;IMAP&#x2F;SMTP&#x2F;Exchange&#x2F;CardDAV&#x2F;CalDAV服务”之后，或许一个授权码用作登录即可。记得要勾选”<strong>SMTP 发信后保存到服务器</strong>“！</p><p>本身在传家宝上搭WP是为了方便，现在反而产生了更多的不方便。</p><h2 id="其他小问题"><a href="#其他小问题" class="headerlink" title="其他小问题"></a>其他小问题</h2><ul><li>插件提示存在屏蔽词或者被判断为垃圾内容，如下提示：</li></ul><blockquote><p>The mail may contain inappropriate words or content.</p></blockquote><p>此时你应该在插件中找到<strong>发送方式（Mailer Type）</strong>，修改为<code>PHPMailer</code></p><ul><li>发送失败，提示信息中存在如下字样：</li></ul><blockquote><p>Mail from address must be same as authorization user.</p></blockquote><p>此时你应该在插件中寻找**发件地址（From Address）**设置，然后修改文SMTP登录账号的邮箱。并且勾选”<strong>禁止其他插件&#x2F;主题修改此内容</strong>“选项</p>]]></content:encoded>
      
      
      <category domain="https://www.banzhuanriji.com/categories/posts/">默认</category>
      
      
      <category domain="https://www.banzhuanriji.com/tags/wordpress/">wordpress</category>
      
      <category domain="https://www.banzhuanriji.com/tags/%E6%BC%AB%E8%B0%88/">漫谈</category>
      
      <category domain="https://www.banzhuanriji.com/tags/%E6%8F%92%E4%BB%B6/">插件</category>
      
      <category domain="https://www.banzhuanriji.com/tags/email/">email</category>
      
      <category domain="https://www.banzhuanriji.com/tags/foxmail/">foxmail</category>
      
      <category domain="https://www.banzhuanriji.com/tags/OUTLOOK/">OUTLOOK</category>
      
      <category domain="https://www.banzhuanriji.com/tags/SMTP/">SMTP</category>
      
      <category domain="https://www.banzhuanriji.com/tags/%E9%82%AE%E4%BB%B6/">邮件</category>
      
      
      <comments>https://www.banzhuanriji.com/posts/wordpress-mail-sender/#disqus_thread</comments>
      
    </item>
    
    <item>
      <title>Pocket给我的失望与启示</title>
      <link>https://www.banzhuanriji.com/posts/pocket-will-shut-down/</link>
      <guid>https://www.banzhuanriji.com/posts/pocket-will-shut-down/</guid>
      <pubDate>Fri, 23 May 2025 16:00:00 GMT</pubDate>
      
        
        
          
          
      <description></description>
          
        
      
      
      
      <content:encoded><![CDATA[<h2 id="TL-DR"><a href="#TL-DR" class="headerlink" title="TL;DR"></a>TL;DR</h2><p>这似乎是一个经典的互联网产品悲剧循环：</p><blockquote><p>其他产品同类出现&gt; 导致用户流失 &gt; 更新不积极，市场定位偏差，没有突出买点以及差异化feature，固收基础功能以及紧缩的免费用户权限&gt; 加深市场竞争与用户流失&gt;Mozilla 战略调整&gt;宣布关停</p></blockquote><p>现在，如果做不到样样精通，还没有极端差异化的宣传噱头，似乎只有这一个剧本了。</p><hr><h2 id="引言"><a href="#引言" class="headerlink" title="引言"></a>引言</h2><p><img src="/pocket-twitter.webp"></p><p>当我听说 Mozilla 将在 2025 年 7 月 8 日关闭 Pocket 时，心里既不意外又有些唏嘘。作为一个曾经长期使用 Pocket 的免费用户，我一直觉得它的功能像个漂亮的”收藏夹”。Pocket 的关闭让我开始思考：是什么让这个”稍后阅读”工具走到尽头？</p><h2 id="Pocket-停止运营的背景"><a href="#Pocket-停止运营的背景" class="headerlink" title="Pocket 停止运营的背景"></a>Pocket 停止运营的背景</h2><p>2025 年 5 月 23 日，Mozilla 宣布 Pocket 将于 2025 年 7 月 8 日停止运营，同时关闭的还有其评论真伪分析工具 Fakespot（宣布今年7月1日关闭）。Mozilla 表示，关闭是为了集中资源发展 Firefox 浏览器。具体的安排如下：</p><ul><li><p><strong>服务终止</strong>：2025 年 7 月 8 日，Pocket 的 Web 端、移动端应用和浏览器扩展将全部停止。从 5 月 22 日起，用户无法下载应用或新购 Premium 订阅。</p></li><li><p><strong>数据导出</strong>：我需要在 2025 年 10 月 8 日前通过 Pocket 的导出页面备份文章，包括链接、标题、笔记和高亮，否则数据将永久丢失。</p></li><li><p><strong>连带关闭</strong>：Fakespot 的关闭表明 Mozilla 正在收缩非核心业务。</p></li></ul><h2 id="Pocket-为何让我失望"><a href="#Pocket-为何让我失望" class="headerlink" title="Pocket 为何让我失望"></a>Pocket 为何让我失望</h2><p>作为 Pocket 的免费用户，我一直觉得它的功能非常有限。Pocket 允许我保存网页和文章（或者说是链接）以便稍后阅读，但缺乏离线保存和下载网页内容的功能。这意味着，一旦网站失效或链接更改，我保存的内容就成了死链接，毫无用处。我曾保存过一篇教程，但网站下线后，Pocket 只能显示一个无用的标题，形同”残废收藏夹”。</p><p>相比之下，我用 Notion 搭配 Notion Web Clipper 就能轻松保存网页的完整内容，包括图片和格式化文本，还能添加笔记、标签，甚至整合到我的知识库中。不仅Notion，其他各种剪藏工具（<a href="https://chromewebstore.google.com/detail/web-clipper/mhfbofiokmppgdliakminbgdgcmbhbac">Web Clipper</a>不错）+同步工具都不错，你可以使用juplin（同样提供剪藏工具）+<a href="https://infini-cloud.net/en/">teracloud</a>（我的邀请码 <strong>NKM4G</strong> ）的搭配。为什么我推荐使用笔记工具？就像我之前在博客<a href="https://banzhuanriji.com/essay/how-to-take-notes.html#:~:text=%E8%AE%B0%E7%AC%94%E8%AE%B0%E7%9A%84%E7%9B%AE%E7%9A%84%E5%B9%B6%E9%9D%9E%E5%8D%95%E7%BA%AF%E4%B8%BA%E4%BA%86%E8%AE%B0%E5%BD%95%EF%BC%8C%E8%80%8C%E6%98%AF%E4%B8%BA%E4%BA%86%E5%A4%8D%E7%94%A8%E3%80%82">《如何记笔记》</a> 中有写：<strong>记笔记的目的并非单纯为了记录，而是为了复用。</strong> </p><p>或许你可以反驳，我需要的并不是一款”稍后读”软件而是笔记软件。或许是这样的，但是这两个需求并不冲突。如果你确实”稍后读”了收藏的内容，单纯地欣赏便罢了。若是还想记录下来，岂不是又要重复操作。所以带有我还是推荐带有全文搜索功能的笔记软件。</p><h2 id="导致-Pocket-关闭的连带效应"><a href="#导致-Pocket-关闭的连带效应" class="headerlink" title="导致 Pocket 关闭的连带效应"></a>导致 Pocket 关闭的连带效应</h2><p>Pocket 的关闭不是偶然，而是由一系列连带效应导致的，从竞品崛起到 Mozilla 的战略调整，每一步都让 Pocket 走向终结。</p><h3 id="竞品崛起引发用户流失"><a href="#竞品崛起引发用户流失" class="headerlink" title="竞品崛起引发用户流失"></a>竞品崛起引发用户流失</h3><p>Notion、Obsidian、Raindrop.io 和 Instapaper 等工具的出现让我和许多用户看到了更好的选择。Notion 提供了强大的笔记和协作功能，Obsidian 的双向链接适合知识管理，Raindrop.io 则在书签管理上更直观。这些工具的功能全面且不断更新，吸引了大量 Pocket 用户。X 平台上，不少用户表示因为竞品更强大而放弃了 Pocket。</p><h3 id="更新滞后与市场定位偏差"><a href="#更新滞后与市场定位偏差" class="headerlink" title="更新滞后与市场定位偏差"></a>更新滞后与市场定位偏差</h3><p>Pocket 的功能更新停滞不前，博客上最后一次提到新功能是在 2023 年。它的核心功能仅限于保存链接和阅读，缺乏 AI 摘要、深度笔记整合等差异化特性。免费用户像我一样，权限受限，无法使用标签管理或离线阅读，体验大打折扣。Pocket 的定位也模糊不清，既不如浏览器书签简单，也不如笔记工具强大，形同”半吊子”产品。</p><h3 id="加剧的市场竞争与用户流失"><a href="#加剧的市场竞争与用户流失" class="headerlink" title="加剧的市场竞争与用户流失"></a>加剧的市场竞争与用户流失</h3><p>功能停滞和定位偏差让 Pocket 在竞争中节节败退。竞品不断推出新功能，比如 Notion 的 AI 工具和 Obsidian 的插件生态，而 Pocket 却毫无进展。X 平台上的用户反馈也反映了类似的不满：界面陈旧、功能单一、标签管理繁琐。这些问题导致用户流失加剧，Pocket 的商业价值进一步下降。</p><h3 id="Mozilla-战略调整"><a href="#Mozilla-战略调整" class="headerlink" title="Mozilla 战略调整"></a>Mozilla 战略调整</h3><p>面对用户流失，Mozilla 的资源分配向 Firefox 倾斜。Firefox 需应对 Chrome 的竞争，迫使 Mozilla 放弃非核心项目。Pocket 与 Mozilla 的核心使命（隐私保护、开放互联网）关联度低，财务压力下，关闭 Pocket 和 Fakespot 成为必然选择。</p><h3 id="最终关闭决定"><a href="#最终关闭决定" class="headerlink" title="最终关闭决定"></a>最终关闭决定</h3><p>竞品崛起引发用户流失，功能滞后和定位偏差加剧竞争劣势，最终迫使 Mozilla 宣布关闭 Pocket。这个连带效应清晰地解释了 Pocket 从流行工具到被淘汰的轨迹。</p><h2 id="所以"><a href="#所以" class="headerlink" title="所以"></a>所以</h2><p>无论什么软件，数据可以无限制导出是最重要滴。Pocket 的关闭源于竞品崛起、用户流失、功能滞后、定位偏差和 Mozilla 战略调整的连带效应。作为免费用户，我早已因 Pocket 的局限性转向 Notion 等更强大的工具。这次经历让我意识到，选择数字工具时必须注重其功能完整性和长期稳定性。Pocket 的”稍后阅读”时代结束了，但它提醒我和所有用户：数据备份和工具选择需要更加谨慎。未来，工具开发者也应吸取教训，通过持续创新和清晰定位赢得用户信任。</p>]]></content:encoded>
      
      
      <category domain="https://www.banzhuanriji.com/categories/posts/">默认</category>
      
      
      <category domain="https://www.banzhuanriji.com/tags/notion/">notion</category>
      
      <category domain="https://www.banzhuanriji.com/tags/POCKET/">POCKET</category>
      
      <category domain="https://www.banzhuanriji.com/tags/teracloud/">teracloud</category>
      
      <category domain="https://www.banzhuanriji.com/tags/web-clipper/">web clipper</category>
      
      <category domain="https://www.banzhuanriji.com/tags/webdav/">webdav</category>
      
      <category domain="https://www.banzhuanriji.com/tags/%E4%BA%91%E5%90%8C%E6%AD%A5/">云同步</category>
      
      <category domain="https://www.banzhuanriji.com/tags/%E6%95%B0%E6%8D%AE%E5%AF%BC%E5%87%BA/">数据导出</category>
      
      <category domain="https://www.banzhuanriji.com/tags/%E6%BC%AB%E8%B0%88/">漫谈</category>
      
      <category domain="https://www.banzhuanriji.com/tags/%E7%A8%8D%E5%90%8E%E8%AF%BB/">稍后读</category>
      
      <category domain="https://www.banzhuanriji.com/tags/%E9%89%B4%E8%97%8F/">鉴藏</category>
      
      
      <comments>https://www.banzhuanriji.com/posts/pocket-will-shut-down/#disqus_thread</comments>
      
    </item>
    
    <item>
      <title>三十而立，JAVA生日快乐！</title>
      <link>https://www.banzhuanriji.com/posts/java-turning-30/</link>
      <guid>https://www.banzhuanriji.com/posts/java-turning-30/</guid>
      <pubDate>Thu, 22 May 2025 16:00:00 GMT</pubDate>
      
        
        
          
          
      <description></description>
          
        
      
      
      
      <content:encoded><![CDATA[<p>当一款一出生就颇具竞争力，甚至在每个榜单都名列前茅的编程语言持续更新到30周年，它已经不只是一门编程语言，一个学科，一项技术了。它是一个时代的标志。</p><h2 id="Java-技术演进之路与时代回响"><a href="#Java-技术演进之路与时代回响" class="headerlink" title="Java 技术演进之路与时代回响"></a>Java 技术演进之路与时代回响</h2><p>1995 年 5 月 23 日，Sun Microsystems 发布了 Java 语言，谁也没想到这个诞生于小型家电控制项目的编程语言，会在接下来的三十年里重塑整个软件开发生态。从 Web 时代的 Servlet 到移动互联网的 Android，从大数据领域的 Hadoop 到云原生时代的 Kubernetes，Java 始终站在技术变革的潮头。今天，在 Java 迎来三十周年生日之际，让我们回溯那些改变技术史的里程碑时刻。</p><h2 id="一、”一次编写，到处运行”-的传奇起点"><a href="#一、”一次编写，到处运行”-的传奇起点" class="headerlink" title="一、”一次编写，到处运行” 的传奇起点"></a>一、”一次编写，到处运行” 的传奇起点</h2><p>Java 的诞生源于 Sun 公司 “Green Team” 的一个冷门项目 – 为智能电视、机顶盒等设备开发编程语言。詹姆斯・高斯林（James Gosling）带领团队设计出 Oak 语言（Java 前身），却因市场时机未到陷入沉寂。直到互联网浪潮袭来，Oak 凭借 “Write Once, Run Anywhere”（WORA）的跨平台特性，在浏览器插件领域找到突破口。1995 年发布的 Java 1.0 定义了核心特性：字节码、虚拟机（JVM）、垃圾回收机制，这些设计让 Java 程序能够在不同操作系统上以相同行为运行，彻底改变了软件开发的部署模式。</p><p>1996 年，第一个 Java 开发工具包（JDK 1.0）正式发布，包含 AWT 图形库与 Applet 技术。Applet 允许 Java 程序嵌入网页运行，引发了第一次 Java 热潮。尽管后来因安全问题逐渐被淘汰，但它验证了 Java 在网络应用领域的潜力。</p><h2 id="二、企业级开发的统治时代"><a href="#二、企业级开发的统治时代" class="headerlink" title="二、企业级开发的统治时代"></a>二、企业级开发的统治时代</h2><p>1998 年，Java 2 平台的发布标志着 Java 正式进军企业级应用领域。Java 2 平台分为标准版（J2SE）、企业版（J2EE）和微型版（J2ME），其中 J2EE 通过 EJB（Enterprise JavaBeans）、Servlet、JSP 等技术，为分布式应用开发提供了完整解决方案。IBM、Oracle 等巨头纷纷加入 Java 阵营，Java EE 成为构建银行核心系统、电商平台的事实标准，支撑起全球 80% 以上的企业级应用开发。</p><p>2004 年，Java 5（代号 Tiger）的发布堪称 Java 史上的重大飞跃。泛型、自动装箱 &#x2F; 拆箱、枚举类型、可变参数等特性的引入，让 Java 从单纯的面向对象语言进化为更现代化的编程语言。同年，Spring 框架 1.0 发布，以轻量级容器和依赖注入理念革新了企业级开发模式，与 Java EE 形成互补，共同奠定了 Java 在企业级市场的统治地位。</p><h2 id="三、移动革命与开源觉醒"><a href="#三、移动革命与开源觉醒" class="headerlink" title="三、移动革命与开源觉醒"></a>三、移动革命与开源觉醒</h2><p>2007 年，苹果发布 iPhone，智能手机时代拉开帷幕。而 Java 早已提前布局，J2ME 曾是功能机时代的主流开发平台。2008 年，谷歌推出基于 Linux 内核和 Java 的 Android 系统，将 Java 推向移动开发的巅峰。虽然 Android 后来改用 Kotlin 作为官方推荐语言，但 Java 至今仍是安卓开发者的重要工具，全球数十亿台设备上运行着 Java 编写的应用。</p><p>2006 年，Sun 宣布 Java 开源，成立 OpenJDK 社区。这一决策打破了商业闭源的限制，激发了全球开发者的创造力。Apache Harmony、IBM J9 等替代 JVM 项目涌现，OpenJDK 逐步成为 Java 的参考实现。2010 年 Oracle 收购 Sun 后，围绕 Java 版权与开源协议的争议不断，但社区力量最终推动 Java 走向更开放的生态。</p><h2 id="四、云原生时代的涅槃重生"><a href="#四、云原生时代的涅槃重生" class="headerlink" title="四、云原生时代的涅槃重生"></a>四、云原生时代的涅槃重生</h2><p>2011 年起，Java 进入快速迭代周期，每半年发布一个新版本。Java 8 引入的 Lambda 表达式与 Stream API，让函数式编程范式融入传统 OOP 体系，极大提升了代码简洁性与并行处理能力；Java 11 成为首个 LTS（长期支持）版本，精简模块化系统并移除过时特性，标志着 Java 向轻量化转型。</p><p>随着容器化与微服务兴起，Java 凭借 Spring Cloud、Quarkus 等框架，以及 GraalVM 即时编译技术，在云原生领域焕发新生。Kubernetes 项目的核心组件 Kubelet、kube-apiserver 均采用 Java 开发，印证了其在分布式系统领域的卓越性能与稳定性。</p><h2 id="五、下一个三十年：Java-的无限可能"><a href="#五、下一个三十年：Java-的无限可能" class="headerlink" title="五、下一个三十年：Java 的无限可能"></a>五、下一个三十年：Java 的无限可能</h2><p>站在三十周年的节点，Java 生态早已超越编程语言本身，成为覆盖开发、部署、运维全生命周期的技术体系。如今，Java 正积极拥抱 AI 与 Web3 领域，通过 Project Loom 虚拟线程优化高并发场景，探索与机器学习框架的深度集成。OpenJDK 社区持续推进 JVM 性能优化，让 Java 在边缘计算、Serverless 等新兴领域保持竞争力。</p><p>从智能家电到数据中心，从桌面应用到云端服务，Java 用三十年时间证明了 “技术生命力在于持续进化”。对于全球 1000 万 Java 开发者而言，这不仅是一门编程语言的生日，更是一个时代技术精神的缩影。未来，随着 Java 22 计划引入结构化并发、虚拟线程成熟化等重磅特性，这个 “永远年轻” 的语言仍将书写新的传奇。</p><hr><h2 id="Java重要版本更新列表"><a href="#Java重要版本更新列表" class="headerlink" title="Java重要版本更新列表"></a>Java重要版本更新列表</h2><blockquote><p>由GROK.COM生成</p></blockquote><h3 id="1-Java-1-0（1996-年-1-月）"><a href="#1-Java-1-0（1996-年-1-月）" class="headerlink" title="1. Java 1.0（1996 年 1 月）"></a>1. <strong>Java 1.0（1996 年 1 月）</strong></h3><ul><li><p>定义核心特性，如字节码、虚拟机（JVM）、垃圾回收机制，实现 “Write Once, Run Anywhere” 跨平台运行。</p></li><li><p>发布第一个 Java 开发工具包（JDK 1.0），包含 AWT 图形库与 Applet 技术。</p></li></ul><h3 id="2-Java-1-1（1997-年-2-月）"><a href="#2-Java-1-1（1997-年-2-月）" class="headerlink" title="2. Java 1.1（1997 年 2 月）"></a>2. <strong>Java 1.1（1997 年 2 月）</strong></h3><ul><li><p>引入内部类，增强代码封装与组织性。</p></li><li><p>加入反射机制，支持运行时检查和操作类、方法、字段。</p></li><li><p>诞生 JDBC，方便 Java 程序与数据库交互。</p></li></ul><h3 id="3-Java-2-平台（1998-年）"><a href="#3-Java-2-平台（1998-年）" class="headerlink" title="3. Java 2 平台（1998 年）"></a>3. <strong>Java 2 平台（1998 年）</strong></h3><ul><li>分为标准版（J2SE）、企业版（J2EE）和微型版（J2ME）。其中 J2EE 通过 EJB、Servlet、JSP 等技术，为分布式应用开发提供完整解决方案。</li></ul><h3 id="4-Java-5（2004-年-9-月）"><a href="#4-Java-5（2004-年-9-月）" class="headerlink" title="4. Java 5（2004 年 9 月）"></a>4. <strong>Java 5（2004 年 9 月）</strong></h3><ul><li><p>引入泛型，提升类型安全性与代码复用性。</p></li><li><p>实现自动装箱 &#x2F; 拆箱，简化基本数据类型与包装类的转换。</p></li><li><p>新增枚举类型，方便定义常量集合。</p></li><li><p>支持可变参数，使方法参数数量更灵活。</p></li></ul><h3 id="5-Java-7（2011-年-7-月）"><a href="#5-Java-7（2011-年-7-月）" class="headerlink" title="5. Java 7（2011 年 7 月）"></a>5. <strong>Java 7（2011 年 7 月）</strong></h3><ul><li><p>引入 try - with - resources 语句，简化资源管理。</p></li><li><p>钻石操作符<code>（&lt;&gt;）</code>自动推断泛型类型参数。</p></li><li><p>Fork&#x2F;Join 框架提升并行处理大量数据的效率。</p></li><li><p>NIO 升级到 NIO.2，新增异步 I&#x2F;O 和文件系统 API。</p></li></ul><h3 id="6-Java-8（2014-年-3-月）"><a href="#6-Java-8（2014-年-3-月）" class="headerlink" title="6. Java 8（2014 年 3 月）"></a>6. <strong>Java 8（2014 年 3 月）</strong></h3><ul><li><p>引入 Lambda 表达式，支持函数式编程范式。</p></li><li><p>推出 Stream API，提升代码简洁性与并行处理能力。</p></li><li><p>允许为接口方法添加默认方法体。</p></li><li><p>引入日期时间应用程序接口（date time API）。</p></li></ul><h3 id="7-Java-9（2017-年-9-月）"><a href="#7-Java-9（2017-年-9-月）" class="headerlink" title="7. Java 9（2017 年 9 月）"></a>7. <strong>Java 9（2017 年 9 月）</strong></h3><ul><li><p>引入模块化系统（Project Jigsaw），解决类路径问题，提高代码可维护性。</p></li><li><p>提供 Javadoc，支持在 API 文档中进行搜索，输出兼容 HTML5 标准。</p></li><li><p>在 List、Set、Map 接口中，静态工厂方法可创建不可变实例。</p></li><li><p>内置轻量级 JSON API。</p></li></ul><h3 id="8-Java-10（2018-年-3-月）"><a href="#8-Java-10（2018-年-3-月）" class="headerlink" title="8. Java 10（2018 年 3 月）"></a>8. <strong>Java 10（2018 年 3 月）</strong></h3><ul><li><p>带来局部变量类型推断（var 关键字），简化代码声明。</p></li><li><p>提升线程间通信效率的线程本地握手机制。</p></li><li><p>将 JDK 多个代码仓库合并到一个储存库。</p></li></ul><h3 id="9-Java-11（2018-年-9-月）"><a href="#9-Java-11（2018-年-9-月）" class="headerlink" title="9. Java 11（2018 年 9 月）"></a>9. <strong>Java 11（2018 年 9 月）</strong></h3><ul><li><p>成为首个 LTS（长期支持）版本。</p></li><li><p>精简模块化系统，移除过时特性。</p></li><li><p>引入 NestMembers 属性和 NestHost 属性。</p></li><li><p>支持 TLS 1.3 协议。</p></li></ul><h3 id="10-Java-17（2021-年-9-月）"><a href="#10-Java-17（2021-年-9-月）" class="headerlink" title="10. Java 17（2021 年 9 月）"></a>10. <strong>Java 17（2021 年 9 月）</strong></h3><ul><li><p>引入密封类，限制类的继承层次，增强代码安全性。</p></li><li><p>恢复浮点语义，为伪随机数发生器（PRNG）提供新接口类型。</p></li><li><p>增加 macOS AArch64 端口，删除 Applet API、远程方法调用（RMI）激活机制，强封装 JDK 内部。</p></li></ul>]]></content:encoded>
      
      
      <category domain="https://www.banzhuanriji.com/categories/posts/">默认</category>
      
      
      <category domain="https://www.banzhuanriji.com/tags/java/">java</category>
      
      <category domain="https://www.banzhuanriji.com/tags/%E5%BC%80%E5%8F%91/">开发</category>
      
      <category domain="https://www.banzhuanriji.com/tags/java30%E5%91%A8%E5%B9%B4/">java30周年</category>
      
      <category domain="https://www.banzhuanriji.com/tags/jdk/">jdk</category>
      
      <category domain="https://www.banzhuanriji.com/tags/%E5%91%A8%E5%B9%B4/">周年</category>
      
      <category domain="https://www.banzhuanriji.com/tags/%E7%BC%96%E7%A8%8B%E8%AF%AD%E8%A8%80/">编程语言</category>
      
      
      <comments>https://www.banzhuanriji.com/posts/java-turning-30/#disqus_thread</comments>
      
    </item>
    
    <item>
      <title>基于Nextjs的说说程序</title>
      <link>https://www.banzhuanriji.com/posts/microblog-by-nextjs/</link>
      <guid>https://www.banzhuanriji.com/posts/microblog-by-nextjs/</guid>
      <pubDate>Sun, 18 May 2025 16:00:00 GMT</pubDate>
      
        
        
          
          
      <description></description>
          
        
      
      
      
      <content:encoded><![CDATA[<h3 id="TL-DR"><a href="#TL-DR" class="headerlink" title="TL;DR"></a>TL;DR</h3><p>点这里看效果：<a href="https://ss.banzhuanriji.com/"> https://ss.banzhuanriji.com</a></p><p>发布，查看</p><hr><p>一代人有一代人的QQ空间要踩 XD ，可是QQ空间怎么少的了说说呢。而且发长文会带动一切RSS订阅用户，偶尔想当个动态&#x2F;朋友圈发的话，太打扰人了。</p><p>所以我想到了两种方案：</p><ul><li><p>RSS避免某个分类的文章，把这个分类当做说说来发。但我想了想，似乎在function里加一些方法直接控制数据更好用一点，而且样式制作可以直接在CSS里修改，不影响其他文章样式内容。所以wordpress的方案又分为两种</p></li><li><p>不经过RSS的分类</p><ul><li>在wordpress的function中创建独立的方法内容</li></ul></li><li><p>创建一个独立的说说页面</p></li></ul><p>但是目前wordpress的数据库已经够复杂，冗余过于夸张了。并且之后如果我不希望使用wordpress的话，如何迁移？所以决定使用独立的方案。</p><p>第一想到的就是目前已经存在的有没有可用的。第一个想到的就是<a href="https://miantiao.me/">面条</a>大佬的<a href="https://github.com/ccbikai/BroadcastChannel">BroadcastChannel</a> ，把TG频道做数据来源。实话实说挺好用的。不过样式不可控，可能需要自己修改，且一些TG的内容样式不支持。所以我就采用了Nextjs+neon数据库的方案写了这个程序。</p><h3 id="功能"><a href="#功能" class="headerlink" title="功能"></a>功能</h3><ul><li><p>浏览内容，分页</p></li><li><p>鉴权登录</p></li><li><p>发布内容</p></li></ul><p>忽略了内容修改的功能，因为要做修改的话可能需要在首页做登录判断，然后在布局上做一些按钮预留。想了想暂时不做这个了</p><h3 id="特色"><a href="#特色" class="headerlink" title="特色"></a>特色</h3><p>如果可以成为特色的话…</p><ul><li><p>全部白嫖，白嫖！</p></li><li><p>结构超级简单</p></li><li><p>Markdown支持</p></li></ul><h3 id="预览"><a href="#预览" class="headerlink" title="预览"></a>预览</h3><p><img src="/img/image-2.png"></p><h3 id="下载"><a href="#下载" class="headerlink" title="下载"></a>下载</h3><p>如果你有兴趣可以查看这里： <a href="https://ss.banzhuanriji.com/">https://ss.banzhuanriji.com</a></p><p>暂时没有提供下载，等我完善之后在github上发出来XD。比较感兴趣的话也可以联系我，我会发给你一份源码： m#hoytzhang.com 或者在文章下面留言 ；D</p>]]></content:encoded>
      
      
      <category domain="https://www.banzhuanriji.com/categories/posts/">默认</category>
      
      
      <category domain="https://www.banzhuanriji.com/tags/%E5%BC%80%E5%8F%91/">开发</category>
      
      <category domain="https://www.banzhuanriji.com/tags/vercel/">vercel</category>
      
      <category domain="https://www.banzhuanriji.com/tags/%E5%8D%9A%E5%AE%A2/">博客</category>
      
      <category domain="https://www.banzhuanriji.com/tags/%E5%89%8D%E7%AB%AF/">前端</category>
      
      <category domain="https://www.banzhuanriji.com/tags/neon/">neon</category>
      
      <category domain="https://www.banzhuanriji.com/tags/next/">next</category>
      
      <category domain="https://www.banzhuanriji.com/tags/nextjs/">nextjs</category>
      
      <category domain="https://www.banzhuanriji.com/tags/QQ%E7%A9%BA%E9%97%B4/">QQ空间</category>
      
      <category domain="https://www.banzhuanriji.com/tags/serverless/">serverless</category>
      
      <category domain="https://www.banzhuanriji.com/tags/twitter/">twitter</category>
      
      <category domain="https://www.banzhuanriji.com/tags/%E8%AF%B4%E8%AF%B4/">说说</category>
      
      
      <comments>https://www.banzhuanriji.com/posts/microblog-by-nextjs/#disqus_thread</comments>
      
    </item>
    
    <item>
      <title>如何为wordpress的文章添加阅读量</title>
      <link>https://www.banzhuanriji.com/posts/show-read-count-on-wordpress/</link>
      <guid>https://www.banzhuanriji.com/posts/show-read-count-on-wordpress/</guid>
      <pubDate>Wed, 14 May 2025 16:00:00 GMT</pubDate>
      
        
        
          
          
      <description></description>
          
        
      
      
      
      <content:encoded><![CDATA[<p>要在 WordPress 上实现文章阅读统计（跟踪每篇文章的阅读次数或页面浏览量），可以借助插件或自定义代码。以下是几种常见的方法和工具，如果使用代码请注意文章内容。</p><h3 id="方法一：使用插件实现文章阅读统计"><a href="#方法一：使用插件实现文章阅读统计" class="headerlink" title="方法一：使用插件实现文章阅读统计"></a>方法一：使用插件实现文章阅读统计</h3><ol><li><strong>MonsterInsights</strong></li></ol><ul><li>MonsterInsights 是最受欢迎的 Google Analytics 插件之一，可以直接在 WordPress 仪表板中查看每篇文章的页面浏览量、访客行为、跳出率等数据。通过其 Page Insights 扩展，还可以查看单篇文章的详细统计（如页面浏览量、停留时间等）。<strong>必须绑定GA。</strong></li></ul><ol start="2"><li><strong>WP Statistics</strong></li></ol><ul><li>WP Statistics 是一个隐私友好的分析插件，符合 GDPR 要求，无需外部账户，直接在 WordPress 数据库中存储数据。它可以跟踪每篇文章的访问量，并提供详细的图表和分类分析。不过<strong>最顶上那个评论似乎说明了一些问题</strong>，我没尝试这个插件，观望一下。</li></ul><ol start="3"><li><strong>Post Views Counter</strong></li></ol><ul><li>一个轻量级插件，专门用于显示文章、页面或自定义内容的浏览次数。它支持通过 PHP、JavaScript 或 REST API 跟踪数据，并允许自定义计数器的显示位置和样式。</li></ul><ol start="4"><li><strong>Jetpack Stats</strong></li></ol><ul><li>Jetpack 的统计模块提供文章的浏览量、热门内容、流量来源等数据，适合 WordPress.com 用户或安装了 Jetpack 的自托管站点。免费用户可查看<strong>过去 7 天</strong>的统计，付费计划解锁更多功能。</li></ul><ol start="5"><li><strong>Independent Analytics</strong></li></ol><ul><li>一个专为 WordPress 设计的免费分析插件，加载速度快，符合 GDPR。它可以自动记录文章的访问量，并按类别显示统计数据。</li></ul><ol start="6"><li><strong>如果你恰好财力雄厚，你可以使用一些主题的pro版本自带的功能</strong></li></ol><ul><li>这里就不对赘述了，例如我使用的blocksy主题，在设置页面左侧点击blocksy，设置中文章元数据打开显示即可。</li></ul><h3 id="方法二：插件之短码插件-推荐"><a href="#方法二：插件之短码插件-推荐" class="headerlink" title="方法二：插件之短码插件(推荐)"></a>方法二：插件之短码插件(推荐)</h3><ol><li><strong>Post Views for Jetpack</strong></li></ol><ul><li>提供短码，可以在文章、页面或小工具中显示各种统计数据。以下部分代码复制于这个插件的讨论区： <a href="https://wordpress.org/support/topic/way-to-display-views-in-blocksy-post-meta/">https://wordpress.org/support/topic/way-to-display-views-in-blocksy-post-meta/</a></li></ul><p>这个插件如何在blocksy中使用？请在你的<code>function.php</code>中添加以下代码 （<strong>注意把<code>[sbs_views]</code> 修改为你上面短码插件中约定的短码</strong>）：</p><pre><code class="hljs plaintext">add_filter( &#x27;blocksy:archive:render-card-layer&#x27;, function ( $output, $single_component) &#123;if ( &#x27;post_meta&#x27; !== $single_component[&#x27;id&#x27;] ) &#123;return $output;&#125;$post_views = do_shortcode( &#x27;&lt;span style=&quot;background-color: initial; font-family: inherit; text-align: initial;&quot;&gt;[sbs_views]&lt;/span&gt;&#x27; );$output = str_replace( &#x27;&lt;/li&gt;&lt;/ul&gt;&#x27;, &#x27;&lt;/li&gt;&lt;li class=&quot;post-views&quot;&gt;&#x27; . $post_views . &#x27;&lt;/li&gt;&lt;/ul&gt;&#x27;, $output );return $output;&#125;, 11, 2 );</code></pre><h3 id="方法三：自定义代码实现阅读统计"><a href="#方法三：自定义代码实现阅读统计" class="headerlink" title="方法三：自定义代码实现阅读统计"></a>方法三：自定义代码实现阅读统计</h3><p>在你的主题的<code>function.php</code>中添加以下代码：</p><pre><code class="hljs plaintext">// 记录文章浏览量function set_post_views($postID) &#123;    $count_key = &#x27;post_views_count&#x27;;    $count = get_post_meta($postID, $count_key, true);    if ($count == &#x27;&#x27;) &#123;        $count = 0;        delete_post_meta($postID, $count_key);        add_post_meta($postID, $count_key, &#x27;0&#x27;);    &#125; else &#123;        $count++;        update_post_meta($postID, $count_key, $count);    &#125;&#125;// 在单篇文章页面记录浏览量function track_post_views($postID) &#123;    if (!is_single()) return;    if (empty($postID)) &#123;        global $post;        $postID = $post-&gt;ID;    &#125;    set_post_views($postID);&#125;add_action(&#x27;wp_head&#x27;, &#x27;track_post_views&#x27;);// 显示文章浏览量function get_post_views($postID) &#123;    $count_key = &#x27;post_views_count&#x27;;    $count = get_post_meta($postID, $count_key, true);    if ($count == &#x27;&#x27;) &#123;        delete_post_meta($postID, $count_key);        add_post_meta($postID, $count_key, &#x27;0&#x27;);        return &quot;0 Views&quot;;    &#125;    return $count . &#x27; Views&#x27;;&#125;</code></pre><p>在需要使用的地方添加：</p><pre><code class="hljs plaintext">echo get_post_views(get_the_ID());</code></pre><p>如果需要在blocksy主题中使用：</p><pre><code class="hljs plaintext">add_filter( &#x27;blocksy:archive:render-card-layer&#x27;, function ( $output, $single_component ) &#123;    if ( &#x27;post_meta&#x27; !== $single_component[&#x27;id&#x27;] ) &#123;        return $output;    &#125;    $output = str_replace( &#x27;&lt;/li&gt;&lt;/ul&gt;&#x27;, &#x27;&lt;/li&gt;&lt;li class=&quot;post-views&quot;&gt;&#x27; . get_post_views(get_the_ID()) . &#x27;&lt;/li&gt;&lt;/ul&gt;&#x27;, $output );    return $output;&#125;, 10, 2 );</code></pre><hr><p>注意上面所有的代码部分，可能会造成不可回溯的内容。</p><p>部分可能会在你数据库的<code>前缀_postmeta</code>表中创建很多key为<code>post_views_count</code> 的数据，介意的话可以使用第三方插件或者自己手动修改。另外注意，有的主题不允许修改<code>functions.php</code>的代码，你复制代码保存失败时，可以按 <code>ctrl</code>+ <code>shift</code> + <code>v</code> 粘贴。</p>]]></content:encoded>
      
      
      <category domain="https://www.banzhuanriji.com/categories/posts/">默认</category>
      
      
      <category domain="https://www.banzhuanriji.com/tags/%E4%B8%BB%E9%A2%98/">主题</category>
      
      <category domain="https://www.banzhuanriji.com/tags/%E6%9C%8D%E5%8A%A1%E5%99%A8/">服务器</category>
      
      <category domain="https://www.banzhuanriji.com/tags/%E5%89%8D%E7%AB%AF/">前端</category>
      
      <category domain="https://www.banzhuanriji.com/tags/wordpress/">wordpress</category>
      
      <category domain="https://www.banzhuanriji.com/tags/%E7%BB%9F%E8%AE%A1/">统计</category>
      
      <category domain="https://www.banzhuanriji.com/tags/count/">count</category>
      
      <category domain="https://www.banzhuanriji.com/tags/jetpack/">jetpack</category>
      
      <category domain="https://www.banzhuanriji.com/tags/view-count/">view count</category>
      
      <category domain="https://www.banzhuanriji.com/tags/%E6%8F%92%E4%BB%B6/">插件</category>
      
      <category domain="https://www.banzhuanriji.com/tags/%E6%B5%8F%E8%A7%88%E9%87%8F/">浏览量</category>
      
      <category domain="https://www.banzhuanriji.com/tags/%E7%82%B9%E5%87%BB%E9%87%8F/">点击量</category>
      
      <category domain="https://www.banzhuanriji.com/tags/%E8%AE%A1%E6%95%B0/">计数</category>
      
      <category domain="https://www.banzhuanriji.com/tags/%E9%98%85%E8%AF%BB%E8%AE%A1%E6%95%B0/">阅读计数</category>
      
      
      <comments>https://www.banzhuanriji.com/posts/show-read-count-on-wordpress/#disqus_thread</comments>
      
    </item>
    
  </channel>
</rss>