<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
  <title>小强IT屋</title>
  
  <subtitle>IT小强的个人博客，这是一间开源分享的实验室，也是每位技术同路人的交流驿站!</subtitle>
  <link href="https://xqitw.cn/atom.xml" rel="self"/>
  
  <link href="https://xqitw.cn/"/>
  <updated>2026-01-21T17:04:45.000Z</updated>
  <id>https://xqitw.cn/</id>
  
  <author>
    <name>IT小强</name>
    
  </author>
  
  <generator uri="https://hexo.io/">Hexo</generator>
  
  <entry>
    <title>Claude Skills深度解析：扩展AI助手的无限可能</title>
    <link href="https://xqitw.cn/2026/01/22/post-23/"/>
    <id>https://xqitw.cn/2026/01/22/post-23/</id>
    <published>2026-01-21T17:03:19.000Z</published>
    <updated>2026-01-21T17:04:45.000Z</updated>
    
    <content type="html"><![CDATA[<blockquote><p>在AI助手日益普及的今天，Claude Skills作为Claude Code CLI的核心功能，正重新定义开发者与AI协作的方式。本文将深入探讨Claude Skills的技术架构、核心功能以及实际应用场景，带你全面了解这一革命性的AI扩展系统。</p></blockquote><h2 id="什么是Claude-Skills？"><a href="#什么是Claude-Skills？" class="headerlink" title="什么是Claude Skills？"></a>什么是Claude Skills？</h2><p><strong>Claude Skills</strong>是Claude Code CLI中的插件式扩展系统，允许用户通过简单的命令扩展Claude的功能边界。与传统的AI对话不同，Skills赋予了Claude执行具体任务的能力，从代码提交到PR审查，从文件处理到系统操作，真正实现了”对话即操作”的愿景。</p><h3 id="核心理念：从对话到执行"><a href="#核心理念：从对话到执行" class="headerlink" title="核心理念：从对话到执行"></a>核心理念：从对话到执行</h3><p>传统AI助手仅限于文本生成和建议，而Claude Skills通过：</p><ul><li><strong>命令化接口</strong>：使用<code>/</code>前缀触发特定技能</li><li><strong>上下文感知</strong>：技能能够理解当前工作环境和状态</li><li><strong>安全沙箱</strong>：在受控环境中执行操作，保障系统安全</li></ul><h2 id="Claude-Skills技术架构"><a href="#Claude-Skills技术架构" class="headerlink" title="Claude Skills技术架构"></a>Claude Skills技术架构</h2><h3 id="1-模块化设计"><a href="#1-模块化设计" class="headerlink" title="1. 模块化设计"></a>1. 模块化设计</h3><p>Claude Skills采用高度模块化的架构：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line">graph TB</span><br><span class="line">    A[&quot;Claude Core&quot;] --&gt; B[&quot;Skill Router&quot;]</span><br><span class="line">    B --&gt; C[&quot;Git Skills&quot;]</span><br><span class="line">    B --&gt; D[&quot;Code Review Skills&quot;]</span><br><span class="line">    B --&gt; E[&quot;File Operation Skills&quot;]</span><br><span class="line">    B --&gt; F[&quot;Custom Skills&quot;]</span><br><span class="line"></span><br><span class="line">    C --&gt; C1[&quot;/commit&quot;]</span><br><span class="line">    C --&gt; C2[&quot;/review-pr&quot;]</span><br><span class="line">    D --&gt; D1[&quot;/lint&quot;]</span><br><span class="line">    D --&gt; D2[&quot;/test&quot;]</span><br><span class="line"></span><br><span class="line">    F --&gt; F1[&quot;User-defined&quot;]</span><br><span class="line">    F --&gt; F2[&quot;Community Skills&quot;]</span><br></pre></td></tr></table></figure><h3 id="2-安全执行层"><a href="#2-安全执行层" class="headerlink" title="2. 安全执行层"></a>2. 安全执行层</h3><p>每个Skill都在独立的安全环境中运行：</p><ul><li><strong>权限隔离</strong>：每个技能拥有明确的权限边界</li><li><strong>操作审计</strong>：所有执行记录都有完整日志</li><li><strong>回滚机制</strong>：关键操作支持撤销功能</li></ul><h3 id="3-上下文管理系统"><a href="#3-上下文管理系统" class="headerlink" title="3. 上下文管理系统"></a>3. 上下文管理系统</h3><p>Skills能够智能理解当前环境：</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># 示例：/commit技能上下文感知</span></span><br><span class="line">current_context = &#123;</span><br><span class="line">    <span class="string">&quot;working_directory&quot;</span>: <span class="string">&quot;/path/to/project&quot;</span>,</span><br><span class="line">    <span class="string">&quot;git_status&quot;</span>: <span class="string">&quot;2 files modified&quot;</span>,</span><br><span class="line">    <span class="string">&quot;recent_changes&quot;</span>: [<span class="string">&quot;src/main.py&quot;</span>, <span class="string">&quot;tests/test_main.py&quot;</span>],</span><br><span class="line">    <span class="string">&quot;project_type&quot;</span>: <span class="string">&quot;Python&quot;</span>,</span><br><span class="line">    <span class="string">&quot;commit_history&quot;</span>: [<span class="string">&quot;feat: add authentication&quot;</span>, <span class="string">&quot;fix: bug in login&quot;</span>]</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="核心Skills详解"><a href="#核心Skills详解" class="headerlink" title="核心Skills详解"></a>核心Skills详解</h2><h3 id="1-Git操作技能组"><a href="#1-Git操作技能组" class="headerlink" title="1. Git操作技能组"></a>1. Git操作技能组</h3><h4 id="commit-智能代码提交"><a href="#commit-智能代码提交" class="headerlink" title="/commit - 智能代码提交"></a><code>/commit</code> - 智能代码提交</h4><p><strong>功能特点</strong>：</p><ul><li>自动分析代码变更，生成有意义的提交信息</li><li>遵循项目特定的提交规范（Conventional Commits等）</li><li>智能检测并避免提交敏感信息（API密钥、密码等）</li><li>支持多文件提交的分组和分类</li></ul><p><strong>使用示例</strong>：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># 自动分析当前变更并提交</span></span><br><span class="line">/commit -m <span class="string">&quot;添加用户认证功能&quot;</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># 交互式提交，Claude会引导完成整个过程</span></span><br><span class="line">/commit --interactive</span><br></pre></td></tr></table></figure><h4 id="review-pr-PR审查助手"><a href="#review-pr-PR审查助手" class="headerlink" title="/review-pr - PR审查助手"></a><code>/review-pr</code> - PR审查助手</h4><p><strong>功能特点</strong>：</p><ul><li>代码质量分析：复杂度、重复度、最佳实践检查</li><li>安全漏洞扫描：SQL注入、XSS等常见安全风险</li><li>测试覆盖率评估：识别缺少测试的关键路径</li><li>架构一致性检查：确保新代码符合项目架构模式</li></ul><h3 id="2-代码质量技能组"><a href="#2-代码质量技能组" class="headerlink" title="2. 代码质量技能组"></a>2. 代码质量技能组</h3><h4 id="lint-代码规范检查"><a href="#lint-代码规范检查" class="headerlink" title="/lint - 代码规范检查"></a><code>/lint</code> - 代码规范检查</h4><p>支持多种语言的静态分析：</p><ul><li><strong>Python</strong>: pylint, flake8, black格式检查</li><li><strong>JavaScript&#x2F;TypeScript</strong>: ESLint, Prettier</li><li><strong>Go</strong>: gofmt, govet, staticcheck</li><li><strong>Java</strong>: Checkstyle, PMD</li></ul><h4 id="test-测试辅助"><a href="#test-测试辅助" class="headerlink" title="/test - 测试辅助"></a><code>/test</code> - 测试辅助</h4><ul><li>测试用例生成：基于函数签名的智能测试生成</li><li>测试覆盖率分析：识别测试盲区</li><li>性能基准测试：识别性能回归</li></ul><h3 id="3-文件处理技能组"><a href="#3-文件处理技能组" class="headerlink" title="3. 文件处理技能组"></a>3. 文件处理技能组</h3><h4 id="format-多格式文件处理"><a href="#format-多格式文件处理" class="headerlink" title="/format - 多格式文件处理"></a><code>/format</code> - 多格式文件处理</h4><p>支持的文件类型：</p><ul><li><strong>代码文件</strong>: 自动格式化，语法检查</li><li><strong>Markdown文档</strong>: 格式优化，链接检查</li><li><strong>配置文件</strong>: YAML&#x2F;JSON&#x2F;TOML格式化和验证</li><li><strong>数据文件</strong>: CSV&#x2F;Excel数据清洗和转换</li></ul><h4 id="analyze-代码库分析"><a href="#analyze-代码库分析" class="headerlink" title="/analyze - 代码库分析"></a><code>/analyze</code> - 代码库分析</h4><ul><li>依赖关系可视化：生成项目架构图</li><li>技术债务评估：识别需要重构的代码区域</li><li>变更影响分析：评估代码修改的连锁影响</li></ul><h2 id="高级特性"><a href="#高级特性" class="headerlink" title="高级特性"></a>高级特性</h2><h3 id="1-技能组合（Skill-Chaining）"><a href="#1-技能组合（Skill-Chaining）" class="headerlink" title="1. 技能组合（Skill Chaining）"></a>1. 技能组合（Skill Chaining）</h3><p>多个技能可以串联执行复杂工作流：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># 典型开发工作流</span></span><br><span class="line">/lint &amp;&amp; /test &amp;&amp; /commit -m <span class="string">&quot;重构用户模块&quot;</span></span><br></pre></td></tr></table></figure><h3 id="2-上下文感知技能调用"><a href="#2-上下文感知技能调用" class="headerlink" title="2. 上下文感知技能调用"></a>2. 上下文感知技能调用</h3><p>Skills能够根据当前工作状态智能建议：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># 检测到未提交的更改时</span></span><br><span class="line">检测到5个已修改文件，建议使用 /commit 提交更改</span><br><span class="line">检测到测试失败，建议使用 /test 调试问题</span><br><span class="line">检测到代码规范问题，建议使用 /lint 修复</span><br></pre></td></tr></table></figure><h3 id="3-自定义技能开发"><a href="#3-自定义技能开发" class="headerlink" title="3. 自定义技能开发"></a>3. 自定义技能开发</h3><p>Claude Skills支持用户自定义扩展：</p><figure class="highlight yaml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># 自定义skill配置示例</span></span><br><span class="line"><span class="attr">my-custom-skill:</span></span><br><span class="line">  <span class="attr">name:</span> <span class="string">&quot;项目文档生成器&quot;</span></span><br><span class="line">  <span class="attr">description:</span> <span class="string">&quot;自动生成项目技术文档&quot;</span></span><br><span class="line">  <span class="attr">triggers:</span> [<span class="string">&quot;/gen-docs&quot;</span>, <span class="string">&quot;/document&quot;</span>]</span><br><span class="line">  <span class="attr">permissions:</span></span><br><span class="line">    <span class="bullet">-</span> <span class="string">read_project_files</span></span><br><span class="line">    <span class="bullet">-</span> <span class="string">write_docs_directory</span></span><br><span class="line">  <span class="attr">actions:</span></span><br><span class="line">    <span class="bullet">-</span> <span class="string">analyze_code_structure</span></span><br><span class="line">    <span class="bullet">-</span> <span class="string">generate_api_docs</span></span><br><span class="line">    <span class="bullet">-</span> <span class="string">create_architecture_diagram</span></span><br></pre></td></tr></table></figure><h2 id="实际应用场景"><a href="#实际应用场景" class="headerlink" title="实际应用场景"></a>实际应用场景</h2><h3 id="场景1：新功能开发工作流"><a href="#场景1：新功能开发工作流" class="headerlink" title="场景1：新功能开发工作流"></a>场景1：新功能开发工作流</h3><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># 1. 创建新分支并开始开发</span></span><br><span class="line">git checkout -b feat/new-authentication</span><br><span class="line"></span><br><span class="line"><span class="comment"># 2. 编写代码后，使用Claude Skills辅助</span></span><br><span class="line">/lint           <span class="comment"># 检查代码规范</span></span><br><span class="line">/test          <span class="comment"># 运行测试</span></span><br><span class="line">/analyze       <span class="comment"># 分析代码质量</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># 3. 提交代码</span></span><br><span class="line">/commit -m <span class="string">&quot;feat: 添加OAuth2.0认证支持&quot;</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># 4. 创建PR并请求审查</span></span><br><span class="line">/review-pr --target main</span><br></pre></td></tr></table></figure><h3 id="场景2：代码库维护日"><a href="#场景2：代码库维护日" class="headerlink" title="场景2：代码库维护日"></a>场景2：代码库维护日</h3><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># 批量检查和修复代码库问题</span></span><br><span class="line">/lint --fix-all    <span class="comment"># 自动修复所有可自动修复的问题</span></span><br><span class="line">/test --coverage   <span class="comment"># 检查测试覆盖率</span></span><br><span class="line">/analyze --tech-debt  <span class="comment"># 评估技术债务</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># 生成维护报告</span></span><br><span class="line">/report --format markdown &gt; maintenance-report.md</span><br></pre></td></tr></table></figure><h3 id="场景3：团队协作标准化"><a href="#场景3：团队协作标准化" class="headerlink" title="场景3：团队协作标准化"></a>场景3：团队协作标准化</h3><figure class="highlight yaml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># 团队Skills配置</span></span><br><span class="line"><span class="attr">team-skills-config:</span></span><br><span class="line">  <span class="attr">required-checks:</span></span><br><span class="line">    <span class="bullet">-</span> <span class="string">lint</span></span><br><span class="line">    <span class="bullet">-</span> <span class="string">test</span></span><br><span class="line">    <span class="bullet">-</span> <span class="string">security-scan</span></span><br><span class="line">  <span class="attr">commit-standards:</span></span><br><span class="line">    <span class="attr">format:</span> <span class="string">&quot;conventional-commits&quot;</span></span><br><span class="line">    <span class="attr">types:</span> [<span class="string">&quot;feat&quot;</span>, <span class="string">&quot;fix&quot;</span>, <span class="string">&quot;docs&quot;</span>, <span class="string">&quot;style&quot;</span>, <span class="string">&quot;refactor&quot;</span>, <span class="string">&quot;test&quot;</span>, <span class="string">&quot;chore&quot;</span>]</span><br><span class="line">  <span class="attr">review-checklist:</span></span><br><span class="line">    <span class="bullet">-</span> <span class="string">code-quality</span></span><br><span class="line">    <span class="bullet">-</span> <span class="string">test-coverage</span></span><br><span class="line">    <span class="bullet">-</span> <span class="string">security</span></span><br><span class="line">    <span class="bullet">-</span> <span class="string">performance</span></span><br></pre></td></tr></table></figure><h2 id="最佳实践"><a href="#最佳实践" class="headerlink" title="最佳实践"></a>最佳实践</h2><h3 id="1-技能使用策略"><a href="#1-技能使用策略" class="headerlink" title="1. 技能使用策略"></a>1. 技能使用策略</h3><ul><li><strong>渐进式采用</strong>：从最常用的技能开始（如<code>/commit</code>、<code>/lint</code>）</li><li><strong>团队标准化</strong>：统一团队内的技能使用规范</li><li><strong>定期评估</strong>：定期回顾技能使用效果，优化工作流</li></ul><h3 id="2-安全注意事项"><a href="#2-安全注意事项" class="headerlink" title="2. 安全注意事项"></a>2. 安全注意事项</h3><ul><li><strong>权限最小化</strong>：只授予必要的文件系统访问权限</li><li><strong>操作确认</strong>：对于破坏性操作，要求显式确认</li><li><strong>审计日志</strong>：保留所有技能执行记录</li></ul><h3 id="3-性能优化"><a href="#3-性能优化" class="headerlink" title="3. 性能优化"></a>3. 性能优化</h3><ul><li><strong>批量操作</strong>：使用管道组合多个技能</li><li><strong>缓存利用</strong>：充分利用Claude的上下文缓存</li><li><strong>异步执行</strong>：长时间运行的任务使用后台模式</li></ul><h2 id="未来展望"><a href="#未来展望" class="headerlink" title="未来展望"></a>未来展望</h2><h3 id="1-技能生态系统发展"><a href="#1-技能生态系统发展" class="headerlink" title="1. 技能生态系统发展"></a>1. 技能生态系统发展</h3><ul><li><strong>社区技能市场</strong>：用户共享和下载自定义技能</li><li><strong>技能商店</strong>：官方和第三方认证技能</li><li><strong>技能组合包</strong>：针对特定场景的预配置技能组合</li></ul><h3 id="2-智能化增强"><a href="#2-智能化增强" class="headerlink" title="2. 智能化增强"></a>2. 智能化增强</h3><ul><li><strong>预测性技能推荐</strong>：基于工作模式智能推荐技能</li><li><strong>自适应技能优化</strong>：技能根据使用反馈自我改进</li><li><strong>多模态技能扩展</strong>：支持图像、音频等非文本技能</li></ul><h3 id="3-集成扩展"><a href="#3-集成扩展" class="headerlink" title="3. 集成扩展"></a>3. 集成扩展</h3><ul><li><strong>IDE深度集成</strong>：与VS Code、IntelliJ等IDE无缝集成</li><li><strong>CI&#x2F;CD管道集成</strong>：作为CI&#x2F;CD流程的智能节点</li><li><strong>团队协作平台集成</strong>：与Slack、Teams等协作工具集成</li></ul><h2 id="开始使用Claude-Skills"><a href="#开始使用Claude-Skills" class="headerlink" title="开始使用Claude Skills"></a>开始使用Claude Skills</h2><h3 id="安装与配置"><a href="#安装与配置" class="headerlink" title="安装与配置"></a>安装与配置</h3><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># 安装Claude Code CLI</span></span><br><span class="line"><span class="comment"># 参考官方安装文档</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># 查看可用技能</span></span><br><span class="line">claude skills list</span><br><span class="line"></span><br><span class="line"><span class="comment"># 启用特定技能</span></span><br><span class="line">claude skills <span class="built_in">enable</span> commit review-pr lint</span><br><span class="line"></span><br><span class="line"><span class="comment"># 自定义技能配置</span></span><br><span class="line">claude skills config --editor</span><br></pre></td></tr></table></figure><h3 id="学习路径建议"><a href="#学习路径建议" class="headerlink" title="学习路径建议"></a>学习路径建议</h3><ol><li><strong>第一周</strong>：掌握基础技能（<code>/commit</code>, <code>/lint</code>）</li><li><strong>第一个月</strong>：熟练使用代码审查和质量检查技能</li><li><strong>第三个月</strong>：探索高级功能和自定义技能开发</li><li><strong>持续学习</strong>：关注官方更新和社区最佳实践</li></ol><h2 id="结语"><a href="#结语" class="headerlink" title="结语"></a>结语</h2><p>Claude Skills不仅仅是一组工具命令，它代表着AI助手从”对话伙伴”向”协作伙伴”的转变。通过将AI的智能分析与具体的开发操作相结合，Claude Skills正在重新定义开发工作流。</p><p><strong>核心价值</strong>：</p><ul><li><strong>效率提升</strong>：自动化重复性开发任务</li><li><strong>质量保证</strong>：标准化代码审查和质量检查</li><li><strong>知识传承</strong>：固化团队最佳实践</li><li><strong>学习加速</strong>：新成员快速掌握项目规范</li></ul><p>在AI技术快速发展的今天，掌握Claude Skills不仅意味着掌握了更高效的开发工具，更是为适应未来AI增强的开发范式做好准备。</p><blockquote><p><strong>立即行动</strong>：打开你的终端，尝试使用第一个Claude Skill，体验AI增强开发的魅力！</p></blockquote>]]></content>
    
    
      
      
    <summary type="html">&lt;blockquote&gt;
&lt;p&gt;在AI助手日益普及的今天，Claude Skills作为Claude Code CLI的核心功能，正重新定义开发者与AI协作的方式。本文将深入探讨Claude Skills的技术架构、核心功能以及实际应用场景，带你全面了解这一革命性的AI扩展系统。</summary>
      
    
    
    
    <category term="开发工具" scheme="https://xqitw.cn/categories/%E5%BC%80%E5%8F%91%E5%B7%A5%E5%85%B7/"/>
    
    
    <category term="AI" scheme="https://xqitw.cn/tags/AI/"/>
    
    <category term="ClaudeCode" scheme="https://xqitw.cn/tags/ClaudeCode/"/>
    
    <category term="ClaudeSkills" scheme="https://xqitw.cn/tags/ClaudeSkills/"/>
    
  </entry>
  
  <entry>
    <title>Consul 集群部署完全指南</title>
    <link href="https://xqitw.cn/2025/12/05/post-22/"/>
    <id>https://xqitw.cn/2025/12/05/post-22/</id>
    <published>2025-12-05T02:36:27.000Z</published>
    <updated>2026-01-25T23:34:22.000Z</updated>
    
    <content type="html"><![CDATA[<h2 id="概述"><a href="#概述" class="headerlink" title="概述"></a>概述</h2><p>Consul 是一个服务网格解决方案，提供服务发现、配置管理、健康检查等功能。本文将详细介绍如何部署高可用的Consul集群。</p><h2 id="单机模拟集群部署"><a href="#单机模拟集群部署" class="headerlink" title="单机模拟集群部署"></a>单机模拟集群部署</h2><h3 id="Docker-Compose-部署方案"><a href="#Docker-Compose-部署方案" class="headerlink" title="Docker Compose 部署方案"></a>Docker Compose 部署方案</h3><p>使用Docker Compose可以在单机上快速搭建Consul集群进行测试：</p><figure class="highlight yaml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br><span class="line">89</span><br><span class="line">90</span><br><span class="line">91</span><br><span class="line">92</span><br><span class="line">93</span><br><span class="line">94</span><br><span class="line">95</span><br><span class="line">96</span><br><span class="line">97</span><br><span class="line">98</span><br><span class="line">99</span><br><span class="line">100</span><br><span class="line">101</span><br><span class="line">102</span><br><span class="line">103</span><br><span class="line">104</span><br><span class="line">105</span><br><span class="line">106</span><br><span class="line">107</span><br><span class="line">108</span><br><span class="line">109</span><br><span class="line">110</span><br><span class="line">111</span><br></pre></td><td class="code"><pre><span class="line"><span class="attr">services:</span></span><br><span class="line">  <span class="comment"># ======================== Consul Server 节点（3节点高可用集群） ========================</span></span><br><span class="line">  <span class="attr">consul-server-1:</span></span><br><span class="line">    <span class="attr">image:</span> <span class="string">docker.cnb.cool/xqitw/docker/consul:1.22.3</span></span><br><span class="line">    <span class="attr">container_name:</span> <span class="string">consul-server-1</span></span><br><span class="line">    <span class="attr">restart:</span> <span class="string">always</span></span><br><span class="line">    <span class="attr">environment:</span></span><br><span class="line">      <span class="bullet">-</span> <span class="string">TZ=Asia/Shanghai</span></span><br><span class="line">    <span class="attr">volumes:</span></span><br><span class="line">      <span class="bullet">-</span> <span class="string">./consul/data/server1:/consul/data</span></span><br><span class="line">      <span class="bullet">-</span> <span class="string">./consul/config/server1:/consul/config</span></span><br><span class="line">    <span class="attr">command:</span> <span class="string">&gt;</span></span><br><span class="line"><span class="string">      agent -server </span></span><br><span class="line"><span class="string">            -bootstrap-expect=3 </span></span><br><span class="line"><span class="string">            -node=consul-server-1 </span></span><br><span class="line"><span class="string">            -ui </span></span><br><span class="line"><span class="string">            -client=0.0.0.0 </span></span><br><span class="line"><span class="string">            -bind=&#x27;&#123;&#123; GetInterfaceIP &quot;eth0&quot; &#125;&#125;&#x27; </span></span><br><span class="line"><span class="string">            -datacenter=dc1 </span></span><br><span class="line"><span class="string">            -data-dir=/consul/data </span></span><br><span class="line"><span class="string">            -config-dir=/consul/config</span></span><br><span class="line"><span class="string">            -retry-join=consul-server-2</span></span><br><span class="line"><span class="string">            -retry-join=consul-server-3</span></span><br><span class="line"><span class="string"></span>    <span class="attr">ports:</span></span><br><span class="line">      <span class="bullet">-</span> <span class="string">&quot;8501:8500&quot;</span></span><br><span class="line">      <span class="bullet">-</span> <span class="string">&quot;8601:8600/udp&quot;</span></span><br><span class="line"></span><br><span class="line"></span><br><span class="line">  <span class="attr">consul-server-2:</span></span><br><span class="line">    <span class="attr">image:</span> <span class="string">docker.cnb.cool/xqitw/docker/consul:1.22.3</span></span><br><span class="line">    <span class="attr">container_name:</span> <span class="string">consul-server-2</span></span><br><span class="line">    <span class="attr">restart:</span> <span class="string">always</span></span><br><span class="line">    <span class="attr">environment:</span></span><br><span class="line">      <span class="bullet">-</span> <span class="string">TZ=Asia/Shanghai</span></span><br><span class="line">    <span class="attr">volumes:</span></span><br><span class="line">      <span class="bullet">-</span> <span class="string">./consul/data/server2:/consul/data</span></span><br><span class="line">      <span class="bullet">-</span> <span class="string">./consul/config/server2:/consul/config</span></span><br><span class="line">    <span class="attr">command:</span> <span class="string">&gt;</span></span><br><span class="line"><span class="string">      agent -server </span></span><br><span class="line"><span class="string">            -bootstrap-expect=3 </span></span><br><span class="line"><span class="string">            -node=consul-server-2</span></span><br><span class="line"><span class="string">            -ui  </span></span><br><span class="line"><span class="string">            -client=0.0.0.0 </span></span><br><span class="line"><span class="string">            -bind=&#x27;&#123;&#123; GetInterfaceIP &quot;eth0&quot; &#125;&#125;&#x27; </span></span><br><span class="line"><span class="string">            -datacenter=dc1 </span></span><br><span class="line"><span class="string">            -data-dir=/consul/data </span></span><br><span class="line"><span class="string">            -config-dir=/consul/config</span></span><br><span class="line"><span class="string">            -retry-join=consul-server-1</span></span><br><span class="line"><span class="string">            -retry-join=consul-server-3</span></span><br><span class="line"><span class="string"></span>    <span class="attr">ports:</span></span><br><span class="line">      <span class="bullet">-</span> <span class="string">&quot;8502:8500&quot;</span></span><br><span class="line">      <span class="bullet">-</span> <span class="string">&quot;8602:8600/udp&quot;</span></span><br><span class="line"></span><br><span class="line"></span><br><span class="line">  <span class="attr">consul-server-3:</span></span><br><span class="line">    <span class="attr">image:</span> <span class="string">docker.cnb.cool/xqitw/docker/consul:1.22.3</span></span><br><span class="line">    <span class="attr">container_name:</span> <span class="string">consul-server-3</span></span><br><span class="line">    <span class="attr">restart:</span> <span class="string">always</span></span><br><span class="line">    <span class="attr">environment:</span></span><br><span class="line">      <span class="bullet">-</span> <span class="string">TZ=Asia/Shanghai</span></span><br><span class="line">    <span class="attr">volumes:</span></span><br><span class="line">      <span class="bullet">-</span> <span class="string">./consul/data/server3:/consul/data</span></span><br><span class="line">      <span class="bullet">-</span> <span class="string">./consul/config/server3:/consul/config</span></span><br><span class="line">    <span class="attr">command:</span> <span class="string">&gt;</span></span><br><span class="line"><span class="string">      agent -server </span></span><br><span class="line"><span class="string">            -bootstrap-expect=3 </span></span><br><span class="line"><span class="string">            -node=consul-server-3</span></span><br><span class="line"><span class="string">            -ui  </span></span><br><span class="line"><span class="string">            -client=0.0.0.0 </span></span><br><span class="line"><span class="string">            -bind=&#x27;&#123;&#123; GetInterfaceIP &quot;eth0&quot; &#125;&#125;&#x27; </span></span><br><span class="line"><span class="string">            -datacenter=dc1 </span></span><br><span class="line"><span class="string">            -data-dir=/consul/data </span></span><br><span class="line"><span class="string">            -config-dir=/consul/config</span></span><br><span class="line"><span class="string">            -retry-join=consul-server-1</span></span><br><span class="line"><span class="string">            -retry-join=consul-server-2</span></span><br><span class="line"><span class="string"></span>    <span class="attr">ports:</span></span><br><span class="line">      <span class="bullet">-</span> <span class="string">&quot;8503:8500&quot;</span></span><br><span class="line">      <span class="bullet">-</span> <span class="string">&quot;8603:8600/udp&quot;</span></span><br><span class="line"></span><br><span class="line"></span><br><span class="line">  <span class="comment"># ======================== Consul Client 节点（服务注册代理） ========================</span></span><br><span class="line">  <span class="attr">consul-client:</span></span><br><span class="line">    <span class="attr">image:</span> <span class="string">docker.cnb.cool/xqitw/docker/consul:1.22.3</span></span><br><span class="line">    <span class="attr">container_name:</span> <span class="string">consul-client</span></span><br><span class="line">    <span class="attr">restart:</span> <span class="string">always</span></span><br><span class="line">    <span class="attr">environment:</span></span><br><span class="line">      <span class="bullet">-</span> <span class="string">TZ=Asia/Shanghai</span></span><br><span class="line">    <span class="attr">volumes:</span></span><br><span class="line">      <span class="bullet">-</span> <span class="string">./consul/data/client:/consul/data</span></span><br><span class="line">      <span class="bullet">-</span> <span class="string">./consul/config/client:/consul/config</span></span><br><span class="line">    <span class="attr">command:</span> <span class="string">&gt;</span></span><br><span class="line"><span class="string">      agent -node=consul-client</span></span><br><span class="line"><span class="string">            -ui  </span></span><br><span class="line"><span class="string">            -client=0.0.0.0 </span></span><br><span class="line"><span class="string">            -bind=&#x27;&#123;&#123; GetInterfaceIP &quot;eth0&quot; &#125;&#125;&#x27; </span></span><br><span class="line"><span class="string">            -datacenter=dc1 </span></span><br><span class="line"><span class="string">            -data-dir=/consul/data</span></span><br><span class="line"><span class="string">            -config-dir=/consul/config</span></span><br><span class="line"><span class="string">            -retry-join=consul-server-1</span></span><br><span class="line"><span class="string">            -retry-join=consul-server-2 </span></span><br><span class="line"><span class="string">            -retry-join=consul-server-3</span></span><br><span class="line"><span class="string"></span>    <span class="attr">ports:</span></span><br><span class="line">      <span class="bullet">-</span> <span class="string">&quot;8504:8500&quot;</span></span><br><span class="line">      <span class="bullet">-</span> <span class="string">&quot;8604:8600/udp&quot;</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># ======================== 网络配置（容器间通信隔离） ========================</span></span><br><span class="line"><span class="attr">networks:</span></span><br><span class="line">  <span class="attr">default:</span></span><br><span class="line">    <span class="attr">name:</span> <span class="string">consul-server</span></span><br><span class="line">    <span class="attr">enable_ipv6:</span> <span class="literal">true</span></span><br><span class="line">    <span class="comment"># external: true</span></span><br></pre></td></tr></table></figure><h3 id="参数说明"><a href="#参数说明" class="headerlink" title="参数说明"></a>参数说明</h3><ul><li><code>bootstrap-expect=3</code>: 期望加入集群的服务器数量，达到此数量后开始选举</li><li><code>retry-join</code>: 自动加入集群的节点地址</li><li><code>-config-dir</code>: 指定配置文件目录，便于管理复杂配置</li></ul><h2 id="生产环境部署"><a href="#生产环境部署" class="headerlink" title="生产环境部署"></a>生产环境部署</h2><h3 id="多机部署配置"><a href="#多机部署配置" class="headerlink" title="多机部署配置"></a>多机部署配置</h3><p>在生产环境中，建议将节点分散部署在不同物理机上：</p><figure class="highlight yaml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line"><span class="attr">command:</span> <span class="string">&gt;</span></span><br><span class="line"><span class="string">  agent -server </span></span><br><span class="line"><span class="string">        -bootstrap-expect=3 </span></span><br><span class="line"><span class="string">        -node=consul-server-1 </span></span><br><span class="line"><span class="string">        -ui </span></span><br><span class="line"><span class="string">        -client=0.0.0.0 </span></span><br><span class="line"><span class="string">        -bind=&#x27;&#123;&#123; GetInterfaceIP &quot;eth0&quot; &#125;&#125;&#x27; </span></span><br><span class="line"><span class="string">        -datacenter=dc1 </span></span><br><span class="line"><span class="string">        -data-dir=/consul/data </span></span><br><span class="line"><span class="string">        -config-dir=/consul/config</span></span><br><span class="line"><span class="string">        -retry-join=192.168.1.10</span></span><br><span class="line"><span class="string">        -retry-join=192.168.1.11</span></span><br><span class="line"><span class="string">        -retry-join=192.168.1.12</span></span><br></pre></td></tr></table></figure><h3 id="使用配置文件管理节点列表"><a href="#使用配置文件管理节点列表" class="headerlink" title="使用配置文件管理节点列表"></a>使用配置文件管理节点列表</h3><p>为避免在命令行中重复配置大量节点，可使用JSON配置文件：</p><figure class="highlight json"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="punctuation">&#123;</span></span><br><span class="line">  <span class="attr">&quot;retry_join&quot;</span><span class="punctuation">:</span> <span class="punctuation">[</span></span><br><span class="line">    <span class="string">&quot;192.168.1.10&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="string">&quot;192.168.1.11&quot;</span><span class="punctuation">,</span> </span><br><span class="line">    <span class="string">&quot;192.168.1.12&quot;</span></span><br><span class="line">  <span class="punctuation">]</span></span><br><span class="line"><span class="punctuation">&#125;</span></span><br></pre></td></tr></table></figure><h3 id="DNS-SRV记录自动发现"><a href="#DNS-SRV记录自动发现" class="headerlink" title="DNS SRV记录自动发现"></a>DNS SRV记录自动发现</h3><p>在支持DNS SRV记录的环境中，可以实现自动节点发现：</p><figure class="highlight json"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="punctuation">&#123;</span></span><br><span class="line">  <span class="attr">&quot;retry_join&quot;</span><span class="punctuation">:</span> <span class="punctuation">[</span><span class="string">&quot;_consul._tcp.dc1.service.consul&quot;</span><span class="punctuation">]</span></span><br><span class="line"><span class="punctuation">&#125;</span></span><br></pre></td></tr></table></figure><p>对应的DNS SRV记录：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">_consul._tcp.dc1.service.consul. 86400 IN SRV 1 1 8300 consul-server-1</span><br><span class="line">_consul._tcp.dc1.service.consul. 86400 IN SRV 1 1 8300 consul-server-2</span><br><span class="line">_consul._tcp.dc1.service.consul. 86400 IN SRV 1 1 8300 consul-server-3</span><br></pre></td></tr></table></figure><h2 id="安全配置（ACL）"><a href="#安全配置（ACL）" class="headerlink" title="安全配置（ACL）"></a>安全配置（ACL）</h2><h3 id="ACL配置文件"><a href="#ACL配置文件" class="headerlink" title="ACL配置文件"></a>ACL配置文件</h3><p>创建<code>acl.json</code>配置文件：</p><figure class="highlight json"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line"><span class="punctuation">&#123;</span></span><br><span class="line">  <span class="attr">&quot;acl&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">    <span class="attr">&quot;enabled&quot;</span><span class="punctuation">:</span> <span class="literal"><span class="keyword">true</span></span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;default_policy&quot;</span><span class="punctuation">:</span> <span class="string">&quot;deny&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;enable_token_persistence&quot;</span><span class="punctuation">:</span> <span class="literal"><span class="keyword">true</span></span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;tokens&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">      <span class="attr">&quot;master&quot;</span><span class="punctuation">:</span> <span class="string">&quot;your-master-token&quot;</span><span class="punctuation">,</span></span><br><span class="line">      <span class="attr">&quot;agent&quot;</span><span class="punctuation">:</span> <span class="string">&quot;your-agent-token&quot;</span><span class="punctuation">,</span></span><br><span class="line">      <span class="attr">&quot;agent_recovery&quot;</span><span class="punctuation">:</span> <span class="string">&quot;your-agent-recovery-token&quot;</span></span><br><span class="line">    <span class="punctuation">&#125;</span></span><br><span class="line">  <span class="punctuation">&#125;</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;encrypt&quot;</span><span class="punctuation">:</span> <span class="string">&quot;your-gossip-encryption-key&quot;</span></span><br><span class="line"><span class="punctuation">&#125;</span></span><br></pre></td></tr></table></figure><h3 id="Token生成方法"><a href="#Token生成方法" class="headerlink" title="Token生成方法"></a>Token生成方法</h3><p>使用以下命令生成安全token：</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta prompt_"># </span><span class="language-bash">生成gossip加密密钥</span></span><br><span class="line">consul keygen</span><br><span class="line"><span class="meta prompt_"></span></span><br><span class="line"><span class="meta prompt_"># </span><span class="language-bash">生成UUID格式的token</span></span><br><span class="line">uuidgen</span><br><span class="line"><span class="meta prompt_"># </span><span class="language-bash">或者</span></span><br><span class="line">python3 -c &quot;import uuid; print(str(uuid.uuid4()).replace(&#x27;-&#x27;, &#x27;&#x27;))&quot;</span><br></pre></td></tr></table></figure><h3 id="不同Token的作用"><a href="#不同Token的作用" class="headerlink" title="不同Token的作用"></a>不同Token的作用</h3><ul><li><code>master</code>: 用于初始化ACL系统，最高权限</li><li><code>agent</code>: 用于节点间内部通信认证</li><li><code>agent_recovery</code>: 用于紧急恢复访问权限</li><li><code>encrypt</code>: 用于节点间gossip协议加密</li></ul><h2 id="高可用性考虑"><a href="#高可用性考虑" class="headerlink" title="高可用性考虑"></a>高可用性考虑</h2><h3 id="容错能力"><a href="#容错能力" class="headerlink" title="容错能力"></a>容错能力</h3><ul><li>3节点集群可容忍1个节点故障</li><li>5节点集群可容忍2个节点故障</li><li>集群节点数应为奇数以避免脑裂问题</li></ul><h3 id="部署位置"><a href="#部署位置" class="headerlink" title="部署位置"></a>部署位置</h3><ul><li>将节点部署在不同的故障域</li><li>考虑跨可用区部署</li><li>避免单点故障</li></ul><h2 id="ACL配置详解"><a href="#ACL配置详解" class="headerlink" title="ACL配置详解"></a>ACL配置详解</h2><h3 id="启用ACL后的注意事项"><a href="#启用ACL后的注意事项" class="headerlink" title="启用ACL后的注意事项"></a>启用ACL后的注意事项</h3><p>启用ACL后，必须为集群内各节点配置相同的agent token，否则会导致节点间无法正常通信。在docker-compose文件中，需要添加环境变量：</p><figure class="highlight yaml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="attr">environment:</span></span><br><span class="line">  <span class="bullet">-</span> <span class="string">CONSUL_HTTP_TOKEN=your-agent-token-here</span></span><br><span class="line">  <span class="bullet">-</span> <span class="string">CONSUL_RPC_TOKEN=your-agent-token-here</span></span><br></pre></td></tr></table></figure><h3 id="ACL配置文件规范"><a href="#ACL配置文件规范" class="headerlink" title="ACL配置文件规范"></a>ACL配置文件规范</h3><p>注意，gossip通信加密参数<code>encrypt</code>应作为顶级配置项，与<code>acl</code>同级，不能放在<code>acl</code>配置块内：</p><figure class="highlight json"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line"><span class="punctuation">&#123;</span></span><br><span class="line">  <span class="attr">&quot;acl&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">    <span class="attr">&quot;enabled&quot;</span><span class="punctuation">:</span> <span class="literal"><span class="keyword">true</span></span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;default_policy&quot;</span><span class="punctuation">:</span> <span class="string">&quot;deny&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;enable_token_persistence&quot;</span><span class="punctuation">:</span> <span class="literal"><span class="keyword">true</span></span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;tokens&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">      <span class="attr">&quot;master&quot;</span><span class="punctuation">:</span> <span class="string">&quot;your-master-token&quot;</span><span class="punctuation">,</span></span><br><span class="line">      <span class="attr">&quot;agent&quot;</span><span class="punctuation">:</span> <span class="string">&quot;your-agent-token&quot;</span><span class="punctuation">,</span></span><br><span class="line">      <span class="attr">&quot;agent_recovery&quot;</span><span class="punctuation">:</span> <span class="string">&quot;your-agent-recovery-token&quot;</span></span><br><span class="line">    <span class="punctuation">&#125;</span></span><br><span class="line">  <span class="punctuation">&#125;</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;encrypt&quot;</span><span class="punctuation">:</span> <span class="string">&quot;your-gossip-encryption-key&quot;</span>  <span class="comment">// 注意：这是顶级配置项，不在acl块内</span></span><br><span class="line"><span class="punctuation">&#125;</span></span><br></pre></td></tr></table></figure><h2 id="多节点部署优化"><a href="#多节点部署优化" class="headerlink" title="多节点部署优化"></a>多节点部署优化</h2><h3 id="配置文件集中管理"><a href="#配置文件集中管理" class="headerlink" title="配置文件集中管理"></a>配置文件集中管理</h3><p>部署多个Consul节点时，可通过配置文件集中管理retry_join地址，避免命令行重复配置，提高可维护性。</p><h3 id="节点自动发现机制"><a href="#节点自动发现机制" class="headerlink" title="节点自动发现机制"></a>节点自动发现机制</h3><p>使用DNS SRV记录实现Consul节点自动发现，避免在部署多个节点时手动配置大量-retry-join参数，提升可扩展性。</p><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>Consul集群的部署需要注意：</p><ol><li>使用奇数个Server节点确保高可用</li><li>合理配置网络和存储</li><li>实施适当的安全措施（ACL）</li><li>根据环境选择合适的节点发现方式</li><li>定期备份和监控集群状态</li><li>将Consul Server节点部署在不同的物理机器、可用区或网络段，避免单点故障</li><li>正确配置ACL token，确保节点间正常通信</li></ol><p>通过以上配置，可以构建一个安全、可靠的Consul集群。</p>]]></content>
    
    
      
      
    <summary type="html">&lt;h2 id=&quot;概述&quot;&gt;&lt;a href=&quot;#概述&quot; class=&quot;headerlink&quot; title=&quot;概述&quot;&gt;&lt;/a&gt;概述&lt;/h2&gt;&lt;p&gt;Consul 是一个服务网格解决方案，提供服务发现、配置管理、健康检查等功能。本文将详细介绍如何部署高可用的Consul集群。&lt;/p&gt;
&lt;h</summary>
      
    
    
    
    
  </entry>
  
  <entry>
    <title>CNB自定义开发环境</title>
    <link href="https://xqitw.cn/2025/12/02/post-21/"/>
    <id>https://xqitw.cn/2025/12/02/post-21/</id>
    <published>2025-12-02T07:27:52.000Z</published>
    <updated>2025-12-05T02:34:43.000Z</updated>
    
    <content type="html"><![CDATA[<p>一个功能完整的 Docker 开发环境，预装了多个 JetBrains IDE 和常用开发工具。</p><p><a href="https://cnb.cool/xqitw.dev/dev-env">项目地址：https://cnb.cool/xqitw.dev/dev-env</a></p><h2 id="🚀-特性"><a href="#🚀-特性" class="headerlink" title="🚀 特性"></a>🚀 特性</h2><ul><li><strong>多语言支持</strong>: Go、Node.js、Python、Java、Web 开发</li><li><strong>IDE 集成</strong>: 预装 GoLand、IntelliJ IDEA、PyCharm、WebStorm</li><li><strong>版本管理</strong>: g (Go 版本管理器)、nvm (Node.js 版本管理器)</li><li><strong>基于 cnbcool&#x2F;default-dev-env</strong>: 继承了基础开发环境的所有功能</li></ul><h2 id="📦-预装软件"><a href="#📦-预装软件" class="headerlink" title="📦 预装软件"></a>📦 预装软件</h2><h3 id="JetBrains-IDE"><a href="#JetBrains-IDE" class="headerlink" title="JetBrains IDE"></a>JetBrains IDE</h3><ul><li><strong>GoLand</strong> ${KELOVE_GOLAND_VSERSION} - Go 语言开发 IDE</li><li><strong>IntelliJ IDEA</strong> ${KELOVE_IDEA_VSERSION} - Java 集成开发环境</li><li><strong>PyCharm</strong> ${KELOVE_PYCHARM_VSERSION} - Python 开发 IDE</li><li><strong>WebStorm</strong> ${KELOVE_WEBSTORM_VSERSION} - JavaScript&#x2F;TypeScript 开发 IDE</li></ul><h3 id="开发工具"><a href="#开发工具" class="headerlink" title="开发工具"></a>开发工具</h3><ul><li><strong>Go</strong> ${KELOVE_GO_VSERSION} - Go 编程语言</li><li><strong>Node.js</strong> ${KELOVE_NODE_VSERSION} - JavaScript 运行时</li><li><strong>nvm</strong> ${KELOVE_NVM_VSERSION} - Node.js 版本管理器</li><li><strong>g</strong> - Go 版本管理器</li></ul><h2 id="🛠️-使用方法"><a href="#🛠️-使用方法" class="headerlink" title="🛠️ 使用方法"></a>🛠️ 使用方法</h2><p>可以通过在 .cnb.yml 编写远程开发事件流水线，并指定pipeline.docker.image 指定开发环境镜像。</p><figure class="highlight yaml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># .cnb.yml</span></span><br><span class="line"><span class="string">$:</span></span><br><span class="line">  <span class="attr">vscode:</span></span><br><span class="line">    <span class="bullet">-</span> <span class="attr">docker:</span></span><br><span class="line">        <span class="comment"># 可按需替换为其他镜像</span></span><br><span class="line">        <span class="attr">image:</span> <span class="string">docker.cnb.cool/xqitw.dev/dev-env:latest</span></span><br><span class="line">      <span class="attr">services:</span></span><br><span class="line">        <span class="bullet">-</span> <span class="string">vscode</span></span><br><span class="line">        <span class="bullet">-</span> <span class="string">docker</span></span><br><span class="line">      <span class="comment"># 开发环境启动后会执行的任务</span></span><br><span class="line">      <span class="attr">stages:</span></span><br><span class="line">        <span class="bullet">-</span> <span class="attr">name:</span> <span class="string">ls</span></span><br><span class="line">          <span class="attr">script:</span> <span class="string">ls</span> <span class="string">-al</span></span><br></pre></td></tr></table></figure><h3 id="自定义版本"><a href="#自定义版本" class="headerlink" title="自定义版本"></a>自定义版本</h3><p>可以通过构建参数自定义各个软件的版本：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line">docker build \</span><br><span class="line">  --build-arg KELOVE_GOLAND_VSERSION=<span class="string">&quot;2025.2.5&quot;</span> \</span><br><span class="line">  --build-arg KELOVE_IDEA_VSERSION=<span class="string">&quot;2025.2.5&quot;</span> \</span><br><span class="line">  --build-arg KELOVE_PYCHARM_VSERSION=<span class="string">&quot;2025.2.5&quot;</span> \</span><br><span class="line">  --build-arg KELOVE_WEBSTORM_VSERSION=<span class="string">&quot;2025.2.5&quot;</span> \</span><br><span class="line">  --build-arg KELOVE_GO_VSERSION=<span class="string">&quot;1.25.4&quot;</span> \</span><br><span class="line">  --build-arg KELOVE_NODE_VSERSION=<span class="string">&quot;v24.11.1&quot;</span> \</span><br><span class="line">  --build-arg KELOVE_NVM_VSERSION=<span class="string">&quot;v0.40.3&quot;</span> \</span><br><span class="line">  -t custom-dev-env .</span><br></pre></td></tr></table></figure><h2 id="📁-目录结构"><a href="#📁-目录结构" class="headerlink" title="📁 目录结构"></a>📁 目录结构</h2><ul><li><code>/ide_cnb/</code> - JetBrains IDE 安装目录</li><li><code>~/.g/</code> - g 版本管理器配置</li><li><code>~/.nvm/</code> - nvm 版本管理器配置</li></ul><h2 id="🔧-环境变量"><a href="#🔧-环境变量" class="headerlink" title="🔧 环境变量"></a>🔧 环境变量</h2><p>所有 IDE 都安装在 <code>/ide_cnb</code> 目录下，便于自动识别环境中支持的 IDE。</p><h2 id="📝-注意事项"><a href="#📝-注意事项" class="headerlink" title="📝 注意事项"></a>📝 注意事项</h2><ul><li>首次构建需要下载大量文件，请耐心等待</li><li>JetBrains IDE 需要有效的许可证才能使用</li><li>建议配合 Docker Compose 使用以获得更好的开发体验</li></ul><h2 id="🤝-贡献"><a href="#🤝-贡献" class="headerlink" title="🤝 贡献"></a>🤝 贡献</h2><p>欢迎提交 Issue 和 Pull Request 来改进这个开发环境。</p><h2 id="📄-许可证"><a href="#📄-许可证" class="headerlink" title="📄 许可证"></a>📄 许可证</h2><p>本项目采用 MIT 许可证 - 查看 <a href="LICENSE">LICENSE</a> 文件了解详情。</p>]]></content>
    
    
      
      
    <summary type="html">&lt;p&gt;一个功能完整的 Docker 开发环境，预装了多个 JetBrains IDE 和常用开发工具。&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://cnb.cool/xqitw.dev/dev-env&quot;&gt;项目地址：https://cnb.cool/xqitw.dev/dev-</summary>
      
    
    
    
    <category term="开发工具" scheme="https://xqitw.cn/categories/%E5%BC%80%E5%8F%91%E5%B7%A5%E5%85%B7/"/>
    
    
    <category term="docker" scheme="https://xqitw.cn/tags/docker/"/>
    
    <category term="标签:CNB" scheme="https://xqitw.cn/tags/%E6%A0%87%E7%AD%BE-CNB/"/>
    
    <category term="自动化工具" scheme="https://xqitw.cn/tags/%E8%87%AA%E5%8A%A8%E5%8C%96%E5%B7%A5%E5%85%B7/"/>
    
  </entry>
  
  <entry>
    <title>Docker 镜像同步工具</title>
    <link href="https://xqitw.cn/2025/12/01/post-20/"/>
    <id>https://xqitw.cn/2025/12/01/post-20/</id>
    <published>2025-12-01T05:34:44.000Z</published>
    <updated>2025-12-05T02:35:06.000Z</updated>
    
    <content type="html"><![CDATA[<p>这是一个用于将外部 Docker 镜像同步到 CNB 仓库的自动化工具。</p><p><a href="https://cnb.cool/xqitw/tools/docker">项目地址：https://cnb.cool/xqitw/tools/docker</a></p><h2 id="功能特性"><a href="#功能特性" class="headerlink" title="功能特性"></a>功能特性</h2><ul><li>🔄 支持将任意 Docker Hub 或其他仓库的镜像同步到 CNB 私有仓库</li><li>🚀 通过 Web 界面一键触发同步操作</li><li>✅ 自动验证环境变量和镜像格式</li><li>📝 详细的日志输出和错误处理</li><li>🏷️ 智能处理镜像标签（支持默认 latest 标签）</li></ul><h2 id="使用方法"><a href="#使用方法" class="headerlink" title="使用方法"></a>使用方法</h2><h3 id="1-通过-Web-界面同步"><a href="#1-通过-Web-界面同步" class="headerlink" title="1. 通过 Web 界面同步"></a>1. 通过 Web 界面同步</h3><ol><li>在项目页面找到 “Docker镜像同步” 按钮</li><li>点击按钮并输入要同步的镜像地址（如：<code>nginx:1.21</code> 或 <code>docker.io/library/nginx:latest</code>）</li><li>点击确认开始同步</li></ol><h3 id="2-支持的镜像格式"><a href="#2-支持的镜像格式" class="headerlink" title="2. 支持的镜像格式"></a>2. 支持的镜像格式</h3><ul><li><code>nginx:1.21</code> - 同步 nginx 镜像的 1.21 标签</li><li><code>docker.io/library/nginx:latest</code> - 完整的镜像地址</li><li><code>redis</code> - 同步 redis 镜像的 latest 标签（默认）</li></ul><h2 id="工作原理"><a href="#工作原理" class="headerlink" title="工作原理"></a>工作原理</h2><p>工具执行以下步骤：</p><ol><li><strong>环境验证</strong> - 检查必需的环境变量是否设置</li><li><strong>镜像解析</strong> - 提取镜像名称和版本标签</li><li><strong>拉取镜像</strong> - 从源仓库拉取指定镜像</li><li><strong>重新标记</strong> - 将镜像标记为 CNB 仓库格式</li><li><strong>推送镜像</strong> - 将标记后的镜像推送到 CNB 仓库</li></ol><h2 id="环境变量"><a href="#环境变量" class="headerlink" title="环境变量"></a>环境变量</h2><p>工具依赖以下环境变量（由 CNB 平台自动提供）：</p><ul><li><code>DOCKER_FULL_IMAGE</code> - 用户输入的要同步的镜像地址</li><li><code>CNB_DOCKER_REGISTRY</code> - CNB Docker 仓库地址</li><li><code>CNB_REPO_SLUG_LOWERCASE</code> - 项目标识符</li></ul><h2 id="输出示例"><a href="#输出示例" class="headerlink" title="输出示例"></a>输出示例</h2><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line">开始同步 Docker 镜像: nginx:1.21</span><br><span class="line">镜像名称: nginx</span><br><span class="line">镜像版本: 1.21</span><br><span class="line">目标镜像: registry.cnb.io/your-project/nginx:1.21</span><br><span class="line">正在拉取源镜像...</span><br><span class="line">正在标记镜像...</span><br><span class="line">正在推送镜像...</span><br><span class="line">Docker 镜像同步完成!</span><br></pre></td></tr></table></figure><h2 id="错误处理"><a href="#错误处理" class="headerlink" title="错误处理"></a>错误处理</h2><p>工具包含完善的错误处理机制：</p><ul><li>环境变量未设置时会提示具体缺失的变量</li><li>Docker 命令执行失败时会显示详细的错误信息</li><li>每个步骤都有独立的错误检查和退出机制</li></ul><h2 id="注意事项"><a href="#注意事项" class="headerlink" title="注意事项"></a>注意事项</h2><ul><li>确保目标镜像在源仓库中存在</li><li>需要有足够的权限访问 CNB Docker 仓库</li><li>大型镜像同步可能需要较长时间</li><li>建议在非高峰时段进行大量镜像同步</li></ul><h2 id="配置文件"><a href="#配置文件" class="headerlink" title="配置文件"></a>配置文件</h2><ul><li><code>.cnb.yml</code> - 主要的 CI&#x2F;CD 配置文件，定义同步流程</li><li><code>.cnb/web_trigger.yml</code> - Web 触发器配置，定义用户界面</li></ul><h2 id="许可证"><a href="#许可证" class="headerlink" title="许可证"></a>许可证</h2><p>本项目采用 MIT 许可证。</p>]]></content>
    
    
      
      
    <summary type="html">&lt;p&gt;这是一个用于将外部 Docker 镜像同步到 CNB 仓库的自动化工具。&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://cnb.cool/xqitw/tools/docker&quot;&gt;项目地址：https://cnb.cool/xqitw/tools/docker&lt;/a&gt;&lt;/p</summary>
      
    
    
    
    
    <category term="docker" scheme="https://xqitw.cn/tags/docker/"/>
    
    <category term="标签:CNB" scheme="https://xqitw.cn/tags/%E6%A0%87%E7%AD%BE-CNB/"/>
    
    <category term="自动化工具" scheme="https://xqitw.cn/tags/%E8%87%AA%E5%8A%A8%E5%8C%96%E5%B7%A5%E5%85%B7/"/>
    
    <category term="镜像同步" scheme="https://xqitw.cn/tags/%E9%95%9C%E5%83%8F%E5%90%8C%E6%AD%A5/"/>
    
  </entry>
  
  <entry>
    <title>Python 並集union, 交集intersection, 差集difference</title>
    <link href="https://xqitw.cn/2025/11/30/post-16/"/>
    <id>https://xqitw.cn/2025/11/30/post-16/</id>
    <published>2025-11-30T06:01:54.000Z</published>
    <updated>2025-12-05T02:35:03.000Z</updated>
    
    <content type="html"><![CDATA[<h2 id="并集"><a href="#并集" class="headerlink" title="并集"></a>并集</h2><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line">a=[<span class="number">1</span>,<span class="number">3</span>,<span class="number">5</span>]</span><br><span class="line">b=[<span class="number">1</span>,<span class="number">2</span>,<span class="number">3</span>]</span><br><span class="line"></span><br><span class="line"><span class="comment"># 并集</span></span><br><span class="line"><span class="built_in">set</span>(a) | <span class="built_in">set</span>(b)</span><br><span class="line"><span class="comment"># 或者</span></span><br><span class="line"><span class="built_in">set</span>(a).union(b)</span><br><span class="line"></span><br><span class="line"><span class="built_in">set</span>([<span class="number">1</span>, <span class="number">2</span>, <span class="number">3</span>, <span class="number">5</span>])</span><br></pre></td></tr></table></figure><h2 id="交集"><a href="#交集" class="headerlink" title="交集"></a>交集</h2><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line">a=[<span class="number">1</span>,<span class="number">3</span>,<span class="number">5</span>]</span><br><span class="line">b=[<span class="number">1</span>,<span class="number">2</span>,<span class="number">3</span>]</span><br><span class="line"></span><br><span class="line"><span class="comment"># 交集</span></span><br><span class="line"><span class="built_in">set</span>(a) &amp; <span class="built_in">set</span>(b)</span><br><span class="line"><span class="comment"># 或者</span></span><br><span class="line"><span class="built_in">set</span>(a).intersection(b)</span><br><span class="line"></span><br><span class="line"><span class="built_in">set</span>([<span class="number">1</span>, <span class="number">3</span>])</span><br></pre></td></tr></table></figure><h2 id="差集"><a href="#差集" class="headerlink" title="差集"></a>差集</h2><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line">a=[<span class="number">1</span>,<span class="number">3</span>,<span class="number">5</span>]</span><br><span class="line">b=[<span class="number">1</span>,<span class="number">2</span>,<span class="number">3</span>]</span><br><span class="line"></span><br><span class="line"><span class="comment"># 差集</span></span><br><span class="line"><span class="built_in">set</span>(a) - <span class="built_in">set</span>(b)</span><br><span class="line"><span class="comment"># 或者</span></span><br><span class="line"><span class="built_in">set</span>(a).difference(b)</span><br><span class="line"></span><br><span class="line"><span class="built_in">set</span>([<span class="number">5</span>])</span><br></pre></td></tr></table></figure><h2 id="对称差集"><a href="#对称差集" class="headerlink" title="对称差集"></a>对称差集</h2><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line">a=[<span class="number">1</span>,<span class="number">3</span>,<span class="number">5</span>]</span><br><span class="line">b=[<span class="number">1</span>,<span class="number">2</span>,<span class="number">3</span>]</span><br><span class="line"></span><br><span class="line"><span class="comment"># 对称差集</span></span><br><span class="line"><span class="built_in">set</span>(a) ^ <span class="built_in">set</span>(b)</span><br><span class="line"><span class="comment"># 或者</span></span><br><span class="line"><span class="built_in">set</span>(a).symmetric_difference(b)</span><br><span class="line"></span><br><span class="line"><span class="built_in">set</span>([<span class="number">2</span>, <span class="number">5</span>])</span><br></pre></td></tr></table></figure>]]></content>
    
    
      
      
    <summary type="html">&lt;h2 id=&quot;并集&quot;&gt;&lt;a href=&quot;#并集&quot; class=&quot;headerlink&quot; title=&quot;并集&quot;&gt;&lt;/a&gt;并集&lt;/h2&gt;&lt;figure class=&quot;highlight python&quot;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&quot;gutter&quot;&gt;&lt;pre&gt;&lt;span</summary>
      
    
    
    
    <category term="代码笔记" scheme="https://xqitw.cn/categories/%E4%BB%A3%E7%A0%81%E7%AC%94%E8%AE%B0/"/>
    
    
    <category term="python" scheme="https://xqitw.cn/tags/python/"/>
    
  </entry>
  
  <entry>
    <title>python包管理工具pip的使用</title>
    <link href="https://xqitw.cn/2025/11/30/post-12/"/>
    <id>https://xqitw.cn/2025/11/30/post-12/</id>
    <published>2025-11-30T05:27:00.000Z</published>
    <updated>2025-12-05T02:35:00.000Z</updated>
    
    <content type="html"><![CDATA[<h2 id="更新包管理工具pip"><a href="#更新包管理工具pip" class="headerlink" title="更新包管理工具pip"></a>更新包管理工具pip</h2><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">python -m pip install --upgrade pip</span><br></pre></td></tr></table></figure><h2 id="使用pip安装一个依赖包"><a href="#使用pip安装一个依赖包" class="headerlink" title="使用pip安装一个依赖包"></a>使用pip安装一个依赖包</h2><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">pip install packagename</span><br></pre></td></tr></table></figure><h2 id="使用pip更新一个依赖包"><a href="#使用pip更新一个依赖包" class="headerlink" title="使用pip更新一个依赖包"></a>使用pip更新一个依赖包</h2><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">pip install --upgrade packagename</span><br></pre></td></tr></table></figure><h2 id="下切换源"><a href="#下切换源" class="headerlink" title="下切换源"></a>下切换源</h2><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">pip config set global.index-url https://mirrors.aliyun.com/pypi/simple</span><br></pre></td></tr></table></figure>]]></content>
    
    
      
      
    <summary type="html">&lt;h2 id=&quot;更新包管理工具pip&quot;&gt;&lt;a href=&quot;#更新包管理工具pip&quot; class=&quot;headerlink&quot; title=&quot;更新包管理工具pip&quot;&gt;&lt;/a&gt;更新包管理工具pip&lt;/h2&gt;&lt;figure class=&quot;highlight shell&quot;&gt;&lt;table&gt;&lt;t</summary>
      
    
    
    
    <category term="代码工具" scheme="https://xqitw.cn/categories/%E4%BB%A3%E7%A0%81%E5%B7%A5%E5%85%B7/"/>
    
    
    <category term="pip" scheme="https://xqitw.cn/tags/pip/"/>
    
    <category term="python" scheme="https://xqitw.cn/tags/python/"/>
    
  </entry>
  
  <entry>
    <title>Git 相关配置</title>
    <link href="https://xqitw.cn/2025/11/30/post-10/"/>
    <id>https://xqitw.cn/2025/11/30/post-10/</id>
    <published>2025-11-30T03:10:58.000Z</published>
    <updated>2025-12-05T02:34:57.000Z</updated>
    
    <content type="html"><![CDATA[<h3 id="配置用户名和邮箱"><a href="#配置用户名和邮箱" class="headerlink" title="配置用户名和邮箱"></a>配置用户名和邮箱</h3><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">// 配置用户名</span><br><span class="line">git config --global user.name &quot;username&quot;</span><br><span class="line">// 配置邮箱</span><br><span class="line">git config --global user.email &quot;xxx.@xx.com&quot;</span><br></pre></td></tr></table></figure><h3 id="生成-SSH-KEY"><a href="#生成-SSH-KEY" class="headerlink" title="生成 SSH KEY"></a>生成 SSH KEY</h3><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">ssh-keygen -t rsa -C &quot;xxx.@xx.com&quot;</span><br></pre></td></tr></table></figure><h3 id="忽略文件权限或文件拥有者的改变"><a href="#忽略文件权限或文件拥有者的改变" class="headerlink" title="忽略文件权限或文件拥有者的改变"></a>忽略文件权限或文件拥有者的改变</h3><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"># 当前版本库</span><br><span class="line">git config core.filemode false</span><br><span class="line"># 全局配置</span><br><span class="line">git config --global core.fileMode false</span><br></pre></td></tr></table></figure>]]></content>
    
    
      
      
    <summary type="html">&lt;h3 id=&quot;配置用户名和邮箱&quot;&gt;&lt;a href=&quot;#配置用户名和邮箱&quot; class=&quot;headerlink&quot; title=&quot;配置用户名和邮箱&quot;&gt;&lt;/a&gt;配置用户名和邮箱&lt;/h3&gt;&lt;figure class=&quot;highlight shell&quot;&gt;&lt;table&gt;&lt;tr&gt;&lt;td cl</summary>
      
    
    
    
    <category term="代码笔记" scheme="https://xqitw.cn/categories/%E4%BB%A3%E7%A0%81%E7%AC%94%E8%AE%B0/"/>
    
    
    <category term="git" scheme="https://xqitw.cn/tags/git/"/>
    
    <category term="ssh" scheme="https://xqitw.cn/tags/ssh/"/>
    
  </entry>
  
  <entry>
    <title>Hexo文章生成工具</title>
    <link href="https://xqitw.cn/2025/11/30/post-4/"/>
    <id>https://xqitw.cn/2025/11/30/post-4/</id>
    <published>2025-11-29T21:19:15.000Z</published>
    <updated>2025-12-05T02:52:42.000Z</updated>
    
    <content type="html"><![CDATA[<p>这个工具用于从 CNB Cool API 获取 issues 数据并自动生成 Hexo 博客文章。</p><h2 id="功能特性"><a href="#功能特性" class="headerlink" title="功能特性"></a>功能特性</h2><ul><li>🔄 自动分页获取所有 issues</li><li>📝 获取 issue 详情（包含 body 内容）</li><li>🏷️ 智能解析标签：以”分类:”开头的标签转为分类，其他为普通标签</li><li>👤 自动提取作者、时间等信息</li><li>⏱️ 内置1秒延迟防止API限制</li><li>📁 生成标准 Hexo 文章格式（文件名：post-{number}.md）</li><li>🔄 覆盖已存在文件</li><li>⚙️ 可配置API地址</li><li>🔗 支持友情链接页面和详情处理</li><li>📋 解析多个友情链接代码块并自动更新数据文件</li></ul><h2 id="安装和使用"><a href="#安装和使用" class="headerlink" title="安装和使用"></a>安装和使用</h2><h3 id="1-安装依赖"><a href="#1-安装依赖" class="headerlink" title="1. 安装依赖"></a>1. 安装依赖</h3><p>首先安装dotenv包（用于加载.env文件）：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">cd</span> blog</span><br><span class="line">npm install dotenv</span><br></pre></td></tr></table></figure><h3 id="2-设置Token"><a href="#2-设置Token" class="headerlink" title="2. 设置Token"></a>2. 设置Token</h3><p><strong>方法一：使用.env文件（推荐）</strong></p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># 在blog目录创建.env文件</span></span><br><span class="line"><span class="built_in">cp</span> ../.env.example .<span class="built_in">env</span></span><br><span class="line"><span class="comment"># 编辑.env文件，填入你的token和API地址</span></span><br></pre></td></tr></table></figure><p>.env文件示例：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">BLOG_CNB_ISSUE_TOKEN=your_token_here</span><br><span class="line">BLOG_CNB_ISSUE_API_URL=https://api.cnb.cool/xqitw/blog/-/issues</span><br></pre></td></tr></table></figure><p><strong>方法二：设置环境变量</strong></p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">export</span> BLOG_CNB_ISSUE_TOKEN=<span class="string">&quot;your_token_here&quot;</span></span><br><span class="line"><span class="built_in">export</span> BLOG_CNB_ISSUE_API_URL=<span class="string">&quot;https://api.cnb.cool/xqitw/blog/-/issues&quot;</span></span><br></pre></td></tr></table></figure><p><strong>方法三：直接修改代码</strong><br>在 <code>generate-posts.js</code> 中直接修改 <code>CONFIG</code> 对象。</p><h3 id="2-运行工具"><a href="#2-运行工具" class="headerlink" title="2. 运行工具"></a>2. 运行工具</h3><p>在 <code>blog</code> 目录下运行：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">npm run fetch-posts</span><br></pre></td></tr></table></figure><p>或者直接运行：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">node generate-posts.js</span><br></pre></td></tr></table></figure><h2 id="生成的文章格式"><a href="#生成的文章格式" class="headerlink" title="生成的文章格式"></a>生成的文章格式</h2><p>每篇文章都会生成标准的 Hexo front matter：</p><figure class="highlight yaml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">---</span></span><br><span class="line"><span class="attr">title:</span> <span class="string">Issue标题</span></span><br><span class="line"><span class="attr">date:</span> <span class="number">2024-01-01T00:00:00.000Z</span></span><br><span class="line"><span class="attr">updated:</span> <span class="number">2024-01-01T00:00:00.000Z</span></span><br><span class="line"><span class="attr">author:</span> <span class="string">作者昵称</span></span><br><span class="line"><span class="attr">author_username:</span> <span class="string">作者用户名</span></span><br><span class="line"><span class="attr">categories:</span> [<span class="string">&quot;个人笔记&quot;</span>]  <span class="comment"># 如果有&quot;分类:个人笔记&quot;标签</span></span><br><span class="line"><span class="attr">tags:</span> [<span class="string">&quot;普通标签1&quot;</span>, <span class="string">&quot;普通标签2&quot;</span>]  <span class="comment"># 普通标签</span></span><br><span class="line"><span class="attr">comment_count:</span> <span class="number">0</span></span><br><span class="line"><span class="attr">issue_number:</span> <span class="number">123</span></span><br><span class="line"><span class="attr">state:</span> <span class="string">open</span></span><br><span class="line"><span class="meta">---</span></span><br><span class="line"><span class="meta"></span></span><br><span class="line"><span class="string">Issue内容...</span></span><br></pre></td></tr></table></figure><h2 id="标签和分类处理"><a href="#标签和分类处理" class="headerlink" title="标签和分类处理"></a>标签和分类处理</h2><p>工具会智能处理issue标签：</p><ul><li><strong>分类标签</strong>：以 <code>分类:</code> 开头的标签 → 转为Hexo分类（categories），自动移除前缀</li><li><strong>普通标签</strong>：其他所有标签 → 保持为Hexo标签（tags）</li></ul><h3 id="示例"><a href="#示例" class="headerlink" title="示例"></a>示例</h3><p>如果issue有以下标签：</p><ul><li><code>分类:个人笔记</code> → 分类：<code>个人笔记</code></li><li><code>分类:技术分享</code> → 分类：<code>技术分享</code></li><li><code>JavaScript</code> → 标签：<code>JavaScript</code></li><li><code>前端</code> → 标签：<code>前端</code></li></ul><p>生成的front matter：</p><figure class="highlight yaml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="attr">categories:</span> [<span class="string">&quot;个人笔记&quot;</span>, <span class="string">&quot;技术分享&quot;</span>]</span><br><span class="line"><span class="attr">tags:</span> [<span class="string">&quot;JavaScript&quot;</span>, <span class="string">&quot;前端&quot;</span>]</span><br></pre></td></tr></table></figure><h2 id="友情链接处理"><a href="#友情链接处理" class="headerlink" title="友情链接处理"></a>友情链接处理</h2><p>工具支持自动处理友情链接相关的issues：</p><h3 id="友情链接页面"><a href="#友情链接页面" class="headerlink" title="友情链接页面"></a>友情链接页面</h3><p>当issue标题为”友情链接”时，会生成到 <code>source/friend/index.md</code>，包含标准的Hexo front matter和页面内容。</p><h3 id="友情链接详情"><a href="#友情链接详情" class="headerlink" title="友情链接详情"></a>友情链接详情</h3><p>当issue标题为”友情链接详情”时，工具会解析issue body中的所有代码块，提取友情链接信息并更新 <code>source/friend/_data.yml</code> 文件。</p><h4 id="支持的格式"><a href="#支持的格式" class="headerlink" title="支持的格式"></a>支持的格式</h4><p>代码块中的友情链接信息应按以下格式填写：</p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">站点名称：小强IT屋</span><br><span class="line"></span><br><span class="line">站点地址：https://xqitw.cn</span><br><span class="line"></span><br><span class="line">站点描述：IT小强的个人博客，这是一间开源分享的实验室，也是每位技术同路人的交流驿站!</span><br><span class="line"></span><br><span class="line">站点头像：https://xqitw.cn/favicon.ico</span><br></pre></td></tr></table></figure><h4 id="转换规则"><a href="#转换规则" class="headerlink" title="转换规则"></a>转换规则</h4><ul><li>支持中英文冒号（：和:）</li><li>自动解析多个代码块</li><li>站点名称和站点地址为必需字段</li><li>站点描述和站点头像为可选字段</li><li>自动去重，相同名称的链接会被更新</li></ul><h4 id="生成的YAML格式"><a href="#生成的YAML格式" class="headerlink" title="生成的YAML格式"></a>生成的YAML格式</h4><figure class="highlight yaml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="bullet">-</span> <span class="attr">name:</span> <span class="string">小强IT屋</span></span><br><span class="line">  <span class="attr">url:</span> <span class="string">https://xqitw.cn</span></span><br><span class="line">  <span class="attr">desc:</span> <span class="string">IT小强的个人博客，这是一间开源分享的实验室，也是每位技术同路人的交流驿站!</span></span><br><span class="line">  <span class="attr">image:</span> <span class="string">https://xqitw.cn/favicon.ico</span></span><br></pre></td></tr></table></figure><h2 id="文件命名规则"><a href="#文件命名规则" class="headerlink" title="文件命名规则"></a>文件命名规则</h2><ul><li><strong>普通文章</strong>：<code>post-{issue_number}.md</code>（例如：<code>post-123.md</code>）</li><li><strong>关于页面</strong>：当issue标题为”关于”时，生成到 <code>about/index.md</code></li><li><strong>友情链接页面</strong>：当issue标题为”友情链接”时，生成到 <code>friend/index.md</code></li><li><strong>友情链接详情</strong>：当issue标题为”友情链接详情”时，解析body中的代码块并更新 <code>friend/_data.yml</code></li></ul><h2 id="配置选项"><a href="#配置选项" class="headerlink" title="配置选项"></a>配置选项</h2><h3 id="环境变量配置"><a href="#环境变量配置" class="headerlink" title="环境变量配置"></a>环境变量配置</h3><ul><li><code>BLOG_CNB_ISSUE_TOKEN</code>: API认证token（必需）</li><li><code>BLOG_CNB_ISSUE_API_URL</code>: API基础URL</li></ul><h3 id="代码配置"><a href="#代码配置" class="headerlink" title="代码配置"></a>代码配置</h3><p>在 <code>generate-posts.js</code> 中的 <code>CONFIG</code> 对象可以修改：</p><ul><li><code>postsDir</code>: 文章保存目录</li><li><code>pageSize</code>: 每页获取的issue数量</li><li><code>delay</code>: API调用间隔（毫秒）</li></ul><h2 id="注意事项"><a href="#注意事项" class="headerlink" title="注意事项"></a>注意事项</h2><ol><li><strong>API限制</strong>: 工具内置了1秒延迟防止被API限制</li><li><strong>文件覆盖</strong>: 已存在的文章会被自动覆盖</li><li><strong>错误处理</strong>: 单个文章获取失败不会影响其他文章</li><li><strong>Token安全</strong>: 建议使用环境变量而不是硬编码token</li></ol><h2 id="输出示例"><a href="#输出示例" class="headerlink" title="输出示例"></a>输出示例</h2><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br></pre></td><td class="code"><pre><span class="line">🚀 开始获取issues并生成Hexo文章...</span><br><span class="line">🔗 API地址: https://api.cnb.cool/xqitw/blog/-/issues</span><br><span class="line">📁 文章保存目录: /path/to/blog/source/_posts</span><br><span class="line">🏷️  分类规则: 以&quot;分类:&quot;开头的标签将作为分类（自动移除前缀）</span><br><span class="line">📄 第 1 页找到 25 个issues</span><br><span class="line">获取issue #123 详情...</span><br><span class="line">🔄 覆盖已存在的文章: post-123.md</span><br><span class="line">获取issue #124 详情...</span><br><span class="line">✅ 已创建文章: post-124.md</span><br><span class="line">获取issue #125 详情...</span><br><span class="line">📄 检测到关于页面，将生成到: about/index.md</span><br><span class="line">✅ 已创建文件: index.md</span><br><span class="line">获取issue #126 详情...</span><br><span class="line">📄 检测到友情链接页面，将生成到: friend/index.md</span><br><span class="line">✅ 已创建文件: index.md</span><br><span class="line">获取issue #127 详情...</span><br><span class="line">✅ 已处理 2 个友情链接</span><br><span class="line">➕ 已添加友情链接: 小强IT屋</span><br><span class="line">🔄 已更新友情链接: CNB</span><br><span class="line">...</span><br><span class="line">✅ 没有更多issues，处理完成</span><br><span class="line"></span><br><span class="line">📊 处理完成统计:</span><br><span class="line">✅ 成功处理: 20 篇文章</span><br><span class="line">⏭️  跳过已存在: 0 篇文章</span><br><span class="line">❌ 处理失败: 0 篇文章</span><br><span class="line">📁 文章保存在: /path/to/blog/source/_posts</span><br></pre></td></tr></table></figure><h2 id="故障排除"><a href="#故障排除" class="headerlink" title="故障排除"></a>故障排除</h2><h3 id="Token错误"><a href="#Token错误" class="headerlink" title="Token错误"></a>Token错误</h3><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">❌ 请设置环境变量 BLOG_CNB_ISSUE_TOKEN 或修改CONFIG.token</span><br></pre></td></tr></table></figure><p>确保设置了正确的API token。</p><h3 id="网络错误"><a href="#网络错误" class="headerlink" title="网络错误"></a>网络错误</h3><p>检查网络连接和API地址是否正确。</p><h3 id="权限错误"><a href="#权限错误" class="headerlink" title="权限错误"></a>权限错误</h3><p>确保文章目录有写入权限。</p><h2 id="完整代码"><a href="#完整代码" class="headerlink" title="完整代码"></a>完整代码</h2><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br><span class="line">89</span><br><span class="line">90</span><br><span class="line">91</span><br><span class="line">92</span><br><span class="line">93</span><br><span class="line">94</span><br><span class="line">95</span><br><span class="line">96</span><br><span class="line">97</span><br><span class="line">98</span><br><span class="line">99</span><br><span class="line">100</span><br><span class="line">101</span><br><span class="line">102</span><br><span class="line">103</span><br><span class="line">104</span><br><span class="line">105</span><br><span class="line">106</span><br><span class="line">107</span><br><span class="line">108</span><br><span class="line">109</span><br><span class="line">110</span><br><span class="line">111</span><br><span class="line">112</span><br><span class="line">113</span><br><span class="line">114</span><br><span class="line">115</span><br><span class="line">116</span><br><span class="line">117</span><br><span class="line">118</span><br><span class="line">119</span><br><span class="line">120</span><br><span class="line">121</span><br><span class="line">122</span><br><span class="line">123</span><br><span class="line">124</span><br><span class="line">125</span><br><span class="line">126</span><br><span class="line">127</span><br><span class="line">128</span><br><span class="line">129</span><br><span class="line">130</span><br><span class="line">131</span><br><span class="line">132</span><br><span class="line">133</span><br><span class="line">134</span><br><span class="line">135</span><br><span class="line">136</span><br><span class="line">137</span><br><span class="line">138</span><br><span class="line">139</span><br><span class="line">140</span><br><span class="line">141</span><br><span class="line">142</span><br><span class="line">143</span><br><span class="line">144</span><br><span class="line">145</span><br><span class="line">146</span><br><span class="line">147</span><br><span class="line">148</span><br><span class="line">149</span><br><span class="line">150</span><br><span class="line">151</span><br><span class="line">152</span><br><span class="line">153</span><br><span class="line">154</span><br><span class="line">155</span><br><span class="line">156</span><br><span class="line">157</span><br><span class="line">158</span><br><span class="line">159</span><br><span class="line">160</span><br><span class="line">161</span><br><span class="line">162</span><br><span class="line">163</span><br><span class="line">164</span><br><span class="line">165</span><br><span class="line">166</span><br><span class="line">167</span><br><span class="line">168</span><br><span class="line">169</span><br><span class="line">170</span><br><span class="line">171</span><br><span class="line">172</span><br><span class="line">173</span><br><span class="line">174</span><br><span class="line">175</span><br><span class="line">176</span><br><span class="line">177</span><br><span class="line">178</span><br><span class="line">179</span><br><span class="line">180</span><br><span class="line">181</span><br><span class="line">182</span><br><span class="line">183</span><br><span class="line">184</span><br><span class="line">185</span><br><span class="line">186</span><br><span class="line">187</span><br><span class="line">188</span><br><span class="line">189</span><br><span class="line">190</span><br><span class="line">191</span><br><span class="line">192</span><br><span class="line">193</span><br><span class="line">194</span><br><span class="line">195</span><br><span class="line">196</span><br><span class="line">197</span><br><span class="line">198</span><br><span class="line">199</span><br><span class="line">200</span><br><span class="line">201</span><br><span class="line">202</span><br><span class="line">203</span><br><span class="line">204</span><br><span class="line">205</span><br><span class="line">206</span><br><span class="line">207</span><br><span class="line">208</span><br><span class="line">209</span><br><span class="line">210</span><br><span class="line">211</span><br><span class="line">212</span><br><span class="line">213</span><br><span class="line">214</span><br><span class="line">215</span><br><span class="line">216</span><br><span class="line">217</span><br><span class="line">218</span><br><span class="line">219</span><br><span class="line">220</span><br><span class="line">221</span><br><span class="line">222</span><br><span class="line">223</span><br><span class="line">224</span><br><span class="line">225</span><br><span class="line">226</span><br><span class="line">227</span><br><span class="line">228</span><br><span class="line">229</span><br><span class="line">230</span><br><span class="line">231</span><br><span class="line">232</span><br><span class="line">233</span><br><span class="line">234</span><br><span class="line">235</span><br><span class="line">236</span><br><span class="line">237</span><br><span class="line">238</span><br><span class="line">239</span><br><span class="line">240</span><br><span class="line">241</span><br><span class="line">242</span><br><span class="line">243</span><br><span class="line">244</span><br><span class="line">245</span><br><span class="line">246</span><br><span class="line">247</span><br><span class="line">248</span><br><span class="line">249</span><br><span class="line">250</span><br><span class="line">251</span><br><span class="line">252</span><br><span class="line">253</span><br><span class="line">254</span><br><span class="line">255</span><br><span class="line">256</span><br><span class="line">257</span><br><span class="line">258</span><br><span class="line">259</span><br><span class="line">260</span><br><span class="line">261</span><br><span class="line">262</span><br><span class="line">263</span><br><span class="line">264</span><br><span class="line">265</span><br><span class="line">266</span><br><span class="line">267</span><br><span class="line">268</span><br><span class="line">269</span><br><span class="line">270</span><br><span class="line">271</span><br><span class="line">272</span><br><span class="line">273</span><br><span class="line">274</span><br><span class="line">275</span><br><span class="line">276</span><br><span class="line">277</span><br><span class="line">278</span><br><span class="line">279</span><br><span class="line">280</span><br><span class="line">281</span><br><span class="line">282</span><br><span class="line">283</span><br><span class="line">284</span><br><span class="line">285</span><br><span class="line">286</span><br><span class="line">287</span><br><span class="line">288</span><br><span class="line">289</span><br><span class="line">290</span><br><span class="line">291</span><br><span class="line">292</span><br><span class="line">293</span><br><span class="line">294</span><br><span class="line">295</span><br><span class="line">296</span><br><span class="line">297</span><br><span class="line">298</span><br><span class="line">299</span><br><span class="line">300</span><br><span class="line">301</span><br><span class="line">302</span><br><span class="line">303</span><br><span class="line">304</span><br><span class="line">305</span><br><span class="line">306</span><br><span class="line">307</span><br><span class="line">308</span><br><span class="line">309</span><br><span class="line">310</span><br><span class="line">311</span><br><span class="line">312</span><br><span class="line">313</span><br><span class="line">314</span><br><span class="line">315</span><br><span class="line">316</span><br><span class="line">317</span><br><span class="line">318</span><br><span class="line">319</span><br><span class="line">320</span><br><span class="line">321</span><br><span class="line">322</span><br><span class="line">323</span><br><span class="line">324</span><br><span class="line">325</span><br><span class="line">326</span><br><span class="line">327</span><br><span class="line">328</span><br><span class="line">329</span><br><span class="line">330</span><br><span class="line">331</span><br><span class="line">332</span><br><span class="line">333</span><br><span class="line">334</span><br><span class="line">335</span><br><span class="line">336</span><br><span class="line">337</span><br><span class="line">338</span><br><span class="line">339</span><br><span class="line">340</span><br><span class="line">341</span><br><span class="line">342</span><br><span class="line">343</span><br><span class="line">344</span><br><span class="line">345</span><br><span class="line">346</span><br><span class="line">347</span><br><span class="line">348</span><br><span class="line">349</span><br><span class="line">350</span><br><span class="line">351</span><br><span class="line">352</span><br><span class="line">353</span><br><span class="line">354</span><br><span class="line">355</span><br><span class="line">356</span><br><span class="line">357</span><br><span class="line">358</span><br><span class="line">359</span><br><span class="line">360</span><br><span class="line">361</span><br><span class="line">362</span><br><span class="line">363</span><br><span class="line">364</span><br><span class="line">365</span><br><span class="line">366</span><br><span class="line">367</span><br><span class="line">368</span><br><span class="line">369</span><br><span class="line">370</span><br><span class="line">371</span><br><span class="line">372</span><br><span class="line">373</span><br><span class="line">374</span><br><span class="line">375</span><br><span class="line">376</span><br><span class="line">377</span><br><span class="line">378</span><br><span class="line">379</span><br><span class="line">380</span><br><span class="line">381</span><br><span class="line">382</span><br><span class="line">383</span><br><span class="line">384</span><br><span class="line">385</span><br><span class="line">386</span><br><span class="line">387</span><br><span class="line">388</span><br><span class="line">389</span><br><span class="line">390</span><br><span class="line">391</span><br><span class="line">392</span><br><span class="line">393</span><br><span class="line">394</span><br><span class="line">395</span><br><span class="line">396</span><br><span class="line">397</span><br><span class="line">398</span><br><span class="line">399</span><br><span class="line">400</span><br><span class="line">401</span><br><span class="line">402</span><br><span class="line">403</span><br><span class="line">404</span><br><span class="line">405</span><br><span class="line">406</span><br><span class="line">407</span><br><span class="line">408</span><br><span class="line">409</span><br><span class="line">410</span><br><span class="line">411</span><br><span class="line">412</span><br><span class="line">413</span><br><span class="line">414</span><br><span class="line">415</span><br><span class="line">416</span><br><span class="line">417</span><br><span class="line">418</span><br><span class="line">419</span><br><span class="line">420</span><br><span class="line">421</span><br><span class="line">422</span><br><span class="line">423</span><br><span class="line">424</span><br><span class="line">425</span><br><span class="line">426</span><br><span class="line">427</span><br><span class="line">428</span><br><span class="line">429</span><br><span class="line">430</span><br><span class="line">431</span><br><span class="line">432</span><br><span class="line">433</span><br><span class="line">434</span><br><span class="line">435</span><br><span class="line">436</span><br><span class="line">437</span><br><span class="line">438</span><br><span class="line">439</span><br><span class="line">440</span><br><span class="line">441</span><br><span class="line">442</span><br><span class="line">443</span><br><span class="line">444</span><br><span class="line">445</span><br><span class="line">446</span><br><span class="line">447</span><br><span class="line">448</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> fs = <span class="built_in">require</span>(<span class="string">&#x27;fs&#x27;</span>);</span><br><span class="line"><span class="keyword">const</span> path = <span class="built_in">require</span>(<span class="string">&#x27;path&#x27;</span>);</span><br><span class="line"><span class="keyword">const</span> https = <span class="built_in">require</span>(<span class="string">&#x27;https&#x27;</span>);</span><br><span class="line"></span><br><span class="line"><span class="comment">// 加载环境变量</span></span><br><span class="line"><span class="keyword">function</span> <span class="title function_">loadEnv</span>(<span class="params"></span>) &#123;</span><br><span class="line">    <span class="keyword">try</span> &#123;</span><br><span class="line">        <span class="keyword">const</span> dotenv = <span class="built_in">require</span>(<span class="string">&#x27;dotenv&#x27;</span>);</span><br><span class="line"></span><br><span class="line">        <span class="comment">// 尝试从多个位置加载.env文件</span></span><br><span class="line">        <span class="keyword">const</span> envPaths = [path.<span class="title function_">join</span>(__dirname, <span class="string">&#x27;.env&#x27;</span>), path.<span class="title function_">join</span>(__dirname, <span class="string">&#x27;..&#x27;</span>, <span class="string">&#x27;.env&#x27;</span>)];</span><br><span class="line"></span><br><span class="line">        <span class="keyword">for</span> (<span class="keyword">const</span> envPath <span class="keyword">of</span> envPaths) &#123;</span><br><span class="line">            <span class="keyword">if</span> (fs.<span class="title function_">existsSync</span>(envPath)) &#123;</span><br><span class="line">                <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">`📄 加载环境变量文件: <span class="subst">$&#123;envPath&#125;</span>`</span>);</span><br><span class="line">                dotenv.<span class="title function_">config</span>(&#123;<span class="attr">path</span>: envPath&#125;);</span><br><span class="line"></span><br><span class="line">                <span class="comment">// 验证token是否加载成功</span></span><br><span class="line">                <span class="keyword">if</span> (process.<span class="property">env</span>.<span class="property">BLOG_CNB_ISSUE_TOKEN</span> &amp;&amp; process.<span class="property">env</span>.<span class="property">BLOG_CNB_ISSUE_TOKEN</span> !== <span class="string">&#x27;YOUR_TOKEN_HERE&#x27;</span>) &#123;</span><br><span class="line">                    <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">&#x27;✅ Token加载成功&#x27;</span>);</span><br><span class="line">                    <span class="keyword">return</span> <span class="literal">true</span>;</span><br><span class="line">                &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">                    <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">&#x27;⚠️  Token未在环境变量中找到&#x27;</span>);</span><br><span class="line">                &#125;</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">&#x27;⚠️  未找到.env文件，将直接使用环境变量&#x27;</span>);</span><br><span class="line">        <span class="keyword">return</span> <span class="literal">false</span>;</span><br><span class="line">    &#125; <span class="keyword">catch</span> (e) &#123;</span><br><span class="line">        <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">&#x27;⚠️  dotenv加载失败:&#x27;</span>, e.<span class="property">message</span>);</span><br><span class="line">        <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">&#x27;💡 请运行: npm install dotenv&#x27;</span>);</span><br><span class="line">        <span class="keyword">return</span> <span class="literal">false</span>;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="title function_">loadEnv</span>();</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="comment">// 配置</span></span><br><span class="line"><span class="keyword">const</span> <span class="variable constant_">CONFIG</span> = &#123;</span><br><span class="line">    <span class="attr">token</span>: process.<span class="property">env</span>.<span class="property">BLOG_CNB_ISSUE_TOKEN</span> || <span class="string">&#x27;YOUR_TOKEN_HERE&#x27;</span>, <span class="comment">// 从环境变量获取token</span></span><br><span class="line">    <span class="attr">baseUrl</span>: process.<span class="property">env</span>.<span class="property">BLOG_CNB_ISSUE_API_URL</span> || <span class="string">&#x27;https://api.cnb.cool/xqitw/blog/-/issues&#x27;</span>,</span><br><span class="line">    <span class="attr">postsDir</span>: path.<span class="title function_">join</span>(__dirname, <span class="string">&#x27;source/_posts&#x27;</span>),</span><br><span class="line">    <span class="attr">pageSize</span>: <span class="number">100</span>,</span><br><span class="line">    <span class="attr">delay</span>: <span class="number">1000</span> <span class="comment">// 1秒延迟</span></span><br><span class="line">&#125;;</span><br><span class="line"></span><br><span class="line"><span class="comment">// HTTP请求封装</span></span><br><span class="line"><span class="keyword">function</span> <span class="title function_">httpsRequest</span>(<span class="params">url</span>) &#123;</span><br><span class="line">    <span class="keyword">return</span> <span class="keyword">new</span> <span class="title class_">Promise</span>(<span class="function">(<span class="params">resolve, reject</span>) =&gt;</span> &#123;</span><br><span class="line">        <span class="keyword">const</span> options = &#123;</span><br><span class="line">            <span class="attr">headers</span>: &#123;</span><br><span class="line">                <span class="string">&#x27;Authorization&#x27;</span>: <span class="string">`Bearer <span class="subst">$&#123;CONFIG.token&#125;</span>`</span>, <span class="string">&#x27;Accept&#x27;</span>: <span class="string">&#x27;application/json&#x27;</span></span><br><span class="line">            &#125;</span><br><span class="line">        &#125;;</span><br><span class="line"></span><br><span class="line">        https.<span class="title function_">get</span>(url, options, <span class="function">(<span class="params">res</span>) =&gt;</span> &#123;</span><br><span class="line">            <span class="keyword">let</span> data = <span class="string">&#x27;&#x27;</span>;</span><br><span class="line">            res.<span class="title function_">on</span>(<span class="string">&#x27;data&#x27;</span>, <span class="function">(<span class="params">chunk</span>) =&gt;</span> data += chunk);</span><br><span class="line">            res.<span class="title function_">on</span>(<span class="string">&#x27;end&#x27;</span>, <span class="function">() =&gt;</span> &#123;</span><br><span class="line">                <span class="keyword">try</span> &#123;</span><br><span class="line">                    <span class="title function_">resolve</span>(<span class="title class_">JSON</span>.<span class="title function_">parse</span>(data));</span><br><span class="line">                &#125; <span class="keyword">catch</span> (e) &#123;</span><br><span class="line">                    <span class="title function_">reject</span>(e);</span><br><span class="line">                &#125;</span><br><span class="line">            &#125;);</span><br><span class="line">        &#125;).<span class="title function_">on</span>(<span class="string">&#x27;error&#x27;</span>, reject);</span><br><span class="line">    &#125;);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 延迟函数</span></span><br><span class="line"><span class="keyword">function</span> <span class="title function_">sleep</span>(<span class="params">ms</span>) &#123;</span><br><span class="line">    <span class="keyword">return</span> <span class="keyword">new</span> <span class="title class_">Promise</span>(<span class="function"><span class="params">resolve</span> =&gt;</span> <span class="built_in">setTimeout</span>(resolve, ms));</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 获取issue列表</span></span><br><span class="line"><span class="keyword">async</span> <span class="keyword">function</span> <span class="title function_">getIssues</span>(<span class="params">page = <span class="number">1</span></span>) &#123;</span><br><span class="line">    <span class="keyword">const</span> url = <span class="string">`<span class="subst">$&#123;CONFIG.baseUrl&#125;</span>?page=<span class="subst">$&#123;page&#125;</span>&amp;page_size=<span class="subst">$&#123;CONFIG.pageSize&#125;</span>&amp;order_by=-created_at`</span>;</span><br><span class="line">    <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">`获取第 <span class="subst">$&#123;page&#125;</span> 页issue列表...`</span>);</span><br><span class="line"></span><br><span class="line">    <span class="keyword">try</span> &#123;</span><br><span class="line">        <span class="keyword">const</span> issues = <span class="keyword">await</span> <span class="title function_">httpsRequest</span>(url);</span><br><span class="line">        <span class="keyword">await</span> <span class="title function_">sleep</span>(<span class="variable constant_">CONFIG</span>.<span class="property">delay</span>);</span><br><span class="line">        <span class="keyword">return</span> issues;</span><br><span class="line">    &#125; <span class="keyword">catch</span> (error) &#123;</span><br><span class="line">        <span class="variable language_">console</span>.<span class="title function_">error</span>(<span class="string">`获取第 <span class="subst">$&#123;page&#125;</span> 页失败:`</span>, error.<span class="property">message</span>);</span><br><span class="line">        <span class="keyword">return</span> [];</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 获取issue详情</span></span><br><span class="line"><span class="keyword">async</span> <span class="keyword">function</span> <span class="title function_">getIssueDetail</span>(<span class="params">number</span>) &#123;</span><br><span class="line">    <span class="keyword">const</span> url = <span class="string">`<span class="subst">$&#123;CONFIG.baseUrl&#125;</span>/<span class="subst">$&#123;number&#125;</span>`</span>;</span><br><span class="line">    <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">`获取issue #<span class="subst">$&#123;number&#125;</span> 详情...`</span>);</span><br><span class="line"></span><br><span class="line">    <span class="keyword">try</span> &#123;</span><br><span class="line">        <span class="keyword">const</span> detail = <span class="keyword">await</span> <span class="title function_">httpsRequest</span>(url);</span><br><span class="line">        <span class="keyword">await</span> <span class="title function_">sleep</span>(<span class="variable constant_">CONFIG</span>.<span class="property">delay</span>);</span><br><span class="line">        <span class="keyword">return</span> detail;</span><br><span class="line">    &#125; <span class="keyword">catch</span> (error) &#123;</span><br><span class="line">        <span class="variable language_">console</span>.<span class="title function_">error</span>(<span class="string">`获取issue #<span class="subst">$&#123;number&#125;</span> 详情失败:`</span>, error.<span class="property">message</span>);</span><br><span class="line">        <span class="keyword">return</span> <span class="literal">null</span>;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 格式化日期为ISO格式</span></span><br><span class="line"><span class="keyword">function</span> <span class="title function_">formatDate</span>(<span class="params">dateString</span>) &#123;</span><br><span class="line">    <span class="keyword">return</span> <span class="keyword">new</span> <span class="title class_">Date</span>(dateString).<span class="title function_">toISOString</span>();</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 解析标签和分类</span></span><br><span class="line"><span class="keyword">function</span> <span class="title function_">parseLabels</span>(<span class="params">labels</span>) &#123;</span><br><span class="line">    <span class="keyword">if</span> (!labels || !<span class="title class_">Array</span>.<span class="title function_">isArray</span>(labels)) &#123;</span><br><span class="line">        <span class="keyword">return</span> &#123;<span class="attr">categories</span>: [], <span class="attr">tags</span>: []&#125;;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">const</span> categories = [];</span><br><span class="line">    <span class="keyword">const</span> tags = [];</span><br><span class="line"></span><br><span class="line">    labels.<span class="title function_">forEach</span>(<span class="function"><span class="params">label</span> =&gt;</span> &#123;</span><br><span class="line">        <span class="keyword">const</span> labelName = label.<span class="property">name</span>;</span><br><span class="line">        <span class="keyword">if</span> (labelName.<span class="title function_">startsWith</span>(<span class="string">&#x27;分类:&#x27;</span>)) &#123;</span><br><span class="line">            <span class="comment">// 移除&quot;分类:&quot;前缀，作为分类</span></span><br><span class="line">            categories.<span class="title function_">push</span>(labelName.<span class="title function_">substring</span>(<span class="number">3</span>).<span class="title function_">trim</span>());</span><br><span class="line">        &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">            tags.<span class="title function_">push</span>(labelName);</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;);</span><br><span class="line"></span><br><span class="line">    <span class="keyword">return</span> &#123;categories, tags&#125;;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 生成Hexo文章front matter</span></span><br><span class="line"><span class="keyword">function</span> <span class="title function_">generateFrontMatter</span>(<span class="params">issue</span>) &#123;</span><br><span class="line">    <span class="keyword">const</span> &#123;categories, tags&#125; = <span class="title function_">parseLabels</span>(issue.<span class="property">labels</span>);</span><br><span class="line"></span><br><span class="line">    <span class="keyword">let</span> frontMatter = <span class="string">`---</span></span><br><span class="line"><span class="string">title: <span class="subst">$&#123;issue.title&#125;</span></span></span><br><span class="line"><span class="string">date: <span class="subst">$&#123;formatDate(issue.created_at)&#125;</span></span></span><br><span class="line"><span class="string">updated: <span class="subst">$&#123;formatDate(issue.updated_at)&#125;</span></span></span><br><span class="line"><span class="string">author: <span class="subst">$&#123;issue.author ? issue.author.nickname : <span class="string">&#x27;Unknown&#x27;</span>&#125;</span></span></span><br><span class="line"><span class="string">author_username: <span class="subst">$&#123;issue.author ? issue.author.username : <span class="string">&#x27;unknown&#x27;</span>&#125;</span></span></span><br><span class="line"><span class="string">categories: [<span class="subst">$&#123;categories.map(cat =&gt; <span class="string">`&quot;<span class="subst">$&#123;cat&#125;</span>&quot;`</span>).join(<span class="string">&#x27;, &#x27;</span>)&#125;</span>]</span></span><br><span class="line"><span class="string">tags: [<span class="subst">$&#123;tags.map(tag =&gt; <span class="string">`&quot;<span class="subst">$&#123;tag&#125;</span>&quot;`</span>).join(<span class="string">&#x27;, &#x27;</span>)&#125;</span>]</span></span><br><span class="line"><span class="string">comment_count: <span class="subst">$&#123;issue.comment_count || <span class="number">0</span>&#125;</span></span></span><br><span class="line"><span class="string">issue_number: <span class="subst">$&#123;issue.number&#125;</span></span></span><br><span class="line"><span class="string">state: <span class="subst">$&#123;issue.state&#125;</span></span></span><br><span class="line"><span class="string">---</span></span><br><span class="line"><span class="string">`</span>;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">return</span> frontMatter;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 解析友情链接详情</span></span><br><span class="line"><span class="keyword">function</span> <span class="title function_">parseFriendLinkDetail</span>(<span class="params">body</span>) &#123;</span><br><span class="line">    <span class="keyword">if</span> (!body) <span class="keyword">return</span> [];</span><br><span class="line"></span><br><span class="line">    <span class="keyword">const</span> friendLinks = [];</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 匹配所有代码块（支持 ```text 和 ``` 等格式）</span></span><br><span class="line">    <span class="keyword">const</span> codeBlockRegex = <span class="regexp">/```(?:text|)?\s*([\s\S]*?)```/g</span>;</span><br><span class="line">    <span class="keyword">let</span> match;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">while</span> ((match = codeBlockRegex.<span class="title function_">exec</span>(body)) !== <span class="literal">null</span>) &#123;</span><br><span class="line">        <span class="keyword">const</span> codeContent = match[<span class="number">1</span>].<span class="title function_">trim</span>();</span><br><span class="line">        <span class="keyword">const</span> result = &#123;&#125;;</span><br><span class="line"></span><br><span class="line">        <span class="comment">// 使用正则匹配各个字段</span></span><br><span class="line">        <span class="keyword">const</span> nameMatch = codeContent.<span class="title function_">match</span>(<span class="regexp">/站点名称[：:]\s*(.+)/</span>);</span><br><span class="line">        <span class="keyword">const</span> urlMatch = codeContent.<span class="title function_">match</span>(<span class="regexp">/站点地址[：:]\s*(.+)/</span>);</span><br><span class="line">        <span class="keyword">const</span> descMatch = codeContent.<span class="title function_">match</span>(<span class="regexp">/站点描述[：:]\s*(.+)/</span>);</span><br><span class="line">        <span class="keyword">const</span> imageMatch = codeContent.<span class="title function_">match</span>(<span class="regexp">/站点头像[：:]\s*(.+)/</span>);</span><br><span class="line"></span><br><span class="line">        <span class="keyword">if</span> (nameMatch) result.<span class="property">name</span> = nameMatch[<span class="number">1</span>].<span class="title function_">trim</span>();</span><br><span class="line">        <span class="keyword">if</span> (urlMatch) result.<span class="property">url</span> = urlMatch[<span class="number">1</span>].<span class="title function_">trim</span>();</span><br><span class="line">        <span class="keyword">if</span> (descMatch) result.<span class="property">desc</span> = descMatch[<span class="number">1</span>].<span class="title function_">trim</span>();</span><br><span class="line">        <span class="keyword">if</span> (imageMatch) result.<span class="property">image</span> = imageMatch[<span class="number">1</span>].<span class="title function_">trim</span>();</span><br><span class="line"></span><br><span class="line">        <span class="comment">// 检查是否所有必要字段都存在</span></span><br><span class="line">        <span class="keyword">if</span> (result.<span class="property">name</span> &amp;&amp; result.<span class="property">url</span>) &#123;</span><br><span class="line">            friendLinks.<span class="title function_">push</span>(result);</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">return</span> friendLinks;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 更新友情链接数据文件</span></span><br><span class="line"><span class="keyword">async</span> <span class="keyword">function</span> <span class="title function_">updateFriendData</span>(<span class="params">friendLinks</span>) &#123;</span><br><span class="line">    <span class="keyword">const</span> dataFilePath = path.<span class="title function_">join</span>(__dirname, <span class="string">&#x27;source&#x27;</span>, <span class="string">&#x27;friend&#x27;</span>, <span class="string">&#x27;_data.yml&#x27;</span>);</span><br><span class="line"></span><br><span class="line">    <span class="keyword">try</span> &#123;</span><br><span class="line">        <span class="keyword">let</span> yamlContent = <span class="string">&#x27;&#x27;</span>;</span><br><span class="line">        <span class="keyword">let</span> existingLinks = [];</span><br><span class="line"></span><br><span class="line">        <span class="comment">// 读取现有数据</span></span><br><span class="line">        <span class="keyword">if</span> (fs.<span class="title function_">existsSync</span>(dataFilePath)) &#123;</span><br><span class="line">            yamlContent = <span class="keyword">await</span> fs.<span class="property">promises</span>.<span class="title function_">readFile</span>(dataFilePath, <span class="string">&#x27;utf8&#x27;</span>);</span><br><span class="line">            <span class="comment">// 解析现有链接</span></span><br><span class="line">            existingLinks = <span class="title function_">parseYamlFriendLinks</span>(yamlContent);</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        <span class="comment">// 确保目录存在</span></span><br><span class="line">        <span class="keyword">const</span> friendDir = path.<span class="title function_">dirname</span>(dataFilePath);</span><br><span class="line">        <span class="keyword">if</span> (!fs.<span class="title function_">existsSync</span>(friendDir)) &#123;</span><br><span class="line">            fs.<span class="title function_">mkdirSync</span>(friendDir, &#123;<span class="attr">recursive</span>: <span class="literal">true</span>&#125;);</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        <span class="comment">// 合并新链接和现有链接</span></span><br><span class="line">        <span class="keyword">for</span> (<span class="keyword">const</span> friendLink <span class="keyword">of</span> friendLinks) &#123;</span><br><span class="line">            <span class="keyword">const</span> existingIndex = existingLinks.<span class="title function_">findIndex</span>(<span class="function"><span class="params">item</span> =&gt;</span> item.<span class="property">name</span> === friendLink.<span class="property">name</span>);</span><br><span class="line">            <span class="keyword">if</span> (existingIndex !== -<span class="number">1</span>) &#123;</span><br><span class="line">                existingLinks[existingIndex] = friendLink;</span><br><span class="line">                <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">`🔄 已更新友情链接: <span class="subst">$&#123;friendLink.name&#125;</span>`</span>);</span><br><span class="line">            &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">                existingLinks.<span class="title function_">push</span>(friendLink);</span><br><span class="line">                <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">`➕ 已添加友情链接: <span class="subst">$&#123;friendLink.name&#125;</span>`</span>);</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        <span class="comment">// 生成新的YAML内容</span></span><br><span class="line">        <span class="keyword">const</span> newYamlContent = <span class="title function_">generateYamlFriendLinks</span>(existingLinks);</span><br><span class="line">        <span class="keyword">await</span> fs.<span class="property">promises</span>.<span class="title function_">writeFile</span>(dataFilePath, newYamlContent, <span class="string">&#x27;utf8&#x27;</span>);</span><br><span class="line">        <span class="keyword">return</span> <span class="literal">true</span>;</span><br><span class="line">    &#125; <span class="keyword">catch</span> (error) &#123;</span><br><span class="line">        <span class="variable language_">console</span>.<span class="title function_">error</span>(<span class="string">`❌ 更新友情链接数据失败:`</span>, error.<span class="property">message</span>);</span><br><span class="line">        <span class="keyword">return</span> <span class="literal">false</span>;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 解析YAML格式的友情链接</span></span><br><span class="line"><span class="keyword">function</span> <span class="title function_">parseYamlFriendLinks</span>(<span class="params">yamlContent</span>) &#123;</span><br><span class="line">    <span class="keyword">const</span> links = [];</span><br><span class="line">    <span class="keyword">const</span> lines = yamlContent.<span class="title function_">split</span>(<span class="string">&#x27;&#x27;</span>);</span><br><span class="line">    <span class="keyword">let</span> currentLink = <span class="literal">null</span>;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">for</span> (<span class="keyword">const</span> line <span class="keyword">of</span> lines) &#123;</span><br><span class="line">        <span class="keyword">const</span> trimmedLine = line.<span class="title function_">trim</span>();</span><br><span class="line"></span><br><span class="line">        <span class="keyword">if</span> (trimmedLine.<span class="title function_">startsWith</span>(<span class="string">&#x27;- name:&#x27;</span>)) &#123;</span><br><span class="line">            <span class="keyword">if</span> (currentLink) &#123;</span><br><span class="line">                links.<span class="title function_">push</span>(currentLink);</span><br><span class="line">            &#125;</span><br><span class="line">            currentLink = &#123;<span class="attr">name</span>: trimmedLine.<span class="title function_">replace</span>(<span class="string">&#x27;- name:&#x27;</span>, <span class="string">&#x27;&#x27;</span>).<span class="title function_">trim</span>()&#125;;</span><br><span class="line">        &#125; <span class="keyword">else</span> <span class="keyword">if</span> (currentLink &amp;&amp; trimmedLine.<span class="title function_">startsWith</span>(<span class="string">&#x27;url:&#x27;</span>)) &#123;</span><br><span class="line">            currentLink.<span class="property">url</span> = trimmedLine.<span class="title function_">replace</span>(<span class="string">&#x27;url:&#x27;</span>, <span class="string">&#x27;&#x27;</span>).<span class="title function_">trim</span>();</span><br><span class="line">        &#125; <span class="keyword">else</span> <span class="keyword">if</span> (currentLink &amp;&amp; trimmedLine.<span class="title function_">startsWith</span>(<span class="string">&#x27;desc:&#x27;</span>)) &#123;</span><br><span class="line">            currentLink.<span class="property">desc</span> = trimmedLine.<span class="title function_">replace</span>(<span class="string">&#x27;desc:&#x27;</span>, <span class="string">&#x27;&#x27;</span>).<span class="title function_">trim</span>();</span><br><span class="line">        &#125; <span class="keyword">else</span> <span class="keyword">if</span> (currentLink &amp;&amp; trimmedLine.<span class="title function_">startsWith</span>(<span class="string">&#x27;image:&#x27;</span>)) &#123;</span><br><span class="line">            currentLink.<span class="property">image</span> = trimmedLine.<span class="title function_">replace</span>(<span class="string">&#x27;image:&#x27;</span>, <span class="string">&#x27;&#x27;</span>).<span class="title function_">trim</span>();</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">if</span> (currentLink) &#123;</span><br><span class="line">        links.<span class="title function_">push</span>(currentLink);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">return</span> links;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 生成YAML格式的友情链接</span></span><br><span class="line"><span class="keyword">function</span> <span class="title function_">generateYamlFriendLinks</span>(<span class="params">links</span>) &#123;</span><br><span class="line">    <span class="keyword">if</span> (links.<span class="property">length</span> === <span class="number">0</span>) <span class="keyword">return</span> <span class="string">&#x27;&#x27;</span>;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">return</span> links.<span class="title function_">map</span>(<span class="function"><span class="params">link</span> =&gt;</span> &#123;</span><br><span class="line">        <span class="keyword">let</span> yaml = <span class="string">`\n- name: <span class="subst">$&#123;link.name&#125;</span>`</span>;</span><br><span class="line">        <span class="keyword">if</span> (link.<span class="property">url</span>) yaml += <span class="string">`</span></span><br><span class="line"><span class="string">  url: <span class="subst">$&#123;link.url&#125;</span>`</span>;</span><br><span class="line">        <span class="keyword">if</span> (link.<span class="property">desc</span>) yaml += <span class="string">`</span></span><br><span class="line"><span class="string">  desc: <span class="subst">$&#123;link.desc&#125;</span>`</span>;</span><br><span class="line">        <span class="keyword">if</span> (link.<span class="property">image</span>) yaml += <span class="string">`</span></span><br><span class="line"><span class="string">  image: <span class="subst">$&#123;link.image&#125;</span>`</span>;</span><br><span class="line">        <span class="keyword">return</span> yaml;</span><br><span class="line">    &#125;).<span class="title function_">join</span>(<span class="string">&#x27;&#x27;</span>) + <span class="string">&#x27;&#x27;</span>;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 创建文章文件</span></span><br><span class="line"><span class="keyword">async</span> <span class="keyword">function</span> <span class="title function_">createPostFile</span>(<span class="params">issue</span>) &#123;</span><br><span class="line">    <span class="keyword">let</span> fileName, filePath;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 判断是否为关于页面</span></span><br><span class="line">    <span class="keyword">if</span> (issue.<span class="property">title</span>.<span class="title function_">trim</span>() === <span class="string">&#x27;关于&#x27;</span>) &#123;</span><br><span class="line">        fileName = <span class="string">&#x27;index.md&#x27;</span>;</span><br><span class="line">        <span class="keyword">const</span> aboutDir = path.<span class="title function_">join</span>(__dirname, <span class="string">&#x27;source&#x27;</span>, <span class="string">&#x27;about&#x27;</span>);</span><br><span class="line"></span><br><span class="line">        <span class="comment">// 确保about目录存在</span></span><br><span class="line">        <span class="keyword">if</span> (!fs.<span class="title function_">existsSync</span>(aboutDir)) &#123;</span><br><span class="line">            fs.<span class="title function_">mkdirSync</span>(aboutDir, &#123;<span class="attr">recursive</span>: <span class="literal">true</span>&#125;);</span><br><span class="line">            <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">`📁 创建目录: <span class="subst">$&#123;aboutDir&#125;</span>`</span>);</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        filePath = path.<span class="title function_">join</span>(aboutDir, fileName);</span><br><span class="line">        <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">`📄 检测到关于页面，将生成到: about/<span class="subst">$&#123;fileName&#125;</span>`</span>);</span><br><span class="line">    &#125; <span class="keyword">else</span> <span class="keyword">if</span> (issue.<span class="property">title</span>.<span class="title function_">trim</span>() === <span class="string">&#x27;友情链接&#x27;</span>) &#123;</span><br><span class="line">        fileName = <span class="string">&#x27;index.md&#x27;</span>;</span><br><span class="line">        <span class="keyword">const</span> friendDir = path.<span class="title function_">join</span>(__dirname, <span class="string">&#x27;source&#x27;</span>, <span class="string">&#x27;friend&#x27;</span>);</span><br><span class="line"></span><br><span class="line">        <span class="comment">// 确保friend目录存在</span></span><br><span class="line">        <span class="keyword">if</span> (!fs.<span class="title function_">existsSync</span>(friendDir)) &#123;</span><br><span class="line">            fs.<span class="title function_">mkdirSync</span>(friendDir, &#123;<span class="attr">recursive</span>: <span class="literal">true</span>&#125;);</span><br><span class="line">            <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">`📁 创建目录: <span class="subst">$&#123;friendDir&#125;</span>`</span>);</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        filePath = path.<span class="title function_">join</span>(friendDir, fileName);</span><br><span class="line">        <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">`📄 检测到友情链接页面，将生成到: friend/<span class="subst">$&#123;fileName&#125;</span>`</span>);</span><br><span class="line">    &#125; <span class="keyword">else</span> <span class="keyword">if</span> (issue.<span class="property">title</span>.<span class="title function_">trim</span>() === <span class="string">&#x27;友情链接详情&#x27;</span>) &#123;</span><br><span class="line">        <span class="comment">// 处理友情链接详情，解析并更新_data.yml</span></span><br><span class="line">        <span class="keyword">const</span> friendLinks = <span class="title function_">parseFriendLinkDetail</span>(issue.<span class="property">body</span>);</span><br><span class="line">        <span class="keyword">if</span> (friendLinks &amp;&amp; friendLinks.<span class="property">length</span> &gt; <span class="number">0</span>) &#123;</span><br><span class="line">            <span class="keyword">await</span> <span class="title function_">updateFriendData</span>(friendLinks);</span><br><span class="line">            <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">`✅ 已处理 <span class="subst">$&#123;friendLinks.length&#125;</span> 个友情链接`</span>);</span><br><span class="line">            <span class="keyword">return</span> <span class="literal">true</span>;</span><br><span class="line">        &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">            <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">`⚠️  无法解析友情链接详情: <span class="subst">$&#123;issue.number&#125;</span>`</span>);</span><br><span class="line">            <span class="keyword">return</span> <span class="literal">false</span>;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">        fileName = <span class="string">`post-<span class="subst">$&#123;issue.number&#125;</span>.md`</span>;</span><br><span class="line">        filePath = path.<span class="title function_">join</span>(<span class="variable constant_">CONFIG</span>.<span class="property">postsDir</span>, fileName);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">const</span> content = <span class="title function_">generateFrontMatter</span>(issue) + (issue.<span class="property">body</span> || <span class="string">&#x27;&#x27;</span>);</span><br><span class="line"></span><br><span class="line">    <span class="keyword">try</span> &#123;</span><br><span class="line">        <span class="keyword">await</span> fs.<span class="property">promises</span>.<span class="title function_">writeFile</span>(filePath, content, <span class="string">&#x27;utf8&#x27;</span>);</span><br><span class="line">        <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">`✅ 已创建文件: <span class="subst">$&#123;fileName&#125;</span>`</span>);</span><br><span class="line">        <span class="keyword">return</span> <span class="literal">true</span>;</span><br><span class="line">    &#125; <span class="keyword">catch</span> (error) &#123;</span><br><span class="line">        <span class="variable language_">console</span>.<span class="title function_">error</span>(<span class="string">`❌ 创建文件失败 <span class="subst">$&#123;fileName&#125;</span>:`</span>, error.<span class="property">message</span>);</span><br><span class="line">        <span class="keyword">return</span> <span class="literal">false</span>;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 检查文件是否已存在</span></span><br><span class="line"><span class="keyword">function</span> <span class="title function_">fileExists</span>(<span class="params">fileName</span>) &#123;</span><br><span class="line">    <span class="keyword">let</span> filePath;</span><br><span class="line">    <span class="keyword">if</span> (fileName === <span class="string">&#x27;about/index.md&#x27;</span>) &#123;</span><br><span class="line">        filePath = path.<span class="title function_">join</span>(__dirname, <span class="string">&#x27;source&#x27;</span>, <span class="string">&#x27;about&#x27;</span>, <span class="string">&#x27;index.md&#x27;</span>);</span><br><span class="line">    &#125; <span class="keyword">else</span> <span class="keyword">if</span> (fileName === <span class="string">&#x27;friend/index.md&#x27;</span>) &#123;</span><br><span class="line">        filePath = path.<span class="title function_">join</span>(__dirname, <span class="string">&#x27;source&#x27;</span>, <span class="string">&#x27;friend&#x27;</span>, <span class="string">&#x27;index.md&#x27;</span>);</span><br><span class="line">    &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">        filePath = path.<span class="title function_">join</span>(<span class="variable constant_">CONFIG</span>.<span class="property">postsDir</span>, fileName);</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">return</span> fs.<span class="title function_">existsSync</span>(filePath);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 主函数</span></span><br><span class="line"><span class="keyword">async</span> <span class="keyword">function</span> <span class="title function_">main</span>(<span class="params"></span>) &#123;</span><br><span class="line">    <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">&#x27;🚀 开始获取issues并生成Hexo文章...&#x27;</span>);</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 检查token</span></span><br><span class="line">    <span class="keyword">if</span> (<span class="variable constant_">CONFIG</span>.<span class="property">token</span> === <span class="string">&#x27;YOUR_TOKEN_HERE&#x27;</span>) &#123;</span><br><span class="line">        <span class="variable language_">console</span>.<span class="title function_">error</span>(<span class="string">&#x27;❌ 请设置环境变量 BLOG_CNB_ISSUE_TOKEN 或修改CONFIG.token&#x27;</span>);</span><br><span class="line">        process.<span class="title function_">exit</span>(<span class="number">1</span>);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 确保posts目录存在</span></span><br><span class="line">    <span class="keyword">if</span> (!fs.<span class="title function_">existsSync</span>(<span class="variable constant_">CONFIG</span>.<span class="property">postsDir</span>)) &#123;</span><br><span class="line">        fs.<span class="title function_">mkdirSync</span>(<span class="variable constant_">CONFIG</span>.<span class="property">postsDir</span>, &#123;<span class="attr">recursive</span>: <span class="literal">true</span>&#125;);</span><br><span class="line">        <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">`📁 创建目录: <span class="subst">$&#123;CONFIG.postsDir&#125;</span>`</span>);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">`🔗 API地址: <span class="subst">$&#123;CONFIG.baseUrl&#125;</span>`</span>);</span><br><span class="line">    <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">`📁 文章保存目录: <span class="subst">$&#123;CONFIG.postsDir&#125;</span>`</span>);</span><br><span class="line">    <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">`🏷️  分类规则: 以&quot;分类:&quot;开头的标签将作为分类（自动移除前缀）`</span>);</span><br><span class="line"></span><br><span class="line">    <span class="keyword">let</span> page = <span class="number">1</span>;</span><br><span class="line">    <span class="keyword">let</span> totalProcessed = <span class="number">0</span>;</span><br><span class="line">    <span class="keyword">let</span> totalSkipped = <span class="number">0</span>;</span><br><span class="line">    <span class="keyword">let</span> totalErrors = <span class="number">0</span>;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">try</span> &#123;</span><br><span class="line">        <span class="keyword">while</span> (<span class="literal">true</span>) &#123;</span><br><span class="line">            <span class="comment">// 获取当前页的issues</span></span><br><span class="line">            <span class="keyword">const</span> issues = <span class="keyword">await</span> <span class="title function_">getIssues</span>(page);</span><br><span class="line"></span><br><span class="line">            <span class="keyword">if</span> (!issues || issues.<span class="property">length</span> === <span class="number">0</span>) &#123;</span><br><span class="line">                <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">&#x27;✅ 没有更多issues，处理完成&#x27;</span>);</span><br><span class="line">                <span class="keyword">break</span>;</span><br><span class="line">            &#125;</span><br><span class="line"></span><br><span class="line">            <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">`📄 第 <span class="subst">$&#123;page&#125;</span> 页找到 <span class="subst">$&#123;issues.length&#125;</span> 个issues`</span>);</span><br><span class="line"></span><br><span class="line">            <span class="comment">// 处理每个issue</span></span><br><span class="line">            <span class="keyword">for</span> (<span class="keyword">const</span> issue <span class="keyword">of</span> issues) &#123;</span><br><span class="line">                <span class="keyword">try</span> &#123;</span><br><span class="line">                    <span class="comment">// 检查文件是否已存在</span></span><br><span class="line">                    <span class="keyword">let</span> fileName;</span><br><span class="line">                    <span class="keyword">if</span> (issue.<span class="property">title</span>.<span class="title function_">trim</span>() === <span class="string">&#x27;关于&#x27;</span>) &#123;</span><br><span class="line">                        fileName = <span class="string">&#x27;about/index.md&#x27;</span>;</span><br><span class="line">                    &#125; <span class="keyword">else</span> <span class="keyword">if</span> (issue.<span class="property">title</span>.<span class="title function_">trim</span>() === <span class="string">&#x27;友情链接&#x27;</span>) &#123;</span><br><span class="line">                        fileName = <span class="string">&#x27;friend/index.md&#x27;</span>;</span><br><span class="line">                    &#125; <span class="keyword">else</span> <span class="keyword">if</span> (issue.<span class="property">title</span>.<span class="title function_">trim</span>() === <span class="string">&#x27;友情链接详情&#x27;</span>) &#123;</span><br><span class="line">                        <span class="comment">// 友情链接详情不需要检查文件存在性，直接处理</span></span><br><span class="line">                        fileName = <span class="literal">null</span>;</span><br><span class="line">                    &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">                        fileName = <span class="string">`post-<span class="subst">$&#123;issue.number&#125;</span>.md`</span>;</span><br><span class="line">                    &#125;</span><br><span class="line"></span><br><span class="line">                    <span class="keyword">if</span> (fileName &amp;&amp; <span class="title function_">fileExists</span>(fileName)) &#123;</span><br><span class="line">                        <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">`🔄 覆盖已存在的文件: <span class="subst">$&#123;fileName&#125;</span>`</span>);</span><br><span class="line">                    &#125;</span><br><span class="line"></span><br><span class="line">                    <span class="comment">// 获取详情</span></span><br><span class="line">                    <span class="keyword">const</span> detail = <span class="keyword">await</span> <span class="title function_">getIssueDetail</span>(issue.<span class="property">number</span>);</span><br><span class="line">                    <span class="keyword">if</span> (!detail) &#123;</span><br><span class="line">                        totalErrors++;</span><br><span class="line">                        <span class="keyword">continue</span>;</span><br><span class="line">                    &#125;</span><br><span class="line"></span><br><span class="line">                    <span class="comment">// 创建文章文件</span></span><br><span class="line">                    <span class="keyword">const</span> success = <span class="keyword">await</span> <span class="title function_">createPostFile</span>(detail);</span><br><span class="line">                    <span class="keyword">if</span> (success) &#123;</span><br><span class="line">                        totalProcessed++;</span><br><span class="line">                    &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">                        totalErrors++;</span><br><span class="line">                    &#125;</span><br><span class="line"></span><br><span class="line">                &#125; <span class="keyword">catch</span> (error) &#123;</span><br><span class="line">                    <span class="variable language_">console</span>.<span class="title function_">error</span>(<span class="string">`处理issue #<span class="subst">$&#123;issue.number&#125;</span> 时出错:`</span>, error.<span class="property">message</span>);</span><br><span class="line">                    totalErrors++;</span><br><span class="line">                &#125;</span><br><span class="line">            &#125;</span><br><span class="line"></span><br><span class="line">            page++;</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">&#x27;\n📊 处理完成统计:&#x27;</span>);</span><br><span class="line">        <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">`✅ 成功处理: <span class="subst">$&#123;totalProcessed&#125;</span> 篇文章`</span>);</span><br><span class="line">        <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">`⏭️  跳过已存在: <span class="subst">$&#123;totalSkipped&#125;</span> 篇文章`</span>);</span><br><span class="line">        <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">`❌ 处理失败: <span class="subst">$&#123;totalErrors&#125;</span> 篇文章`</span>);</span><br><span class="line">        <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">`📁 文章保存在: <span class="subst">$&#123;CONFIG.postsDir&#125;</span>`</span>);</span><br><span class="line"></span><br><span class="line">    &#125; <span class="keyword">catch</span> (error) &#123;</span><br><span class="line">        <span class="variable language_">console</span>.<span class="title function_">error</span>(<span class="string">&#x27;❌ 主程序执行出错:&#x27;</span>, error.<span class="property">message</span>);</span><br><span class="line">        process.<span class="title function_">exit</span>(<span class="number">1</span>);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 如果直接运行此脚本</span></span><br><span class="line"><span class="keyword">if</span> (<span class="built_in">require</span>.<span class="property">main</span> === <span class="variable language_">module</span>) &#123;</span><br><span class="line">    <span class="title function_">main</span>();</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="variable language_">module</span>.<span class="property">exports</span> = &#123;</span><br><span class="line">    main, getIssues, getIssueDetail, createPostFile, parseLabels</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure>]]></content>
    
    
      
      
    <summary type="html">&lt;p&gt;这个工具用于从 CNB Cool API 获取 issues 数据并自动生成 Hexo 博客文章。&lt;/p&gt;
&lt;h2 id=&quot;功能特性&quot;&gt;&lt;a href=&quot;#功能特性&quot; class=&quot;headerlink&quot; title=&quot;功能特性&quot;&gt;&lt;/a&gt;功能特性&lt;/h2&gt;&lt;ul&gt;
&lt;li</summary>
      
    
    
    
    <category term="代码工具" scheme="https://xqitw.cn/categories/%E4%BB%A3%E7%A0%81%E5%B7%A5%E5%85%B7/"/>
    
    
    <category term="标签:CNB" scheme="https://xqitw.cn/tags/%E6%A0%87%E7%AD%BE-CNB/"/>
    
    <category term="自动化工具" scheme="https://xqitw.cn/tags/%E8%87%AA%E5%8A%A8%E5%8C%96%E5%B7%A5%E5%85%B7/"/>
    
    <category term="Hexo" scheme="https://xqitw.cn/tags/Hexo/"/>
    
  </entry>
  
</feed>
