<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
  <title>可爱可倾</title>
  
  <subtitle>花开有期，云舒有意</subtitle>
  <link href="https://blog.keaikeqing.cn/rss.xml" rel="self"/>
  
  <link href="https://blog.keaikeqing.cn/"/>
  <updated>2025-12-12T06:45:56.000Z</updated>
  <id>https://blog.keaikeqing.cn/</id>
  
  <author>
    <name>可爱可倾</name>
    
  </author>
  
  <generator uri="https://hexo.io/">Hexo</generator>
  
  <entry>
    <title>修复 Conda 导致的 PowerShell 启动缓慢</title>
    <link href="https://blog.keaikeqing.cn/2025/12/12/TechnicalNotes/PowerShellCondaFix/"/>
    <id>https://blog.keaikeqing.cn/2025/12/12/TechnicalNotes/PowerShellCondaFix/</id>
    <published>2025-12-12T06:45:56.000Z</published>
    <updated>2025-12-12T06:45:56.000Z</updated>
    
    <content type="html"><![CDATA[<h1 id="修复-conda-导致的-powershell-启动缓慢">修复 Conda 导致的PowerShell 启动缓慢</h1><h2 id="问题描述">问题描述</h2><p>每次启动 PowerShell时都有明显的卡顿，启动时间长达数秒。经过排查，发现这是由于 Conda的初始化脚本导致的。</p><h2 id="原因">原因</h2><p>在使用 <code>conda init</code> 命令配置 PowerShell 环境时，Conda会向配置文件（Profile）中注入一段初始化代码。这段代码会在每次启动PowerShell 时立即执行，加载整个 Conda 环境，从而拖慢启动速度。</p><h2 id="排查过程">排查过程</h2><ol type="1"><li><p><strong>测试当前启动时间</strong> 使用<code>Measure-Command</code> 测试加载配置文件的启动耗时：</p><pre class="language-powershell" data-language="powershell"><code class="language-powershell"><span class="token function">Measure-Command</span> <span class="token punctuation">&#123;</span> pwsh<span class="token punctuation">.</span>exe <span class="token operator">-</span>Command <span class="token string">"exit"</span> <span class="token punctuation">&#125;</span></code></pre><p>测试结果显示需要约 5 秒。</p></li><li><p><strong>测试裸启动时间</strong>测试不加载配置文件（<code>-NoProfile</code>）的启动耗时：</p><pre class="language-powershell" data-language="powershell"><code class="language-powershell"><span class="token function">Measure-Command</span> <span class="token punctuation">&#123;</span> pwsh<span class="token punctuation">.</span>exe <span class="token operator">-</span>NoProfile <span class="token operator">-</span>Command <span class="token string">"exit"</span> <span class="token punctuation">&#125;</span></code></pre><p>结果仅需 0.5秒左右。巨大的时间差异证实了问题出在配置文件（Profile）的加载过程中。</p></li><li><p><strong>定位配置文件</strong></p><pre class="language-powershell" data-language="powershell"><code class="language-powershell"><span class="token variable">$PROFILE</span> <span class="token punctuation">|</span> <span class="token function">Get-Member</span> <span class="token operator">-</span><span class="token function">Type</span> NoteProperty</code></pre></li><li><p><strong>分析配置文件内容</strong> 打开配置文件（通常是<code>CurrentUserAllHosts</code>），找到 Conda 注入的代码块：</p><pre class="language-powershell" data-language="powershell"><code class="language-powershell"><span class="token comment">#region conda initialize</span><span class="token comment"># !! Contents within this block are managed by 'conda init' !!</span><span class="token keyword">If</span> <span class="token punctuation">(</span><span class="token function">Test-Path</span> <span class="token string">"xx\Scripts\conda.exe"</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>    <span class="token punctuation">(</span>&amp; <span class="token string">"xx\Scripts\conda.exe"</span> <span class="token string">"shell.powershell"</span> <span class="token string">"hook"</span><span class="token punctuation">)</span> <span class="token punctuation">|</span> <span class="token function">Out-String</span> <span class="token punctuation">|</span> ?<span class="token punctuation">&#123;</span><span class="token variable">$_</span><span class="token punctuation">&#125;</span> <span class="token punctuation">|</span> <span class="token function">Invoke-Expression</span><span class="token punctuation">&#125;</span><span class="token comment">#endregion</span></code></pre></li><li><p><strong>验证假设</strong>临时注释掉上述代码块后，再次测试启动时间：</p><pre class="language-powershell" data-language="powershell"><code class="language-powershell"><span class="token function">Measure-Command</span> <span class="token punctuation">&#123;</span> pwsh<span class="token punctuation">.</span>exe <span class="token operator">-</span>Command <span class="token string">"exit"</span> <span class="token punctuation">&#125;</span><span class="token function">Measure-Command</span> <span class="token punctuation">&#123;</span> pwsh<span class="token punctuation">.</span>exe <span class="token operator">-</span>NoProfile <span class="token operator">-</span>Command <span class="token string">"exit"</span> <span class="token punctuation">&#125;</span></code></pre><p>发现与裸启动时间基本持平，确认该段代码是性能瓶颈的根源。</p></li></ol><h2 id="解决方案">解决方案</h2><p>直接注释掉代码虽然能解决启动慢的问题，但会导致无法使用<code>conda</code> 命令。更好的方案是将同步初始化改为<strong>懒加载（LazyLoading）</strong>：仅在首次输入 <code>conda</code>命令时才执行初始化逻辑。</p><p>注：只需将脚本中的 xx.exe 替换为你实际的 conda 安装路径即可。</p><pre class="language-powershell" data-language="powershell"><code class="language-powershell"><span class="token comment">#region conda initialize</span><span class="token comment"># ===== 配置：🛑 改为实际的 conda.exe 路径 =====</span><span class="token variable">$script</span>:CondaExePath = <span class="token string">'xx\Scripts\conda.exe'</span><span class="token comment"># ===== 主逻辑 =====</span><span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token function">Test-Path</span> <span class="token operator">-</span>LiteralPath <span class="token variable">$script</span>:CondaExePath <span class="token operator">-</span>PathType Leaf<span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>        <span class="token comment"># 状态标记：防止递归调用</span>    <span class="token variable">$script</span>:CondaInitialized = <span class="token boolean">$false</span>        <span class="token comment"># 定义核心初始化逻辑</span>    <span class="token keyword">function</span> Global:<span class="token function">Invoke-CondaInit</span> <span class="token punctuation">&#123;</span>        <span class="token comment"># 只有未初始化时才执行 hook 逻辑</span>        <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token operator">-not</span> <span class="token variable">$script</span>:CondaInitialized<span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>            <span class="token function">Write-Host</span> <span class="token string">"🔄 正在初始化 Conda..."</span> <span class="token operator">-</span>ForegroundColor Cyan            <span class="token variable">$script</span>:CondaInitialized = <span class="token boolean">$true</span>                        <span class="token keyword">try</span> <span class="token punctuation">&#123;</span>                <span class="token comment"># 1. 获取 Hook 脚本 (屏蔽 stderr 警告)</span>                <span class="token variable">$hookOutput</span> = &amp; <span class="token variable">$script</span>:CondaExePath <span class="token string">'shell.powershell'</span> <span class="token string">'hook'</span> 2><span class="token variable">$null</span>                <span class="token variable">$exitCode</span> = <span class="token variable">$LASTEXITCODE</span>                                <span class="token comment"># 2. 严格检查：任何异常直接报错</span>                <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token variable">$exitCode</span> <span class="token operator">-ne</span> 0<span class="token punctuation">)</span> <span class="token punctuation">&#123;</span> <span class="token keyword">throw</span> <span class="token string">"Conda hook 进程退出码异常: <span class="token variable">$exitCode</span>"</span> <span class="token punctuation">&#125;</span>                <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token namespace">[string]</span>::IsNullOrWhiteSpace<span class="token punctuation">(</span><span class="token variable">$hookOutput</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span> <span class="token keyword">throw</span> <span class="token string">"Conda hook 返回空内容"</span> <span class="token punctuation">&#125;</span>                                <span class="token comment"># 3. 执行官方 Hook</span>                <span class="token function">Invoke-Expression</span> <span class="token punctuation">(</span><span class="token variable">$hookOutput</span> <span class="token punctuation">|</span> <span class="token function">Out-String</span><span class="token punctuation">)</span>                               <span class="token comment"># 4. 双重保险：确保 Global 作用域生效</span>                <span class="token keyword">try</span> <span class="token punctuation">&#123;</span>                    <span class="token variable">$fn</span> = <span class="token function">Get-Command</span> conda <span class="token operator">-</span>CommandType <span class="token keyword">Function</span> <span class="token operator">-</span>ErrorAction Stop                    <span class="token function">Set-Item</span> <span class="token operator">-</span>Path <span class="token keyword">Function</span>:\Global:conda <span class="token operator">-</span>Value <span class="token variable">$fn</span><span class="token punctuation">.</span>ScriptBlock <span class="token operator">-</span>Force                <span class="token punctuation">&#125;</span>                <span class="token keyword">catch</span> <span class="token punctuation">&#123;</span>                    <span class="token keyword">throw</span> <span class="token string">"Hook 执行后未检测到 conda 函数，环境可能已损坏"</span>                <span class="token punctuation">&#125;</span>                <span class="token function">Write-Host</span> <span class="token string">"✓ Conda 初始化成功"</span> <span class="token operator">-</span>ForegroundColor Green            <span class="token punctuation">&#125;</span>            <span class="token keyword">catch</span> <span class="token punctuation">&#123;</span>                <span class="token comment"># 失败处理：重置状态并直接抛出致命错误</span>                <span class="token variable">$script</span>:CondaInitialized = <span class="token boolean">$false</span>                <span class="token function">Write-Error</span> <span class="token string">"Conda 初始化失败: <span class="token function">$<span class="token punctuation">(</span><span class="token variable">$_</span><span class="token punctuation">.</span>Exception<span class="token punctuation">.</span>Message<span class="token punctuation">)</span></span>"</span> <span class="token operator">-</span>ErrorAction Stop            <span class="token punctuation">&#125;</span>        <span class="token punctuation">&#125;</span>                <span class="token comment"># 执行用户命令</span>        <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token variable">$args</span><span class="token punctuation">.</span>Count <span class="token operator">-gt</span> 0<span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>            &amp; conda @args        <span class="token punctuation">&#125;</span>    <span class="token punctuation">&#125;</span>        <span class="token comment"># 应用代理：拦截首次调用</span>    <span class="token function">Set-Item</span> <span class="token keyword">Function</span>:\Global:conda <span class="token operator">-</span>Value <span class="token punctuation">&#123;</span> <span class="token function">Invoke-CondaInit</span> @args <span class="token punctuation">&#125;</span> <span class="token operator">-</span>Force    <span class="token punctuation">&#125;</span><span class="token keyword">else</span> <span class="token punctuation">&#123;</span>    <span class="token comment"># 路径配置错误的报错逻辑</span>    <span class="token function">Write-Warning</span> <span class="token string">"未找到 Conda: <span class="token variable">$script</span>:CondaExePath"</span>        <span class="token keyword">function</span> Global:conda <span class="token punctuation">&#123;</span>        <span class="token function">Write-Error</span> <span class="token string">"Conda 配置路径无效: <span class="token variable">$script</span>:CondaExePath"</span> <span class="token operator">-</span>ErrorAction Stop    <span class="token punctuation">&#125;</span><span class="token punctuation">&#125;</span><span class="token comment">#endregion</span></code></pre><h2 id="代码解释">代码解释</h2><p>当PowerShell启动时：</p><pre class="language-powershell" data-language="powershell"><code class="language-powershell">PowerShell 启动    ↓<span class="token function">Set-Item</span> <span class="token keyword">Function</span>:\Global:conda <span class="token operator">-</span>Value <span class="token punctuation">&#123;</span> <span class="token function">Invoke-CondaInit</span> @args <span class="token punctuation">&#125;</span>    ↓此时 conda = <span class="token function">Invoke-CondaInit</span></code></pre><p>在上面的代码中，<code>Set-Item Function:\Global:conda ...</code> 会将conda 命令重定向到代理函数 <code>Invoke-CondaInit</code>。此时，当第一次调用 conda 命令时，会触发代理函数<code>Invoke-CondaInit</code>:</p><pre class="language-powershell" data-language="powershell"><code class="language-powershell">用户输入: conda <span class="token operator">-</span>V    ↓调用代理函数 <span class="token function">Invoke-CondaInit</span>    ↓进入 <span class="token function">Invoke-CondaInit</span>    ↓执行官方 Hook: <span class="token function">Invoke-Expression</span> <span class="token punctuation">(</span><span class="token variable">$hookOutput</span> <span class="token punctuation">|</span> <span class="token function">Out-String</span><span class="token punctuation">)</span>    ↓【关键】官方 Hook 会定义一个新的 conda 函数！    ↓<span class="token function">Set-Item</span> <span class="token operator">-</span>Path <span class="token punctuation">.</span><span class="token punctuation">.</span><span class="token punctuation">.</span> <span class="token punctuation">(</span>仅作为防御性检查，通常官方 Hook 已经将 conda 函数作用到全局<span class="token punctuation">)</span>    ↓此时 conda = 官方函数（覆盖了我们的代理）    ↓&amp; conda @args  ← 调用的是官方函数    ↓用户再输入: conda activate xxx    ↓调用的是【官方 conda 函数】，不再是代理    ↓<span class="token function">Invoke-CondaInit</span> 永远不会再被调用</code></pre>]]></content>
    
    
      
      
    <summary type="html">&lt;h1 id=&quot;修复-conda-导致的-powershell-启动缓慢&quot;&gt;修复 Conda 导致的
PowerShell 启动缓慢&lt;/h1&gt;
&lt;h2 id=&quot;问题描述&quot;&gt;问题描述&lt;/h2&gt;
&lt;p&gt;每次启动 PowerShell
时都有明显的卡顿，启动时间长达数秒。经过排查，发现</summary>
      
    
    
    
    <category term="随笔" scheme="https://blog.keaikeqing.cn/categories/%E9%9A%8F%E7%AC%94/"/>
    
    
  </entry>
  
  <entry>
    <title>DNS、端口、安全组与反向代理</title>
    <link href="https://blog.keaikeqing.cn/2025/12/10/Web/DNS%20%E7%AB%AF%E5%8F%A3%20%E5%AE%89%E5%85%A8%E7%BB%84%E4%B8%8E%E5%8F%8D%E5%90%91%E4%BB%A3%E7%90%86/"/>
    <id>https://blog.keaikeqing.cn/2025/12/10/Web/DNS%20%E7%AB%AF%E5%8F%A3%20%E5%AE%89%E5%85%A8%E7%BB%84%E4%B8%8E%E5%8F%8D%E5%90%91%E4%BB%A3%E7%90%86/</id>
    <published>2025-12-10T14:34:40.000Z</published>
    <updated>2025-12-10T14:34:40.000Z</updated>
    
    <content type="html"><![CDATA[<h1 id="dns端口安全组与反向代理">DNS、端口、安全组与反向代理</h1><h2 id="dns只负责域名-ip-的映射">1. DNS：只负责“域名 → IP 的映射”</h2><p>DNS（域名解析）只做一件事：</p><pre class="language-none"><code class="language-none">xx.example.com → 服务器公网 IP</code></pre><p>它<strong>不涉及端口、不涉及防火墙、不涉及安全策略</strong>。你只是告诉全球的 DNS 服务器：</p><blockquote><p>“访问这个域名，请去找这台服务器。”</p></blockquote><hr /><h2 id="端口服务监听的位置">2. 端口：服务监听的位置</h2><p>服务器上的每一个服务，都需要监听一个端口，例如：</p><table><thead><tr><th>服务</th><th>默认端口</th><th>是否需对外开放？</th></tr></thead><tbody><tr><td>HTTP</td><td>80</td><td>✔ 通常开放</td></tr><tr><td>HTTPS</td><td>443</td><td>✔ 通常开放</td></tr><tr><td>xx 后端</td><td>1234</td><td>❌ 不需要开放</td></tr></tbody></table><p>如果你的 xx 运行在：<code>127.0.0.1:1234</code>，那么它只允许<strong>本机访问</strong>。外网无论如何都无法访问这个端口，即使安全组放行了 1234 端口。</p><p>如果你的 xx 运行在：<code>0.0.0.0:1234</code>，那么它允许<strong>所有 IP 访问</strong>。但是，是否能从外网访问，还要看安全组中是否放行了 1234 端口。</p><hr /><h2 id="安全组决定外网能否访问某个端口">3.安全组：决定外网能否访问某个端口</h2><p>安全组是云厂商提供的<strong>入口防火墙</strong>，决定哪些端口可以从外网访问。例如：</p><table><thead><tr><th>端口</th><th>安全组放行？</th><th>外网能访问吗？</th></tr></thead><tbody><tr><td>80</td><td>✔</td><td>能</td></tr><tr><td>443</td><td>✔</td><td>能</td></tr><tr><td>1234</td><td>❌</td><td>不能</td></tr></tbody></table><blockquote><p><strong>只要你的安全组没有放行 1234，即便服务监听了0.0.0.0:1234，外网仍然无法访问。</strong></p></blockquote><hr /><h2 id="反向代理nginx把外网请求转发给内部服务">4.反向代理（Nginx）：把外网请求转发给内部服务</h2><p>反代负责将 80/443 的访问转发到内部端口，如：</p><pre class="language-none"><code class="language-none">https:&#x2F;&#x2F;xx.example.com       ↓Nginx（监听 80&#x2F;443）       ↓127.0.0.1:1234（xx 后端）</code></pre><p>反向代理的意义是：</p><ul><li>外网用户只需要访问域名（80/443）</li><li>内部服务可以隐藏在任意端口</li><li>后端端口无需暴露给外网（更安全）</li></ul><p>这就是为什么<strong>后端端口不用开放</strong>。</p><hr /><h2 id="常见问题">5. 常见问题</h2><p><strong>Q: 配置了反代后，我需要在云服务器后台开启 1234端口吗？</strong></p><p><strong>A:</strong> <strong>完全不需要</strong>。 因为流量是先到达Nginx (443端口)，然后由 Nginx 在服务器<strong>内部</strong>通过<code>localhost</code> 转发给1234。外部防火墙根本看不到这个内部转发过程，所以 1234端口应当保持关闭状态。</p><p><strong>Q:如何检测我的服务是否只监听在本地以及是否开启？</strong></p><p><strong>A:</strong> <strong>使用 <code>netstat</code> 或<code>ss</code> 命令查看端口监听状态：</strong></p><pre class="language-bash" data-language="bash"><code class="language-bash"><span class="token comment"># 使用 netstat</span><span class="token function">sudo</span> <span class="token function">netstat</span> <span class="token parameter variable">-ltn</span> <span class="token operator">|</span> <span class="token function">grep</span> <span class="token number">1234</span><span class="token comment"># 使用 ss</span><span class="token function">sudo</span> ss <span class="token parameter variable">-ltn</span> <span class="token operator">|</span> <span class="token function">grep</span> <span class="token number">1234</span><span class="token comment"># 正确的状态 (只允许本地)</span>tcp  <span class="token number">0</span>  <span class="token number">0</span>  <span class="token number">127.0</span>.0.1:1234   <span class="token number">0.0</span>.0.0:* LISTEN<span class="token comment"># 危险的状态 (允许所有 IP)</span>tcp  <span class="token number">0</span>  <span class="token number">0</span>  <span class="token number">0.0</span>.0.0:1234     <span class="token number">0.0</span>.0.0:* LISTEN</code></pre><h2 id="总结">6. 总结</h2><p>一张简单的关系图</p><pre class="language-none"><code class="language-none">┌──────────────────────────────┐│        用户访问域名           │└───────────────┬──────────────┘                ↓        DNS：解析域名 → 服务器 IP                ↓┌───────────────┴──────────────┐│     Nginx（监听 80 &#x2F; 443）     ││  负责接收外网请求并做反向代理 │└───────────────┬──────────────┘                ↓     内部服务（如 127.0.0.1:1234）</code></pre><p>如果你使用 Nginx 为 xx 或其他后端程序做反代：</p><ul><li>✔ <strong>必须开放的端口：80 / 443</strong></li><li>❌ <strong>不需要开放的端口：1234 等内部端口</strong></li><li>✔ 内部服务保持监听 127.0.0.1 或 0.0.0.0 即可</li><li>✔ <strong>只要做了反代，后端端口就不需要开放！</strong></li></ul><p>用一句话总结：</p><blockquote><p><strong>DNS负责找服务器，安全组控制外网访问权限，端口决定服务位置，反代负责把外部请求转发到内部服务。因此，反代后的后端端口无需对外开放。</strong></p></blockquote>]]></content>
    
    
      
      
    <summary type="html">&lt;h1 id=&quot;dns端口安全组与反向代理&quot;&gt;DNS、端口、安全组与反向代理&lt;/h1&gt;
&lt;h2 id=&quot;dns只负责域名-ip-的映射&quot;&gt;1. DNS：只负责“域名 → IP 的映射”&lt;/h2&gt;
&lt;p&gt;DNS（域名解析）只做一件事：&lt;/p&gt;
&lt;pre class=&quot;languag</summary>
      
    
    
    
    <category term="网站搭建" scheme="https://blog.keaikeqing.cn/categories/%E7%BD%91%E7%AB%99%E6%90%AD%E5%BB%BA/"/>
    
    
  </entry>
  
  <entry>
    <title>Python系列之logging</title>
    <link href="https://blog.keaikeqing.cn/2025/03/18/Python/logging/"/>
    <id>https://blog.keaikeqing.cn/2025/03/18/Python/logging/</id>
    <published>2025-03-18T11:53:35.000Z</published>
    <updated>2025-03-19T08:53:35.000Z</updated>
    
    <content type="html"><![CDATA[<h1 id="logging">logging</h1><p>Python的logging模块提供了灵活的日志记录功能，可用于调试和记录程序运行信息。</p><h2 id="基本配置">1 基本配置</h2><p>将以下代码添加到Python文件中，以配置日志记录：</p><pre class="language-python" data-language="python"><code class="language-python"><span class="token keyword">import</span> logging<span class="token keyword">from</span> pathlib <span class="token keyword">import</span> Path<span class="token comment"># 配置日志文件路径</span>script_dir <span class="token operator">=</span> Path<span class="token punctuation">(</span>__file__<span class="token punctuation">)</span><span class="token punctuation">.</span>parent  <span class="token comment"># 获取当前脚本所在目录</span>log_file_path <span class="token operator">=</span> script_dir <span class="token operator">/</span> <span class="token string">'log_file.log'</span>  <span class="token comment"># 在脚本目录下创建日志文件</span><span class="token comment"># 配置日志</span>logging<span class="token punctuation">.</span>basicConfig<span class="token punctuation">(</span>    level<span class="token operator">=</span>logging<span class="token punctuation">.</span>DEBUG<span class="token punctuation">,</span>  <span class="token comment"># 设置日志级别</span>    <span class="token builtin">format</span><span class="token operator">=</span><span class="token string">'%(asctime)s - %(levelname)s - %(message)s'</span><span class="token punctuation">,</span>  <span class="token comment"># 设置日志格式</span>    handlers<span class="token operator">=</span><span class="token punctuation">[</span>        logging<span class="token punctuation">.</span>StreamHandler<span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">,</span>  <span class="token comment"># 输出到控制台</span>        logging<span class="token punctuation">.</span>FileHandler<span class="token punctuation">(</span>log_file_path<span class="token punctuation">,</span> encoding<span class="token operator">=</span><span class="token string">'utf-8'</span><span class="token punctuation">)</span>  <span class="token comment"># 同时输出到文件</span>    <span class="token punctuation">]</span><span class="token punctuation">)</span>logger <span class="token operator">=</span> logging<span class="token punctuation">.</span>getLogger<span class="token punctuation">(</span>__name__<span class="token punctuation">)</span>  <span class="token comment"># 获取当前模块的logger</span><span class="token comment"># 使用示例：不同级别的日志记录</span>logger<span class="token punctuation">.</span>debug<span class="token punctuation">(</span><span class="token string">"这是调试信息"</span><span class="token punctuation">)</span>logger<span class="token punctuation">.</span>info<span class="token punctuation">(</span><span class="token string">"这是一般信息"</span><span class="token punctuation">)</span> logger<span class="token punctuation">.</span>warning<span class="token punctuation">(</span><span class="token string">"这是警告信息"</span><span class="token punctuation">)</span>logger<span class="token punctuation">.</span>error<span class="token punctuation">(</span><span class="token string">"这是错误信息"</span><span class="token punctuation">)</span>logger<span class="token punctuation">.</span>critical<span class="token punctuation">(</span><span class="token string">"这是严重错误信息"</span><span class="token punctuation">)</span></code></pre><h2 id="高级配置">2 高级配置</h2><p>以下是一个更复杂的日志配置示例，作为模块使用。该示例展示了如何创建一个通用的日志配置类，支持多模块共享日志设置、按大小轮转、压缩旧日志、不同级别日志分文件、控制台输出和自定义过滤器。</p><pre class="language-python" data-language="python"><code class="language-python"><span class="token keyword">import</span> logging<span class="token keyword">import</span> os<span class="token keyword">import</span> gzip<span class="token keyword">from</span> logging<span class="token punctuation">.</span>handlers <span class="token keyword">import</span> RotatingFileHandler<span class="token keyword">from</span> typing <span class="token keyword">import</span> Optional<span class="token punctuation">,</span> Dict<span class="token punctuation">,</span> List<span class="token keyword">import</span> shutil<span class="token keyword">class</span> <span class="token class-name">CompressedRotatingFileHandler</span><span class="token punctuation">(</span>RotatingFileHandler<span class="token punctuation">)</span><span class="token punctuation">:</span>    <span class="token triple-quoted-string string">"""支持压缩的日志轮转处理器"""</span>    <span class="token keyword">def</span> <span class="token function">doRollover</span><span class="token punctuation">(</span>self<span class="token punctuation">)</span><span class="token punctuation">:</span>        <span class="token triple-quoted-string string">"""重写doRollover方法来添加压缩功能"""</span>        <span class="token keyword">if</span> self<span class="token punctuation">.</span>stream<span class="token punctuation">:</span>            self<span class="token punctuation">.</span>stream<span class="token punctuation">.</span>close<span class="token punctuation">(</span><span class="token punctuation">)</span>            self<span class="token punctuation">.</span>stream <span class="token operator">=</span> <span class="token boolean">None</span>        <span class="token comment"># 如果备份文件已存在，需要先轮转这些文件</span>        <span class="token keyword">if</span> self<span class="token punctuation">.</span>backupCount <span class="token operator">></span> <span class="token number">0</span><span class="token punctuation">:</span>            <span class="token comment"># 删除最旧的一个文件(如果存在)</span>            oldest_backup <span class="token operator">=</span> <span class="token string-interpolation"><span class="token string">f"</span><span class="token interpolation"><span class="token punctuation">&#123;</span>self<span class="token punctuation">.</span>baseFilename<span class="token punctuation">&#125;</span></span><span class="token string">.</span><span class="token interpolation"><span class="token punctuation">&#123;</span>self<span class="token punctuation">.</span>backupCount<span class="token punctuation">&#125;</span></span><span class="token string">.gz"</span></span>            <span class="token keyword">if</span> os<span class="token punctuation">.</span>path<span class="token punctuation">.</span>exists<span class="token punctuation">(</span>oldest_backup<span class="token punctuation">)</span><span class="token punctuation">:</span>                os<span class="token punctuation">.</span>remove<span class="token punctuation">(</span>oldest_backup<span class="token punctuation">)</span>            <span class="token comment"># 轮转现有的备份文件</span>            <span class="token keyword">for</span> i <span class="token keyword">in</span> <span class="token builtin">range</span><span class="token punctuation">(</span>self<span class="token punctuation">.</span>backupCount <span class="token operator">-</span> <span class="token number">1</span><span class="token punctuation">,</span> <span class="token number">0</span><span class="token punctuation">,</span> <span class="token operator">-</span><span class="token number">1</span><span class="token punctuation">)</span><span class="token punctuation">:</span>                source <span class="token operator">=</span> <span class="token string-interpolation"><span class="token string">f"</span><span class="token interpolation"><span class="token punctuation">&#123;</span>self<span class="token punctuation">.</span>baseFilename<span class="token punctuation">&#125;</span></span><span class="token string">.</span><span class="token interpolation"><span class="token punctuation">&#123;</span>i<span class="token punctuation">&#125;</span></span><span class="token string">.gz"</span></span>                dest <span class="token operator">=</span> <span class="token string-interpolation"><span class="token string">f"</span><span class="token interpolation"><span class="token punctuation">&#123;</span>self<span class="token punctuation">.</span>baseFilename<span class="token punctuation">&#125;</span></span><span class="token string">.</span><span class="token interpolation"><span class="token punctuation">&#123;</span>i <span class="token operator">+</span> <span class="token number">1</span><span class="token punctuation">&#125;</span></span><span class="token string">.gz"</span></span>                <span class="token keyword">if</span> os<span class="token punctuation">.</span>path<span class="token punctuation">.</span>exists<span class="token punctuation">(</span>source<span class="token punctuation">)</span><span class="token punctuation">:</span>                    <span class="token keyword">if</span> os<span class="token punctuation">.</span>path<span class="token punctuation">.</span>exists<span class="token punctuation">(</span>dest<span class="token punctuation">)</span><span class="token punctuation">:</span>                        os<span class="token punctuation">.</span>remove<span class="token punctuation">(</span>dest<span class="token punctuation">)</span>                    os<span class="token punctuation">.</span>rename<span class="token punctuation">(</span>source<span class="token punctuation">,</span> dest<span class="token punctuation">)</span>            <span class="token comment"># 压缩当前日志为第一个备份</span>            dest <span class="token operator">=</span> <span class="token string-interpolation"><span class="token string">f"</span><span class="token interpolation"><span class="token punctuation">&#123;</span>self<span class="token punctuation">.</span>baseFilename<span class="token punctuation">&#125;</span></span><span class="token string">.1.gz"</span></span>            <span class="token keyword">if</span> os<span class="token punctuation">.</span>path<span class="token punctuation">.</span>exists<span class="token punctuation">(</span>self<span class="token punctuation">.</span>baseFilename<span class="token punctuation">)</span><span class="token punctuation">:</span>                <span class="token keyword">with</span> <span class="token builtin">open</span><span class="token punctuation">(</span>self<span class="token punctuation">.</span>baseFilename<span class="token punctuation">,</span> <span class="token string">'rb'</span><span class="token punctuation">)</span> <span class="token keyword">as</span> f_in<span class="token punctuation">:</span>                    <span class="token keyword">with</span> gzip<span class="token punctuation">.</span><span class="token builtin">open</span><span class="token punctuation">(</span>dest<span class="token punctuation">,</span> <span class="token string">'wb'</span><span class="token punctuation">)</span> <span class="token keyword">as</span> f_out<span class="token punctuation">:</span>                        shutil<span class="token punctuation">.</span>copyfileobj<span class="token punctuation">(</span>f_in<span class="token punctuation">,</span> f_out<span class="token punctuation">)</span>                os<span class="token punctuation">.</span>remove<span class="token punctuation">(</span>self<span class="token punctuation">.</span>baseFilename<span class="token punctuation">)</span>        <span class="token comment"># 创建新的空日志文件</span>        self<span class="token punctuation">.</span>mode <span class="token operator">=</span> <span class="token string">'w'</span>        self<span class="token punctuation">.</span>stream <span class="token operator">=</span> self<span class="token punctuation">.</span>_open<span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token keyword">class</span> <span class="token class-name">KeywordFilter</span><span class="token punctuation">(</span>logging<span class="token punctuation">.</span>Filter<span class="token punctuation">)</span><span class="token punctuation">:</span>    <span class="token keyword">def</span> <span class="token function">__init__</span><span class="token punctuation">(</span>self<span class="token punctuation">,</span> keyword<span class="token punctuation">:</span> <span class="token builtin">str</span><span class="token punctuation">)</span><span class="token punctuation">:</span>        <span class="token builtin">super</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span>__init__<span class="token punctuation">(</span><span class="token punctuation">)</span>        self<span class="token punctuation">.</span>keyword <span class="token operator">=</span> keyword    <span class="token keyword">def</span> <span class="token function">filter</span><span class="token punctuation">(</span>self<span class="token punctuation">,</span> record<span class="token punctuation">:</span> logging<span class="token punctuation">.</span>LogRecord<span class="token punctuation">)</span> <span class="token operator">-</span><span class="token operator">></span> <span class="token builtin">bool</span><span class="token punctuation">:</span>        <span class="token comment"># 仅当日志消息包含特定关键词时返回 True</span>        <span class="token keyword">return</span> self<span class="token punctuation">.</span>keyword <span class="token keyword">in</span> record<span class="token punctuation">.</span>msg<span class="token keyword">class</span> <span class="token class-name">LoggerConfig</span><span class="token punctuation">:</span>    <span class="token triple-quoted-string string">"""    通用日志配置类，支持多模块共享日志设置    特性：    1. 按大小轮转    2. 日志压缩    3. 不同级别日志分文件    4. 支持控制台输出    5. 支持自定义过滤器    """</span>    _instance <span class="token operator">=</span> <span class="token boolean">None</span>    _initialized <span class="token operator">=</span> <span class="token boolean">False</span>    _loggers<span class="token punctuation">:</span> Dict<span class="token punctuation">[</span><span class="token builtin">str</span><span class="token punctuation">,</span> logging<span class="token punctuation">.</span>Logger<span class="token punctuation">]</span> <span class="token operator">=</span> <span class="token punctuation">&#123;</span><span class="token punctuation">&#125;</span>    <span class="token keyword">def</span> <span class="token function">__new__</span><span class="token punctuation">(</span>cls<span class="token punctuation">,</span> <span class="token operator">*</span>args<span class="token punctuation">,</span> <span class="token operator">**</span>kwargs<span class="token punctuation">)</span><span class="token punctuation">:</span>        <span class="token keyword">if</span> cls<span class="token punctuation">.</span>_instance <span class="token keyword">is</span> <span class="token boolean">None</span><span class="token punctuation">:</span>            cls<span class="token punctuation">.</span>_instance <span class="token operator">=</span> <span class="token builtin">super</span><span class="token punctuation">(</span>LoggerConfig<span class="token punctuation">,</span> cls<span class="token punctuation">)</span><span class="token punctuation">.</span>__new__<span class="token punctuation">(</span>cls<span class="token punctuation">)</span>        <span class="token keyword">return</span> cls<span class="token punctuation">.</span>_instance    <span class="token keyword">def</span> <span class="token function">__init__</span><span class="token punctuation">(</span>self<span class="token punctuation">,</span>                 log_dir<span class="token punctuation">:</span> <span class="token builtin">str</span> <span class="token operator">=</span> <span class="token string">'./logs'</span><span class="token punctuation">,</span>                 log_level<span class="token punctuation">:</span> <span class="token builtin">int</span> <span class="token operator">=</span> logging<span class="token punctuation">.</span>INFO<span class="token punctuation">,</span>                 max_bytes<span class="token punctuation">:</span> <span class="token builtin">int</span> <span class="token operator">=</span> <span class="token number">5</span> <span class="token operator">*</span> <span class="token number">1024</span> <span class="token operator">*</span> <span class="token number">1024</span><span class="token punctuation">,</span>  <span class="token comment"># 默认5MB</span>                 backup_count<span class="token punctuation">:</span> <span class="token builtin">int</span> <span class="token operator">=</span> <span class="token number">20</span><span class="token punctuation">,</span>                 enable_console<span class="token punctuation">:</span> <span class="token builtin">bool</span> <span class="token operator">=</span> <span class="token boolean">True</span><span class="token punctuation">,</span>                 compress_logs<span class="token punctuation">:</span> <span class="token builtin">bool</span> <span class="token operator">=</span> <span class="token boolean">True</span><span class="token punctuation">,</span>                 custom_filters<span class="token punctuation">:</span> List<span class="token punctuation">[</span>Dict<span class="token punctuation">]</span> <span class="token operator">=</span> <span class="token boolean">None</span><span class="token punctuation">,</span>                 extra_handlers<span class="token punctuation">:</span> List<span class="token punctuation">[</span>logging<span class="token punctuation">.</span>Handler<span class="token punctuation">]</span> <span class="token operator">=</span> <span class="token boolean">None</span><span class="token punctuation">)</span><span class="token punctuation">:</span>        <span class="token triple-quoted-string string">"""        初始化日志配置        Args:            log_dir: 日志目录            log_level: 基础日志级别            max_bytes: 单个日志文件最大字节数            backup_count: 保留的备份文件数量            enable_console: 是否启用控制台输出            compress_logs: 是否压缩旧日志            custom_filters: 自定义过滤器列表            extra_handlers: 额外的处理器列表        """</span>        <span class="token comment"># 单例模式：确保只初始化一次</span>        <span class="token keyword">if</span> self<span class="token punctuation">.</span>_initialized<span class="token punctuation">:</span>            <span class="token keyword">return</span>        self<span class="token punctuation">.</span>log_dir <span class="token operator">=</span> log_dir        self<span class="token punctuation">.</span>log_level <span class="token operator">=</span> log_level        self<span class="token punctuation">.</span>max_bytes <span class="token operator">=</span> max_bytes        self<span class="token punctuation">.</span>backup_count <span class="token operator">=</span> backup_count        self<span class="token punctuation">.</span>enable_console <span class="token operator">=</span> enable_console        self<span class="token punctuation">.</span>compress_logs <span class="token operator">=</span> compress_logs        self<span class="token punctuation">.</span>custom_filters <span class="token operator">=</span> custom_filters <span class="token keyword">or</span> <span class="token punctuation">[</span><span class="token punctuation">]</span>        self<span class="token punctuation">.</span>extra_handlers <span class="token operator">=</span> extra_handlers <span class="token keyword">or</span> <span class="token punctuation">[</span><span class="token punctuation">]</span>        <span class="token comment"># 创建日志目录</span>        os<span class="token punctuation">.</span>makedirs<span class="token punctuation">(</span>log_dir<span class="token punctuation">,</span> exist_ok<span class="token operator">=</span><span class="token boolean">True</span><span class="token punctuation">)</span>        <span class="token comment"># 默认格式化器</span>        self<span class="token punctuation">.</span>default_formatter <span class="token operator">=</span> logging<span class="token punctuation">.</span>Formatter<span class="token punctuation">(</span>            <span class="token string">'%(asctime)s - %(name)s - %(levelname)s - %(message)s'</span>        <span class="token punctuation">)</span>        self<span class="token punctuation">.</span>_initialized <span class="token operator">=</span> <span class="token boolean">True</span>    <span class="token keyword">def</span> <span class="token function">get_logger</span><span class="token punctuation">(</span>self<span class="token punctuation">,</span> name<span class="token punctuation">:</span> <span class="token builtin">str</span><span class="token punctuation">,</span> formatter<span class="token punctuation">:</span> Optional<span class="token punctuation">[</span>logging<span class="token punctuation">.</span>Formatter<span class="token punctuation">]</span> <span class="token operator">=</span> <span class="token boolean">None</span><span class="token punctuation">)</span> <span class="token operator">-</span><span class="token operator">></span> logging<span class="token punctuation">.</span>Logger<span class="token punctuation">:</span>        <span class="token triple-quoted-string string">"""        获取或创建logger        Args:            name: logger名称            formatter: 自定义格式化器        Returns:            logging.Logger: 配置好的logger        """</span>        <span class="token comment"># 如果已存在该logger，直接返回</span>        <span class="token keyword">if</span> name <span class="token keyword">in</span> self<span class="token punctuation">.</span>_loggers<span class="token punctuation">:</span>            <span class="token keyword">return</span> self<span class="token punctuation">.</span>_loggers<span class="token punctuation">[</span>name<span class="token punctuation">]</span>        <span class="token comment"># 创建logger</span>        logger <span class="token operator">=</span> logging<span class="token punctuation">.</span>getLogger<span class="token punctuation">(</span>name<span class="token punctuation">)</span>        <span class="token comment"># 清理可能存在的handlers</span>        <span class="token keyword">if</span> logger<span class="token punctuation">.</span>hasHandlers<span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">:</span>            logger<span class="token punctuation">.</span>handlers<span class="token punctuation">.</span>clear<span class="token punctuation">(</span><span class="token punctuation">)</span>        <span class="token comment"># 设置日志级别</span>        logger<span class="token punctuation">.</span>setLevel<span class="token punctuation">(</span>self<span class="token punctuation">.</span>log_level<span class="token punctuation">)</span>        formatter <span class="token operator">=</span> formatter <span class="token keyword">or</span> self<span class="token punctuation">.</span>default_formatter        <span class="token comment"># 添加控制台处理器</span>        <span class="token keyword">if</span> self<span class="token punctuation">.</span>enable_console<span class="token punctuation">:</span>            console_handler <span class="token operator">=</span> logging<span class="token punctuation">.</span>StreamHandler<span class="token punctuation">(</span><span class="token punctuation">)</span>            console_handler<span class="token punctuation">.</span>setFormatter<span class="token punctuation">(</span>formatter<span class="token punctuation">)</span>            logger<span class="token punctuation">.</span>addHandler<span class="token punctuation">(</span>console_handler<span class="token punctuation">)</span>        <span class="token comment"># 为不同级别创建单独的日志文件</span>        log_levels <span class="token operator">=</span> <span class="token punctuation">[</span>            <span class="token punctuation">(</span>logging<span class="token punctuation">.</span>DEBUG<span class="token punctuation">,</span> <span class="token string">'debug'</span><span class="token punctuation">)</span><span class="token punctuation">,</span>            <span class="token punctuation">(</span>logging<span class="token punctuation">.</span>INFO<span class="token punctuation">,</span> <span class="token string">'info'</span><span class="token punctuation">)</span><span class="token punctuation">,</span>            <span class="token punctuation">(</span>logging<span class="token punctuation">.</span>WARNING<span class="token punctuation">,</span> <span class="token string">'warning'</span><span class="token punctuation">)</span><span class="token punctuation">,</span>            <span class="token punctuation">(</span>logging<span class="token punctuation">.</span>ERROR<span class="token punctuation">,</span> <span class="token string">'error'</span><span class="token punctuation">)</span><span class="token punctuation">,</span>            <span class="token punctuation">(</span>logging<span class="token punctuation">.</span>CRITICAL<span class="token punctuation">,</span> <span class="token string">'critical'</span><span class="token punctuation">)</span>        <span class="token punctuation">]</span>        <span class="token keyword">for</span> level<span class="token punctuation">,</span> level_name <span class="token keyword">in</span> log_levels<span class="token punctuation">:</span>            <span class="token keyword">if</span> level <span class="token operator">>=</span> self<span class="token punctuation">.</span>log_level<span class="token punctuation">:</span>                handler <span class="token operator">=</span> self<span class="token punctuation">.</span>_create_rotating_handler<span class="token punctuation">(</span>                    os<span class="token punctuation">.</span>path<span class="token punctuation">.</span>join<span class="token punctuation">(</span>self<span class="token punctuation">.</span>log_dir<span class="token punctuation">,</span> <span class="token string-interpolation"><span class="token string">f"LOG_</span><span class="token interpolation"><span class="token punctuation">&#123;</span>level_name<span class="token punctuation">&#125;</span></span><span class="token string">.log"</span></span><span class="token punctuation">)</span><span class="token punctuation">,</span>                    formatter<span class="token punctuation">,</span>                    level                <span class="token punctuation">)</span>                logger<span class="token punctuation">.</span>addHandler<span class="token punctuation">(</span>handler<span class="token punctuation">)</span>        <span class="token comment"># 添加额外的处理器</span>        <span class="token keyword">if</span> self<span class="token punctuation">.</span>extra_handlers<span class="token punctuation">:</span>            <span class="token keyword">for</span> handler <span class="token keyword">in</span> self<span class="token punctuation">.</span>extra_handlers<span class="token punctuation">:</span>                handler<span class="token punctuation">.</span>setFormatter<span class="token punctuation">(</span>formatter<span class="token punctuation">)</span>                logger<span class="token punctuation">.</span>addHandler<span class="token punctuation">(</span>handler<span class="token punctuation">)</span>        <span class="token comment"># 添加自定义过滤器</span>        <span class="token keyword">if</span> self<span class="token punctuation">.</span>custom_filters<span class="token punctuation">:</span>            <span class="token keyword">for</span> filter_config <span class="token keyword">in</span> self<span class="token punctuation">.</span>custom_filters<span class="token punctuation">:</span>                filter_class <span class="token operator">=</span> filter_config<span class="token punctuation">.</span>get<span class="token punctuation">(</span><span class="token string">'filter_class'</span><span class="token punctuation">)</span>                args <span class="token operator">=</span> filter_config<span class="token punctuation">.</span>get<span class="token punctuation">(</span><span class="token string">'args'</span><span class="token punctuation">,</span> <span class="token punctuation">&#123;</span><span class="token punctuation">&#125;</span><span class="token punctuation">)</span>                <span class="token keyword">if</span> filter_class<span class="token punctuation">:</span>                    filter_instance <span class="token operator">=</span> filter_class<span class="token punctuation">(</span><span class="token operator">**</span>args<span class="token punctuation">)</span>                    logger<span class="token punctuation">.</span>addFilter<span class="token punctuation">(</span>filter_instance<span class="token punctuation">)</span>        <span class="token comment"># 禁止向父logger传递日志</span>        logger<span class="token punctuation">.</span>propagate <span class="token operator">=</span> <span class="token boolean">False</span>        <span class="token comment"># 缓存logger实例</span>        self<span class="token punctuation">.</span>_loggers<span class="token punctuation">[</span>name<span class="token punctuation">]</span> <span class="token operator">=</span> logger        <span class="token keyword">return</span> logger    <span class="token keyword">def</span> <span class="token function">_create_rotating_handler</span><span class="token punctuation">(</span>self<span class="token punctuation">,</span>                                 filename<span class="token punctuation">:</span> <span class="token builtin">str</span><span class="token punctuation">,</span>                                 formatter<span class="token punctuation">:</span> logging<span class="token punctuation">.</span>Formatter<span class="token punctuation">,</span>                                 level<span class="token punctuation">:</span> <span class="token builtin">int</span><span class="token punctuation">)</span> <span class="token operator">-</span><span class="token operator">></span> RotatingFileHandler<span class="token punctuation">:</span>        <span class="token triple-quoted-string string">"""        创建轮转处理器        Args:            filename: 日志文件名            formatter: 格式化器            level: 日志级别        Returns:            RotatingFileHandler: 配置好的处理器        """</span>        handler_class <span class="token operator">=</span> CompressedRotatingFileHandler <span class="token keyword">if</span> self<span class="token punctuation">.</span>compress_logs <span class="token keyword">else</span> RotatingFileHandler        handler <span class="token operator">=</span> handler_class<span class="token punctuation">(</span>            filename<span class="token punctuation">,</span>            maxBytes<span class="token operator">=</span>self<span class="token punctuation">.</span>max_bytes<span class="token punctuation">,</span>            backupCount<span class="token operator">=</span>self<span class="token punctuation">.</span>backup_count<span class="token punctuation">,</span>            encoding<span class="token operator">=</span><span class="token string">'utf-8'</span>        <span class="token punctuation">)</span>        handler<span class="token punctuation">.</span>setLevel<span class="token punctuation">(</span>level<span class="token punctuation">)</span>        handler<span class="token punctuation">.</span>setFormatter<span class="token punctuation">(</span>formatter<span class="token punctuation">)</span>        <span class="token keyword">return</span> handler<span class="token keyword">def</span> <span class="token function">setLogConfig</span><span class="token punctuation">(</span>module_name<span class="token operator">=</span><span class="token boolean">None</span><span class="token punctuation">,</span>                 log_dir<span class="token operator">=</span><span class="token string">'./logs'</span><span class="token punctuation">,</span>                 log_level<span class="token operator">=</span>logging<span class="token punctuation">.</span>DEBUG<span class="token punctuation">,</span>                 max_bytes<span class="token operator">=</span><span class="token number">5</span> <span class="token operator">*</span> <span class="token number">1024</span> <span class="token operator">*</span> <span class="token number">1024</span><span class="token punctuation">,</span>  <span class="token comment"># 5MB</span>                 backup_count<span class="token operator">=</span><span class="token number">20</span><span class="token punctuation">,</span>                 enable_console<span class="token operator">=</span><span class="token boolean">True</span><span class="token punctuation">,</span>                 compress_logs<span class="token operator">=</span><span class="token boolean">True</span><span class="token punctuation">,</span>                 custom_filters<span class="token operator">=</span><span class="token boolean">None</span><span class="token punctuation">,</span>                 extra_handlers<span class="token operator">=</span><span class="token boolean">None</span><span class="token punctuation">)</span><span class="token punctuation">:</span>    <span class="token triple-quoted-string string">"""    快速设置并获取logger的便捷函数    Args:        module_name: 模块名称，用于标识日志来源        log_dir: 日志保存目录        log_level: 日志级别        max_bytes: 单个日志文件最大字节数        backup_count: 保留的备份文件数量        enable_console: 是否启用控制台输出        compress_logs: 是否压缩旧日志        custom_filters: 自定义过滤器列表，例如custom_filters=[&#123;'filter_class': KeywordFilter, 'args': &#123;'keyword': 'critical'&#125;&#125;]        extra_handlers: 额外的处理器列表    Returns:        logging.Logger: 配置好的logger实例    """</span>    <span class="token comment"># 初始化日志配置</span>    log_config <span class="token operator">=</span> LoggerConfig<span class="token punctuation">(</span>        log_dir<span class="token operator">=</span>log_dir<span class="token punctuation">,</span>        log_level<span class="token operator">=</span>log_level<span class="token punctuation">,</span>        max_bytes<span class="token operator">=</span>max_bytes<span class="token punctuation">,</span>        backup_count<span class="token operator">=</span>backup_count<span class="token punctuation">,</span>        enable_console<span class="token operator">=</span>enable_console<span class="token punctuation">,</span>        compress_logs<span class="token operator">=</span>compress_logs<span class="token punctuation">,</span>        custom_filters<span class="token operator">=</span>custom_filters<span class="token punctuation">,</span>        extra_handlers<span class="token operator">=</span>extra_handlers    <span class="token punctuation">)</span>    <span class="token comment"># 自动获取调用者的模块名称</span>    <span class="token keyword">if</span> module_name <span class="token keyword">is</span> <span class="token boolean">None</span><span class="token punctuation">:</span>        <span class="token keyword">import</span> inspect        caller_frame <span class="token operator">=</span> inspect<span class="token punctuation">.</span>stack<span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">[</span><span class="token number">1</span><span class="token punctuation">]</span>        module_name <span class="token operator">=</span> caller_frame<span class="token punctuation">.</span>frame<span class="token punctuation">.</span>f_globals<span class="token punctuation">[</span><span class="token string">'__name__'</span><span class="token punctuation">]</span>        <span class="token comment"># 如果没有提供module_name，则通过调用栈获取调用者的__name__</span>    <span class="token keyword">return</span> log_config<span class="token punctuation">.</span>get_logger<span class="token punctuation">(</span>name<span class="token operator">=</span>module_name<span class="token punctuation">,</span> formatter<span class="token operator">=</span><span class="token boolean">None</span><span class="token punctuation">)</span><span class="token comment"># 使用示例</span><span class="token keyword">if</span> __name__ <span class="token operator">==</span> <span class="token string">'__main__'</span><span class="token punctuation">:</span>    logger <span class="token operator">=</span> setLogConfig<span class="token punctuation">(</span><span class="token string">'test_module'</span><span class="token punctuation">)</span>    <span class="token comment"># 使用logger</span>    logger<span class="token punctuation">.</span>debug<span class="token punctuation">(</span><span class="token string">'这是一条调试日志'</span><span class="token punctuation">)</span>    logger<span class="token punctuation">.</span>info<span class="token punctuation">(</span><span class="token string">'这是一条信息日志'</span><span class="token punctuation">)</span>    logger<span class="token punctuation">.</span>warning<span class="token punctuation">(</span><span class="token string">'这是一条警告日志'</span><span class="token punctuation">)</span>    logger<span class="token punctuation">.</span>error<span class="token punctuation">(</span><span class="token string">'这是一条错误日志'</span><span class="token punctuation">)</span>    logger<span class="token punctuation">.</span>critical<span class="token punctuation">(</span><span class="token string">'这是一条严重错误日志'</span><span class="token punctuation">)</span></code></pre>]]></content>
    
    
      
      
    <summary type="html">&lt;h1 id=&quot;logging&quot;&gt;logging&lt;/h1&gt;
&lt;p&gt;Python的logging模块提供了灵活的日志记录功能，可用于调试和记录程序运行信息。&lt;/p&gt;
&lt;h2 id=&quot;基本配置&quot;&gt;1 基本配置&lt;/h2&gt;
&lt;p&gt;将以下代码添加到Python文件中，以配置日志记录：&lt;/p</summary>
      
    
    
    
    <category term="Python" scheme="https://blog.keaikeqing.cn/categories/Python/"/>
    
    
  </entry>
  
  <entry>
    <title>博客资源托管方案</title>
    <link href="https://blog.keaikeqing.cn/2025/03/03/Web/assets/"/>
    <id>https://blog.keaikeqing.cn/2025/03/03/Web/assets/</id>
    <published>2025-03-03T02:00:00.000Z</published>
    <updated>2025-03-03T02:00:00.000Z</updated>
    
    <content type="html"><![CDATA[<h1 id="博客资源托管方案">博客资源托管方案</h1><h2 id="资源托管方案">资源托管方案</h2><p>在搭建博客时，通常会涉及到一些静态资源的托管问题，比如图片、视频、音频等。这里列出几种常见的资源托管方案，供参考：</p><table><colgroup><col style="width: 25%" /><col style="width: 25%" /><col style="width: 25%" /><col style="width: 25%" /></colgroup><thead><tr><th>标号</th><th>方案</th><th>具体方法</th><th>推荐指数</th></tr></thead><tbody><tr><td>1</td><td>本地</td><td>放在博客source文件夹下，使用相对路径，跟随博客同步到github仓库</td><td>★★</td></tr><tr><td>2</td><td>github repo</td><td>将assets资源放在单独的github仓库管理，并打tag</td><td>★★★</td></tr><tr><td>3</td><td>云服务器</td><td>将assets放在云服务器静态网页上</td><td>★★★★</td></tr><tr><td>4</td><td>npm</td><td>将assets发布到npm，经过 unpkg/jsDelivr CDN使用</td><td>★★★★</td></tr></tbody></table><h2 id="优缺点分析">优缺点分析</h2><h3 id="本地">1 本地</h3><ul><li>优点：<ul><li>方便，直接放在博客的source文件夹下，使用相对路径引用即可。</li><li>不需要额外的配置和管理。</li><li>跟随博客同步到github仓库，方便备份和版本控制。</li></ul></li><li>缺点：<ul><li>资源文件较大时，可能会导致博客仓库过大，影响克隆和下载速度。</li><li>仓库容量有限，超过限制后也难以清理。</li></ul></li></ul><h3 id="github-repo">2 github repo</h3><p>一般配合jsDelivr使用，cdn加速。</p><ul><li>优点：<ul><li>可以将assets资源放在单独的github仓库管理，避免影响博客仓库的大小。</li><li>可以使用github的版本控制和备份功能，方便管理和维护。</li></ul></li><li>缺点：<ul><li>需要额外的配置和管理，增加了复杂性。</li><li>并不推荐github做资源托管，限制大文件和高流量访问，尤其是直接调用raw.githubusercontent.com的资源。</li></ul></li></ul><h3 id="云服务器">3 云服务器</h3><ul><li>优点：<ul><li>可以将assets放在云服务器静态网页上，方便管理和维护。</li><li>资源完全自由掌控，可以设置访问权限和流量限制，隐私性最好。</li><li>容量和带宽取决于云服务器的配置。</li></ul></li><li>缺点：<ul><li>需要额外的配置和管理，增加了复杂性。</li><li>需要支付云服务器的费用，增加了成本。</li></ul></li></ul><h3 id="npm">4 npm</h3><ul><li>优点：<ul><li>可以将assets发布到npm，经过 unpkg/jsDelivr CDN使用，方便快捷。</li><li>可以使用npm的版本控制和备份功能，方便管理和维护。</li></ul></li><li>缺点：<ul><li>需要额外的配置和管理，增加了复杂性。</li><li>资源更新后需要重新发布到npm</li><li>包的容量有限制</li></ul></li></ul><h3 id="总结">总结</h3><p>将资源分开管理</p><ol type="1"><li>将配置文件放在source文件夹下，使用相对路径引用。</li><li>将不常变动的资源打包为assets，依据喜好放于云服务器或者npm(使用jsDelivr/unpkgCDN)。</li><li>经常变动的资源如果较大，放在云服务器或者githubrepo(使用jsDelivr)上，如果较小，放在source文件夹下。</li><li>十分推荐为博客(和云服务器，如果使用的话)添加CloudflareCDN加速，免费版足够使用。</li></ol>]]></content>
    
    
      
      
    <summary type="html">&lt;h1 id=&quot;博客资源托管方案&quot;&gt;博客资源托管方案&lt;/h1&gt;
&lt;h2 id=&quot;资源托管方案&quot;&gt;资源托管方案&lt;/h2&gt;
&lt;p&gt;在搭建博客时，通常会涉及到一些静态资源的托管问题，比如图片、视频、音频等。这里列出几种常见的资源托管方案，供参考：&lt;/p&gt;
&lt;table&gt;
&lt;colgro</summary>
      
    
    
    
    <category term="网站搭建" scheme="https://blog.keaikeqing.cn/categories/%E7%BD%91%E7%AB%99%E6%90%AD%E5%BB%BA/"/>
    
    
  </entry>
  
  <entry>
    <title>内容分发网络 (CDN)</title>
    <link href="https://blog.keaikeqing.cn/2025/02/26/TechnicalNotes/CDN/"/>
    <id>https://blog.keaikeqing.cn/2025/02/26/TechnicalNotes/CDN/</id>
    <published>2025-02-26T06:09:26.000Z</published>
    <updated>2025-09-11T01:40:26.000Z</updated>
    
    <content type="html"><![CDATA[<h2 id="内容分发网络-cdn">内容分发网络 (CDN)</h2><p>CDN，全称 Content DeliveryNetwork（内容分发网络），是一种通过分布式部署的服务器网络来加速和优化内容传输的技术。它的核心目标是让用户能够就近获取所需的网页、视频、图片、脚本等内容，从而提升访问速度和体验，同时减轻源站服务器的压力。</p><h3 id="原理">1 原理</h3><ol type="1"><li>内容缓存与分发 (Caching &amp; Distribution):这是CDN最基础也是最关键的技术。CDN会将网站的静态内容（如图片、视频、CSS/JS文件等）主动或被动地从源站服务器缓存到遍布全球的边缘节点服务器上。当用户发起访问请求时，请求会被引导至离用户地理位置最近、网络延迟最低的边缘节点，该节点会直接将缓存的内容响应给用户，避免了对源站的直接访问，极大地缩短了物理距离和数据传输时间。</li><li>负载均衡与智能调度 (Load Balancing &amp; Intelligent Scheduling):CDN拥有一个智能的“大脑”——全局负载均衡（GSLB）系统。这个系统会实时监控所有边缘节点的健康状况、负载情况以及到用户的网络链路质量。当用户发起请求时，GSLB会综合分析用户的IP地址、地理位置、运营商网络等信息，通过DNS解析或HTTP重定向等技术，将用户的请求“调度”到最优的边缘节点上，确保用户获得最快、最稳定的访问体验。</li></ol><h3 id="工作流程">2 工作流程</h3><p>用户使用CDN服务访问一个网站的完整流程，可以分解为以下几个关键步骤：</p><p><strong>前提：</strong>网站主已经将其域名接入CDN服务，并在DNS服务商处将域名解析指向CDN服务商提供的CNAME记录。</p><ol type="1"><li><strong>用户发起请求：</strong> 当用户在浏览器中输入网址（例如<code>www.example.com</code>）后，计算机会向本地DNS服务器（LocalDNS）发起域名解析请求。</li><li><strong>CNAME解析至CDN的DNS调度系统：</strong> LocalDNS从域名的权威DNS服务器获取到该域名被配置了一条CNAME记录，指向了CDN服务商的域名（例如<code>www.example.com.cdn.cloudflare.net</code>）。于是，LocalDNS会再次向CDN的DNS调度系统发起请求。</li><li><strong>智能调度，返回最优节点IP：</strong>CDN的DNS调度系统（即GSLB）接收到请求后，会根据一系列策略进行智能决策：<ul><li><strong>地理位置判断：</strong>分析用户的IP地址，判断其所在的地理位置和运营商网络。</li><li><strong>节点健康度检测：</strong>排除掉故障或负载过高的边缘节点。</li><li><strong>网络质量探测：</strong>评估各个节点到用户的网络延迟和丢包率。</li><li>综合以上信息，选择出一个对该用户来说“最优”的边缘节点，并将其IP地址返回给LocalDNS。</li></ul></li><li><strong>获取最优节点IP：</strong> LocalDNS将获取到的最优边缘节点的IP地址返回给用户的计算机。</li><li><strong>用户与边缘节点建立连接：</strong>用户的浏览器向这个最优的边缘节点IP地址发起HTTP/HTTPS请求，请求获取具体的网页内容（如一张图片）。</li><li><strong>边缘节点响应：</strong><ul><li><strong>缓存命中（Cache Hit）：</strong>如果该边缘节点已经缓存了用户请求的内容，并且缓存尚未过期，它会直接将内容发送给用户。这是最理想、最快速的情况。</li><li><strong>缓存未命中（Cache Miss）：</strong>如果该节点没有缓存该内容，或者缓存已经过期，它会代表用户向源站服务器发起请求，获取最新的内容。</li></ul></li><li><strong>回源与缓存：</strong>边缘节点从源站获取到内容后，一方面将其发送给用户，另一方面会按照预设的缓存策略（如缓存时间、缓存规则等）将内容存储在本地，以便后续有相同请求时可以直接响应。</li></ol><p>通过这样一套完整、自动化的流程，CDN成功地将用户访问流量分散到了各个边缘节点，既减轻了源站服务器的压力，又显著提升了终端用户的访问速度和体验。</p><h3 id="优势">3 优势</h3><p>凭借其独特的技术原理，CDN被广泛应用于各种互联网业务场景，其主要优势包括：</p><ul><li><strong>加速网站访问：</strong>显著减少网页加载时间，提升用户体验，降低用户流失率。</li><li><strong>分担源站压力：</strong>大部分用户请求由CDN边缘节点处理，大幅降低源站服务器的负载和带宽消耗成本。</li><li><strong>提升可用性与可靠性：</strong>单个或多个节点的故障不会影响整个服务的可用性，CDN的分布式架构提供了天然的冗余。</li><li><strong>增强安全性：</strong>CDN可以隐藏源站IP，并提供DDoS攻击防护、WAF（Web应用防火墙）等安全功能，保护源站免受网络攻击。</li><li><strong>支持大规模分发：</strong>尤其适用于视频点播、直播、大文件下载、游戏加速等需要处理海量并发请求和高带宽流量的场景。</li></ul><p>CDN已经成为现代互联网不可或缺的基础设施，它通过智能的调度和分布式的缓存，有效地解决了因地理距离和网络拥塞带来的访问延迟问题，为全球用户提供了更快速、更可靠、更安全的网络访问体验。</p><h3 id="使用">4 使用</h3><p>常见的 CDN 服务有 <strong>cdnjs</strong>、<strong>unpkg</strong> 和<strong>jsDelivr</strong>，它们各自有不同的特点和功能。</p><ol type="1"><li>unpkg类型：只支持 npm 包，npm 包加速<ul><li>提供方<ul><li>unpkg官方: <code>https://unpkg.com</code></li></ul></li><li>示例:<code>{url}/@{organization}/{package-name}@{version}/path/to/file</code></li></ul></li><li>jsdelivr类型：支持 npm 包 和 GitHub 仓库，npm 包 和 GitHub 仓库加速<ul><li>提供方<ul><li>jsdelivr官方: <code>https://cdn.jsdelivr.net</code></li><li>fastly: <code>https://fastly.jsdelivr.net</code></li><li>cloudflare: <code>https://testingcf.jsdelivr.net</code></li><li>gcore: <code>gcore.jsdelivr.net</code></li></ul></li><li>npm:<code>{url}/npm/@{organization}/{package-name}@{version}/path/to/file</code></li><li>GitHub:<code>{url}/gh/{organization}/{repository}@{version}/path/to/file</code></li></ul></li><li>cdnjs类型：只支持已加入的开源库，JavaScript/CSS 库加速<ul><li>基础URL: <code>https://cdnjs.cloudflare.com/ajax/libs/</code></li><li>示例:<code>https://cdnjs.cloudflare.com/ajax/libs/jquery/3.6.0/jquery.min.js</code></li></ul></li></ol>]]></content>
    
    
      
      
    <summary type="html">&lt;h2 id=&quot;内容分发网络-cdn&quot;&gt;内容分发网络 (CDN)&lt;/h2&gt;
&lt;p&gt;CDN，全称 Content Delivery
Network（内容分发网络），是一种通过分布式部署的服务器网络来加速和优化内容传输的技术。它的核心目标是让用户能够就近获取所需的网页、视频、图片、脚</summary>
      
    
    
    
    <category term="随笔" scheme="https://blog.keaikeqing.cn/categories/%E9%9A%8F%E7%AC%94/"/>
    
    
  </entry>
  
  <entry>
    <title>git</title>
    <link href="https://blog.keaikeqing.cn/2025/02/25/TechnicalNotes/Git/"/>
    <id>https://blog.keaikeqing.cn/2025/02/25/TechnicalNotes/Git/</id>
    <published>2025-02-25T06:50:50.000Z</published>
    <updated>2025-03-19T06:50:50.000Z</updated>
    
    <content type="html"><![CDATA[<h2 id="gitignore">1. gitignore</h2><p><code>.gitignore</code> 文件用于指定哪些文件或目录在 Git版本控制中被忽略。它可以避免将不必要的文件（如编译产物、临时文件等）提交到版本库中。</p><pre class="language-ignore" data-language="ignore"><code class="language-ignore"><span class="token comment"># 忽略特定文件</span><span class="token entry string">secret.txt</span><span class="token comment"># 忽略特定目录</span><span class="token entry string">temp<span class="token punctuation">/</span></span><span class="token comment"># 忽略特定目录下的所有文件(但保留temp目录本身)</span><span class="token comment"># 如果 temp/ 目录是空的，Git 默认不会追踪空目录，通常要用 .gitkeep 之类的文件强制保留</span><span class="token entry string">temp<span class="token punctuation">/</span><span class="token operator">*</span></span><span class="token comment"># 忽略特定目录下的所有 .tmp 文件</span><span class="token entry string">temp<span class="token punctuation">/</span><span class="token operator">*</span>.tmp</span><span class="token comment"># 忽略特定目录下的所有文件和子目录，但保留某个文件</span><span class="token entry string">temp<span class="token punctuation">/</span><span class="token operator">*</span></span><span class="token entry string"><span class="token operator">!</span>temp<span class="token punctuation">/</span>keep.txt</span><span class="token comment"># 忽略特定目录下的所有文件和子目录，但保留某个目录</span><span class="token entry string">temp<span class="token punctuation">/</span><span class="token operator">*</span></span><span class="token entry string"><span class="token operator">!</span>temp<span class="token punctuation">/</span>keep<span class="token punctuation">/</span></span></code></pre><h2 id="清除已跟踪的文件">2. 清除已跟踪的文件</h2><p>不会删除 Git 提交历史中的文件，它 只删除工作区（workingdirectory）和未跟踪的文件，不会影响 Git 版本库的提交记录:</p><pre class="language-bash" data-language="bash"><code class="language-bash"><span class="token function">git</span> clean <span class="token parameter variable">-n</span> <span class="token parameter variable">-d</span> <span class="token parameter variable">-x</span>  <span class="token comment"># 预览即将删除的文件</span><span class="token function">git</span> clean <span class="token parameter variable">-f</span> <span class="token parameter variable">-d</span> <span class="token parameter variable">-x</span>  <span class="token comment"># 确认无误后执行删除</span></code></pre><h2 id="回退以前的提交">3. 回退以前的提交</h2><h3 id="reset-硬回退">3.1 reset 硬回退</h3><pre class="language-bash" data-language="bash"><code class="language-bash"><span class="token function">git</span> reset <span class="token parameter variable">--hard</span> commit <span class="token function">id</span></code></pre><p>此时再推到远程仓库用 <code>git push</code> 会报错，需要用<code>git push -f</code> 强推上去</p><h3 id="revert-软回退">3.2 revert 软回退</h3><pre class="language-bash" data-language="bash"><code class="language-bash"><span class="token function">git</span> revert <span class="token parameter variable">-n</span> commit <span class="token function">id</span></code></pre><p>此时会在当前分支上生成一个新的提交，内容是将指定的提交回退到当前分支的状态。注意：<code>-n</code>选项表示不会立即创建一个新的提交，而是将变更放入暂存区</p><h2 id="提交规范">4. 提交规范</h2><h3 id="提交信息的基本结构">4.1 提交信息的基本结构</h3><p>一个标准的 Git 提交信息通常包括三部分：</p><pre class="language-none"><code class="language-none">&lt;类型&gt;(&lt;范围&gt;): &lt;简短描述&gt;&lt;详细描述&gt;&lt;页脚注释&gt;</code></pre><ul><li><strong>类型 (Type)</strong>：描述提交的类别，指明提交的性质。</li><li><strong>范围 (Scope)</strong>：指定变更的具体模块、功能或文件。</li><li><strong>简短描述</strong>：对本次提交的简要总结。</li><li><strong>详细描述</strong>（可选）：提供更详细的信息，解释为什么要做这个提交、如何做的、解决了什么问题等。</li><li><strong>页脚注释</strong>（可选）：链接到项目管理工具中的任务、bug或故事等。</li></ul><h3 id="常见的提交类型">4.2 <strong>常见的提交类型</strong></h3><p>这些类型通常是采用 <ahref="https://www.conventionalcommits.org/zh-hans">ConventionalCommits</a> 标准的常见值，目的是使提交信息一致且具有结构化。</p><ol type="1"><li><code>fix</code>: 类型 为 fix 的提交表示在代码库中修复了一个bug。</li><li><code>feat</code>: 类型 为 feat的提交表示在代码库中新增了一个功能。</li><li><code>BREAKING CHANGE</code>: 在脚注中包含 BREAKING CHANGE: 或<类型>(范围) 后面有一个 ! 的提交，表示引入了破坏性 API 变更。破坏性变更可以是任意<em>类型</em>提交的一部分。</li><li><code>build</code>:用于修改项目构建系统，例如修改依赖库、外部接口或者升级 Node版本等；</li><li><code>chore</code>:用于对非业务性代码进行修改，例如修改构建流程或者工具配置等；</li><li><code>ci</code>: 用于修改持续集成流程，例如修改 Travis、Jenkins等工作流配置；</li><li><code>docs</code>: 用于修改文档，例如修改 README 文件、API文档等；</li><li><code>style</code>:用于修改代码的样式，例如调整缩进、空格、空行等；</li><li><code>refactor</code>:用于重构代码，例如修改代码结构、变量名、函数名等但不修改功能逻辑；</li><li><code>perf</code>:用于优化性能，例如提升代码的性能、减少内存占用等；</li><li><code>test</code>:用于修改测试用例，例如添加、删除、修改代码的测试用例等。</li></ol><p>每次提交应该只包含一个逻辑变更，避免混合多个不相关的修改。</p><h2 id="git全局配置推荐">5. git全局配置推荐</h2><pre class="language-gitconfig" data-language="gitconfig"><code class="language-gitconfig">[user]    name &#x3D; xx    email &#x3D; xx@xx.com[http &quot;https:&#x2F;&#x2F;github.com&quot;]    proxy &#x3D; 127.0.0.1:7890       # GitHub 代理，可根据实际网络环境修改[core]    autocrlf &#x3D; true              # 自动转换换行符，Windows 推荐 true，macOS&#x2F;Linux 推荐 input    quotepath &#x3D; false            # 中文路径不再转义显示为 \uXXXX    editor &#x3D; code --wait         # 默认编辑器 VSCode，可改为 notepad 或 vim    preloadindex &#x3D; true          # 加快 git status 速度    fscache &#x3D; true               # Windows 下缓存文件系统信息，提高性能    untrackedCache &#x3D; true        # 加速未跟踪文件检测，特别是大仓库[color]    ui &#x3D; auto                    # Git 命令输出自动彩色显示（log、diff 等）[alias]    st &#x3D; status                  # git st → git status    co &#x3D; checkout                # git co → git checkout    br &#x3D; branch                  # git br → git branch    ci &#x3D; commit                  # git ci → git commit    df &#x3D; diff                     # git df → git diff    lg &#x3D; log --graph --decorate --pretty&#x3D;format:&#39;%C(yellow)%h%Creset -%C(cyan)%d%Creset %s %Cgreen(%cr) %C(bold blue)&lt;%an&gt;%Creset&#39; --abbrev-commit --date&#x3D;relative                                  # 美观的提交历史，带分支、标签、图形化显示    last &#x3D; log -1 HEAD           # 查看最后一次提交    unstage &#x3D; reset HEAD --      # 取消暂存区文件    amend &#x3D; commit --amend --no-edit  # 修改最后一次提交但不更改提交信息[diff]    tool &#x3D; vscode    algorithm &#x3D; histogram        # 更智能的 diff 算法，处理大文件和移动代码块更准确    colorMoved &#x3D; plain           # 启用移动代码块检测，显示更直观    mnemonicPrefix &#x3D; true        # diff 前缀更易理解    renames &#x3D; true               # 自动检测文件重命名    indentHeuristic &#x3D; true       # 更智能的 diff 块划分    compactionHeuristic &#x3D; true   # 优化 diff 输出可读性[difftool &quot;vscode&quot;]    cmd &#x3D; code --wait --diff $LOCAL $REMOTE  # 使用 VSCode 作为 diff 工具[merge]    tool &#x3D; vscode[mergetool &quot;vscode&quot;]    cmd &#x3D; code --wait $MERGED                 # 使用 VSCode 作为 merge 工具[pull]    rebase &#x3D; false              # 避免默认 rebase，可根据个人习惯改 true[push]    default &#x3D; simple            # 避免推送到错误分支    autoSetupRemote &#x3D; true      # 新建分支时自动设置上游分支    followTags &#x3D; true           # 推送 commit 时自动推送关联的 tag[init]    defaultBranch &#x3D; master      # 初始化仓库默认分支名，可改为 main[column]    ui &#x3D; auto                   # 列表命令自动对齐输出[branch]    sort &#x3D; -committerdate       # 分支列表按最近提交时间排序[tag]    sort &#x3D; version:refname      # 标签列表按版本号排序[fetch]    prune &#x3D; true                # 自动删除远程已删除的分支    pruneTags &#x3D; true            # 自动删除远程已删除的标签    # fetch.all 可选，根据项目是否多远程决定是否开启    # git config --global fetch.all true[help]    autocorrect &#x3D; prompt        # 输入命令有误时提示纠正，避免误操作[commit]    verbose &#x3D; true              # 提交时显示 diff 内容，方便检查[rerere]    enabled &#x3D; true              # 启用冲突重用功能    autoupdate &#x3D; true           # 自动应用已解决的冲突[rebase]    autoStash &#x3D; true            # rebase 前自动 stash，完成后自动 pop    autoSquash &#x3D; true           # 自动处理 fixup!&#x2F;squash! 提交    missingCommitsCheck &#x3D; warn  # 丢失提交时警告[stash]    showPatch &#x3D; true            # stash show 默认显示 diff[status]    submoduleSummary &#x3D; true     # 显示子模块变更摘要    showStash &#x3D; true            # 显示 stash 数量    aheadBehind &#x3D; true          # 显示分支领先&#x2F;落后信息[index]    threads &#x3D; true              # 多线程加速索引操作</code></pre>]]></content>
    
    
      
      
    <summary type="html">&lt;h2 id=&quot;gitignore&quot;&gt;1. gitignore&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;.gitignore&lt;/code&gt; 文件用于指定哪些文件或目录在 Git
版本控制中被忽略。它可以避免将不必要的文件（如编译产物、临时文件等）提交到版本库中。&lt;/p&gt;
&lt;pre class</summary>
      
    
    
    
    <category term="随笔" scheme="https://blog.keaikeqing.cn/categories/%E9%9A%8F%E7%AC%94/"/>
    
    
  </entry>
  
  <entry>
    <title>Live2D Widget使用说明</title>
    <link href="https://blog.keaikeqing.cn/2024/11/20/TechnicalNotes/live2d/"/>
    <id>https://blog.keaikeqing.cn/2024/11/20/TechnicalNotes/live2d/</id>
    <published>2024-11-20T04:50:50.000Z</published>
    <updated>2025-03-19T06:50:50.000Z</updated>
    
    <content type="html"><![CDATA[<h1 id="live2d-widget-使用说明">Live2D Widget 使用说明</h1><p>在网页中添加 Live2D 看板娘。兼容 PJAX，支持无刷新加载。(注：已不再需要配置依赖 jQuery 和 Font Awesome)</p><p>代码： <ahref="https://github.com/stevenjoezhang/live2d-widget">live2d-widget</a><a href="https://github.com/fghrsh/live2d_api">live2d_api</a></p><p>博文：<br /><a href="https://www.fghrsh.net/post/123.html">网页添加 Live2D看板娘</a> <a href="https://www.fghrsh.net/post/170.html">Live2D 看板娘API 迁移公告</a></p><h2 id="简单配置">1 简单配置</h2><p>只需要最基础的功能，那么只用将这一行代码加入 html 页面的<code>head</code> 或 <code>body</code> 中，即可加载看板娘：</p><pre class="language-markup" data-language="markup"><code class="language-markup"><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>script</span> <span class="token attr-name">src</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>https://fastly.jsdelivr.net/gh/stevenjoezhang/live2d-widget@latest/autoload.js<span class="token punctuation">"</span></span><span class="token punctuation">></span></span><span class="token script"></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>script</span><span class="token punctuation">></span></span></code></pre><ol type="1"><li>添加代码的位置取决于你的网站的构建方式。例如，如果你使用的是 <ahref="https://hexo.io">Hexo</a>，那么需要在主题的模版文件中添加以上代码。对于用各种模版引擎生成的页面，修改方法类似。<br /></li><li>如果网站启用了 PJAX，由于看板娘不必每页刷新，需要注意将该脚本放到PJAX 刷新区域之外。</li></ol><h2 id="进阶配置">2 进阶配置</h2><h3 id="修改autoload.js文件">2.1 修改<code>autoload.js</code>文件</h3><p>下载<ahref="https://github.com/stevenjoezhang/live2d-widget">live2d-widget</a>源码，找到其中的<code>autoload.js</code>文件，修改其中的配置项。</p><p><code>autoload.js</code>会自动加载三个文件：<code>waifu.css</code>，<code>live2d.min.js</code>和 <code>waifu-tips.js</code>。<code>waifu-tips.js</code> 会创建<code>initWidget</code>函数，这就是加载看板娘的主函数。<code>initWidget</code> 函数接收一个Object 类型的参数，作为看板娘的配置。以下是配置选项：</p><table><colgroup><col style="width: 25%" /><col style="width: 25%" /><col style="width: 25%" /><col style="width: 25%" /></colgroup><thead><tr><th>选项</th><th>类型</th><th>默认值</th><th>说明</th></tr></thead><tbody><tr><td><code>live2d_path</code></td><td><code>string</code></td><td><code>https://fastly.jsdelivr.net/gh/stevenjoezhang/live2d-widget@latest/</code></td><td>live2d-widget 路径，路径末尾的 <code>/</code> 一定要加上</td></tr><tr><td><code>apiPath</code></td><td><code>string</code></td><td><code>https://live2d.fghrsh.net/api/</code></td><td>API 路径，路径末尾的 <code>/</code> 一定要加上</td></tr><tr><td><code>cdnPath</code></td><td><code>string</code></td><td><code>https://fastly.jsdelivr.net/gh/fghrsh/live2d_api@latest/</code></td><td>CDN 路径，路径末尾的 <code>/</code> 一定要加上</td></tr><tr><td><code>tools</code></td><td><code>string[]</code></td><td>见 <code>autoload.js</code></td><td>加载的小工具按钮，可选参数</td></tr></tbody></table><p>1️⃣ <code>live2d_path</code> 是 live2d-widget的资源路径，可以自行修改。</p><p>下载<ahref="https://github.com/stevenjoezhang/live2d-widget">live2d-widget</a></p><ol type="1"><li>本地存放：将下载的仓库放在本地目录下，指向本地路径，例如<code>/assets/live2d-widget/</code>。如hexo，放在博客源文件目录下（<code>source</code> 目录），需要设置 <code>skip_render</code>。</li><li>云端存放：将下载的仓库上传到云端，指向云端路径<ul><li>github：使用 <code>jsdelivr</code>，例如<code>https://cdn.jsdelivr.net/gh/username/live2d-widget@latest/</code>，需要创建新的git tag 并推送至 GitHub 仓库中，否则此处的 <code>@latest</code>仍然指向更新前的文件。</li><li>npm：使用 <code>unpkg</code>或者<code>jsdelivr</code>，例如<code>https://unpkg.com/[package_name]/@latest/</code> 或<code>https://cdn.jsdelivr.net/npm/[package_name]/@latest/</code></li><li>云服务器: 新建<strong>静态</strong>项目，例如<code>https://example.com/path/to/live2d-widget/</code></li></ul></li></ol><p>2️⃣ <code>apiPath</code> 和 <code>cdnPath</code>两个参数设置其中一项即可。</p><p>下载<a href="https://github.com/fghrsh/live2d_api">live2d_api</a></p><ul><li><code>apiPath</code> 是后端 API 的 URL，可以自行搭建，并增加模型。<ul><li>需要支持 <code>GET</code> 请求，因此需要建站，以宝塔面板为例：<ul><li>首先，在宝塔面板创建新站点，设置好 PHP 版本（不是纯静态），并添加上SSL 证书。</li><li>然后，删去网站根目录 /www/wwwroot/api/下默认添加创建的所有文件。</li><li>打开 SSH 终端，把 Live2D API 源代码放到网站 live2d/ 目录</li></ul></li></ul></li><li><code>cdnPath</code> 则是类似于 <code>live2d_path</code>的路径，用于加载模型文件。<ul><li>本地存放和云端存放同上。</li></ul></li></ul><h3 id="添加autoload.js文件">2.2 添加<code>autoload.js</code>文件</h3><p>修改<code>live2d_path</code>后将这一行代码加入 html 页面的<code>head</code> 或 <code>body</code> 中，即可加载看板娘：</p><pre class="language-markup" data-language="markup"><code class="language-markup"><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>script</span> <span class="token attr-name">src</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>&#123;live2d_path&#125;/autoload.js<span class="token punctuation">"</span></span><span class="token punctuation">></span></span><span class="token script"></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>script</span><span class="token punctuation">></span></span></code></pre><h3 id="测试">2.3 测试</h3><p>不妨试试能否正常地通过浏览器打开 <code>autoload.js</code> 和<code>live2d.min.js</code>等文件，并确认这些文件的内容是完整和正确的。</p><h2 id="高级配置">3 高级配置</h2><p>修改样式和模型：自行查阅两个仓库的README.md文件和源码。</p>]]></content>
    
    
      
      
    <summary type="html">&lt;h1 id=&quot;live2d-widget-使用说明&quot;&gt;Live2D Widget 使用说明&lt;/h1&gt;
&lt;p&gt;在网页中添加 Live2D 看板娘。兼容 PJAX，支持无刷新加载。
(注：已不再需要配置依赖 jQuery 和 Font Awesome)&lt;/p&gt;
&lt;p&gt;代码： &lt;a
</summary>
      
    
    
    
    <category term="随笔" scheme="https://blog.keaikeqing.cn/categories/%E9%9A%8F%E7%AC%94/"/>
    
    
  </entry>
  
  <entry>
    <title>Twikoo评论系统配置</title>
    <link href="https://blog.keaikeqing.cn/2024/11/20/Web/Twikoo/"/>
    <id>https://blog.keaikeqing.cn/2024/11/20/Web/Twikoo/</id>
    <published>2024-11-20T02:00:00.000Z</published>
    <updated>2025-09-23T07:17:00.000Z</updated>
    
    <content type="html"><![CDATA[<h1 id="twikoo-评论系统配置">Twikoo 评论系统配置</h1><p>目前很多面板支持一键配置，以下是私有部署方式介绍</p><h2 id="云端部署">云端部署</h2><ol type="1"><li>在 <a href="https://twikoo.js.org/">TwiKoo 官网</a>找到云函数部署的私有部署方式，按照文档进行配置。</li><li><a href="https://nodejs.org/zh-cn/download">下载nodejs</a>选择最新的LTS版本，Linux，nvm，npm。按照命令安装。</li><li>指定镜像源：<code>npm config set registry https://registry.npmmirror.com</code></li><li>安装 Twikoo server:<code>npm i -g tkserver</code>查看是否安装成功：<code>npm ls tkserver -g</code></li><li>配置环境变量，主要关注以下几个参数：<ul><li><code>TWIKOO_DATA</code>：lokijs 数据库存储路径，默认是<code>./data</code>。</li><li><code>TWIKOO_PORT</code>：服务端口，默认是 <code>8080</code></li><li><code>TWIKOO_IP_HEADERS</code>：在特殊情况下使用，如使用了CloudFlare CDN 它会将请求 IP 写到请求头的 cf-connecting-ip字段上，为了能够正确的获取请求 IP 你可以写成[“headers.cf-connecting-ip”]，默认是 []</li></ul></li><li>启动服务<ul><li>创建一个文件夹，如<code>/www/wwwroot/twikoo</code>，<strong>在该文件夹下</strong>执行启动命令即可将数据库文件存储在该文件夹下</li><li><code>export TWIKOO_DATA=/www/wwwroot/twikoo/data</code></li><li><code>export TWIKOO_PORT=xxxx</code></li><li><code>export TWIKOO_IP_HEADERS=["headers.cf-connecting-ip"]</code></li><li><code>source ~/.bashrc</code></li><li>检查环境变量是否设置成功(注意：有时设置不成功，可以直接添加到/root/.bashrc中)：<ul><li><code>export | grep TWIKOO</code></li><li>粗略启动服务<code>tkserver</code>并访问<code>http://ip:xxxx</code>查看是否成功（<strong>注1</strong>）</li></ul></li></ul></li><li>在<strong>新文件夹</strong>下<code>nohup tkserver &gt;&gt; tkserver.log 2&gt;&amp;1 &amp;</code>命令后台启动，访问<a href="http://服务端IP:端口号" class="uri">http://服务端IP:端口号</a>测试服务是否启动成功。但是概率存在使用<code>nohup &amp;</code>命令运行shell脚本，关闭终端仍然退出(可以试试exit命令注销终端)……</li><li>配置代理实现 HTTPS 访问:<ul><li>添加纯静态站点：PHP项目，twikoo.{域名}</li><li>配置ssl证书:let’s encrypt 手动解析 通配符修改对应的阿里云和cloudflare的DNS解析，也可能只需要修改cloudflare的DNS解析</li><li>添加反向代理: 代理名称：twikoo，目标URL：<ahref="http://127.0.0.1:%7B端口%7D"class="uri">http://127.0.0.1:{端口}</a>，代理域名：$host</li></ul></li><li>最终访问地址为：<a href="https://twikoo.%7B域名%7D"class="uri">https://twikoo.{域名}</a></li><li>注意定期备份数据库文件，防止数据丢失</li></ol><h2 id="前端配置">前端配置</h2><p>到博客配置文件中配置 envId 为 https:// 加域名（例如 <ahref="https://twikoo.yourdomain.com）"class="uri">https://twikoo.yourdomain.com）</a></p><h2 id="更新云端">更新云端</h2><ol type="1"><li>停止旧版本<code>kill $(ps -ef | grep tkserver | grep -v 'grep' | awk '{print $2}')</code></li><li>拉取新版本 <code>npm i -g tkserver@latest</code></li><li>回到上文第7步重新启动服务</li></ol><p>注1：前提是需要在<strong>服务器安全组</strong>和<strong>宝塔面板</strong>中开放该端口，用完<strong>关闭</strong>这两个端口。或者先配置反向代理，直接访问域名。就不需要开放端口了。</p>]]></content>
    
    
      
      
    <summary type="html">&lt;h1 id=&quot;twikoo-评论系统配置&quot;&gt;Twikoo 评论系统配置&lt;/h1&gt;
&lt;p&gt;目前很多面板支持一键配置，以下是私有部署方式介绍&lt;/p&gt;
&lt;h2 id=&quot;云端部署&quot;&gt;云端部署&lt;/h2&gt;
&lt;ol type=&quot;1&quot;&gt;
&lt;li&gt;在 &lt;a href=&quot;https://twiko</summary>
      
    
    
    
    <category term="网站搭建" scheme="https://blog.keaikeqing.cn/categories/%E7%BD%91%E7%AB%99%E6%90%AD%E5%BB%BA/"/>
    
    
  </entry>
  
  <entry>
    <title>强化学习—— A 基于值函数vs基于策略方法</title>
    <link href="https://blog.keaikeqing.cn/2024/09/19/RL/A%20%E5%9F%BA%E4%BA%8E%E5%80%BC%E5%87%BD%E6%95%B0vs%E5%9F%BA%E4%BA%8E%E7%AD%96%E7%95%A5%E6%96%B9%E6%B3%95/"/>
    <id>https://blog.keaikeqing.cn/2024/09/19/RL/A%20%E5%9F%BA%E4%BA%8E%E5%80%BC%E5%87%BD%E6%95%B0vs%E5%9F%BA%E4%BA%8E%E7%AD%96%E7%95%A5%E6%96%B9%E6%B3%95/</id>
    <published>2024-09-19T01:40:43.000Z</published>
    <updated>2025-05-29T01:40:43.000Z</updated>
    
    <content type="html"><![CDATA[<h2 id="基于值函数-vs-基于策略方法">1. 基于值函数 vs 基于策略方法</h2><p>强化学习解决决策问题的思路可以分为两大类：一类是”评估价值后决策”，另一类是”直接优化决策”。这就是我们常说的<strong>基于值函数的方法</strong>和<strong>基于策略的方法</strong>。</p><h3 id="基于值函数的方法先评估再决策">1.1基于值函数的方法：先评估，再决策</h3><p>想象你是一个下棋的新手，如何提高棋艺？一个直观的想法是：学会评估每个局面的好坏，然后选择能达到最好局面的走法。这就是基于值函数方法的核心思想。</p><h4 id="核心理念">核心理念</h4><p>基于值函数的方法通过学习<strong>价值评估函数</strong>来指导决策：</p><ul><li><strong>值函数评估</strong>：基于值函数的方法主要通过估计状态值函数( V(s) ) 或动作价值函数 ( Q(s, a) )来评估每个状态或状态-动作对的优劣。这里，( V(s) ) 表示在状态 ( s )下能获得的期望累计奖励，而 ( Q(s, a) ) 则表示在状态 ( s ) 采取动作 ( a )后的期望累计奖励。</li><li><strong>策略间接导出</strong>：这些方法通常不直接表示策略，而是通过优化值函数，然后从中推导出最优策略。例如，在给定( Q(s, a) ) 的情况下，可以通过选择使 ( Q(s, a) )最大的动作来获得最优策略。</li><li><strong>状态价值函数</strong> <spanclass="math inline">\(V(s)\)</span>：评估”在状态s下，按照当前策略能获得多少长期奖励”</li><li><strong>动作价值函数</strong> <spanclass="math inline">\(Q(s,a)\)</span>：评估”在状态s下采取动作a，能获得多少长期奖励”</li></ul><p>有了这些价值评估，策略就水到渠成了——总是选择价值最高的动作。</p><h4 id="典型算法">典型算法</h4><p><strong>Q学习(Q-Learning)</strong>：最经典的值函数方法</p><ul><li>直接学习最优动作价值函数 <spanclass="math inline">\(Q^*(s,a)\)</span></li><li>采用”时间差分”的思想逐步改进Q值估计</li><li>属于off-policy算法，可以从任意行为策略中学习</li></ul><p><strong>深度Q网络(DQN)</strong>：Q学习的深度学习版本</p><ul><li>用神经网络近似Q函数，处理高维状态空间</li><li>引入经验回放和目标网络等技巧提高稳定性</li></ul><h3 id="基于策略的方法直接优化决策">1.2基于策略的方法：直接优化决策</h3><p>如果说值函数方法是”三思而后行”，那么策略方法就是”熟能生巧”——直接练习决策过程本身，通过不断试错来改进策略。</p><h4 id="核心理念-1">核心理念</h4><p>基于策略的方法将策略参数化为 <spanclass="math inline">\(\pi_\theta(a|s)\)</span>，直接优化策略参数 <spanclass="math inline">\(\theta\)</span>：</p><ul><li><strong>目标明确</strong>：最大化期望累积奖励 <spanclass="math inline">\(J(\theta) = \mathbb{E}_{\tau \sim\pi_\theta}[R(\tau)]\)</span></li><li><strong>梯度上升</strong>：利用策略梯度定理计算参数更新方向</li><li><strong>自然探索</strong>：随机策略天然具备探索能力</li></ul><h4 id="策略梯度定理">策略梯度定理</h4><p>策略优化的数学基础来自于著名的策略梯度定理：</p><p><span class="math display">\[\nabla_\theta J(\theta) =\mathbb{E}_{\pi_\theta}[\nabla_\theta \log \pi_\theta(a|s) \cdotQ^{\pi_\theta}(s,a)]\]</span></p><p>这个公式告诉我们：如果某个动作的价值高，就增加选择它的概率；反之则降低概率。</p><h4 id="典型算法-1">典型算法</h4><p><strong>REINFORCE算法</strong>：最基础的策略梯度方法</p><ul><li>使用蒙特卡洛方法估计回报</li><li>简单直接，但方差较大</li></ul><p><strong>PPO(Proximal PolicyOptimization)</strong>：现代策略优化的代表</p><ul><li>通过”信任区域”思想控制策略更新幅度</li><li>在样本效率和稳定性间取得良好平衡</li></ul><h2 id="两种方法的深度对比">1.3 两种方法的深度对比</h2><table><thead><tr><th>维度</th><th>基于值函数</th><th>基于策略</th></tr></thead><tbody><tr><td><strong>动作空间</strong></td><td>适合离散动作</td><td>天然支持连续动作</td></tr><tr><td><strong>策略复杂度</strong></td><td>简单确定性策略</td><td>支持复杂随机策略</td></tr><tr><td><strong>样本效率</strong></td><td>通常较高</td><td>相对较低</td></tr><tr><td><strong>探索机制</strong></td><td>需要额外设计(如ε-贪心)</td><td>策略本身包含探索</td></tr></tbody></table><h4 id="值函数方法的特点">值函数方法的特点</h4><ul><li>样本效率高，能从有限经验中高效学习</li><li>理论基础扎实，基于贝尔曼方程</li><li>在离散动作空间中表现优异</li><li>难以处理连续或高维动作空间</li><li>需要额外机制平衡探索与利用</li><li>函数近似可能导致不稳定</li></ul><h4 id="策略方法的特点">策略方法的特点</h4><ul><li>自然处理连续动作空间</li><li>策略表达能力强，可学习复杂行为</li><li>收敛性保证较好（在合适条件下）</li><li>样本需求量大</li><li>梯度估计方差高，需要降方差技巧</li><li>容易陷入局部最优</li></ul><h3 id="融合之道actor-critic方法">1.4 融合之道：Actor-Critic方法</h3><p>既然两种方法各有所长，自然有人想到将它们结合起来。这就是<strong>Actor-Critic方法</strong>的核心思想：</p><ul><li><strong>Actor(演员)</strong>：基于策略的组件，负责选择动作</li><li><strong>Critic(评论家)</strong>：基于值函数的组件，负责评估动作价值</li></ul><p>这种结合带来了显著优势：</p><ol type="1"><li>Critic帮助Actor减少梯度方差</li><li>Actor为Critic提供更好的探索策略</li><li>两者相互促进，加速收敛</li></ol><h2 id="动态规划中的经典对决策略迭代-vs-价值迭代">2.动态规划中的经典对决：策略迭代 vs 价值迭代</h2><p>在基于值函数的方法中，动态规划提供了两种经典的求解思路。需要注意的是，策略迭代和价值迭代都属于<strong>基于值函数</strong>的方法，它们的区别在于如何利用值函数来优化策略。</p><h3 id="策略迭代">2.1 策略迭代</h3><p>策略迭代是一种通过不断交替执行<strong>策略评估</strong>和<strong>策略改进</strong>来找到最优策略的方法。</p><ul><li><strong>策略评估</strong>：固定当前策略，计算该策略下所有状态的值函数(V^(s))，即评估在当前策略下，每个状态的长期奖励。</li><li><strong>策略改进</strong>：利用评估出的值函数，改进当前策略，即在每个状态下选择使值函数最大的动作。</li></ul><p>策略迭代的步骤是：</p><ol type="1"><li>初始化策略 (_0)。</li><li>对策略 (_i) 进行策略评估，计算对应的值函数 (V^{_i}(s))。</li><li>基于 (V^{<em>i}(s)) 改进策略，得到新的策略 (</em>{i+1})。</li><li>重复步骤 2 和 3，直到策略收敛到最优策略 (^*)。</li></ol><h3 id="价值迭代">2.2 价值迭代</h3><p>价值迭代通过直接更新值函数的方式来迭代逼近最优值函数(V^*(s))，然后通过值函数导出策略。</p><ul><li>每一步都会对每个状态 (s) 更新其值函数为最大化预期奖励的值</li><li>通过不断更新值函数，逐步逼近最优值函数。当值函数收敛时，可以通过选择最大化值函数的动作来获得最优策略。</li></ul><p>价值迭代的步骤是：</p><ol type="1"><li>初始化值函数 (V_0(s))。</li><li>对每个状态 (s)，更新值函数 (V_{i+1}(s))，使其最大化预期回报。</li><li>重复上述过程，直到值函数收敛。</li><li>当值函数收敛后，通过选择使 (V(s)) 最大的动作(a)，导出最优策略。</li></ol><h3 id="策略迭代-vs-价值迭代">2.3 策略迭代 vs 价值迭代</h3><ul><li><strong>策略迭代</strong>：交替执行完整的策略评估和策略改进，计算较精确的策略更新，通常收敛较快，但每次评估需要花费较多时间。</li><li><strong>价值迭代</strong>：在更新值函数的同时隐式优化策略，每次更新的粒度较小，虽然更新速度快，但可能需要更多迭代次数才能达到收敛。</li></ul><table><thead><tr><th style="text-align: center;">特性</th><th style="text-align: center;">策略迭代</th><th style="text-align: center;">价值迭代</th></tr></thead><tbody><tr><td style="text-align: center;">迭代步骤</td><td style="text-align: center;">策略评估 + 策略改进</td><td style="text-align: center;">值函数更新</td></tr><tr><td style="text-align: center;">每次迭代成本</td><td style="text-align: center;">高</td><td style="text-align: center;">低</td></tr><tr><td style="text-align: center;">迭代次数</td><td style="text-align: center;">少</td><td style="text-align: center;">多</td></tr><tr><td style="text-align: center;">收敛速度</td><td style="text-align: center;">快</td><td style="text-align: center;">慢</td></tr><tr><td style="text-align: center;">适用场景</td><td style="text-align: center;">小型 MDP，需精确解</td><td style="text-align: center;">大型 MDP，需快速近似</td></tr><tr><td style="text-align: center;">稳定性</td><td style="text-align: center;">高</td><td style="text-align: center;">可能震荡，需要调节参数</td></tr></tbody></table>]]></content>
    
    
      
      
    <summary type="html">&lt;h2 id=&quot;基于值函数-vs-基于策略方法&quot;&gt;1. 基于值函数 vs 基于策略方法&lt;/h2&gt;
&lt;p&gt;强化学习解决决策问题的思路可以分为两大类：一类是”评估价值后决策”，另一类是”直接优化决策”。这就是我们常说的&lt;strong&gt;基于值函数的方法&lt;/strong&gt;和&lt;strong</summary>
      
    
    
    
    <category term="强化学习" scheme="https://blog.keaikeqing.cn/categories/%E5%BC%BA%E5%8C%96%E5%AD%A6%E4%B9%A0/"/>
    
    
  </entry>
  
  <entry>
    <title>强化学习—— 12 DDPG算法</title>
    <link href="https://blog.keaikeqing.cn/2024/09/17/RL/12%20DDPG%E7%AE%97%E6%B3%95/"/>
    <id>https://blog.keaikeqing.cn/2024/09/17/RL/12%20DDPG%E7%AE%97%E6%B3%95/</id>
    <published>2024-09-17T11:40:43.000Z</published>
    <updated>2025-05-29T01:40:43.000Z</updated>
    
    <content type="html"><![CDATA[<h2 id="ddpg算法">12 DDPG算法</h2><p>深度确定性策略梯度是在动作空间无限的环境使用off-policy的actor-critic算法。它它的actor是一个确定性策略，通过梯度上升法来最大化Q值；它的critic是一个Q网络，通过梯度下降法来最小化Q值的TD误差。</p><h3 id="ddpg算法-1">12.1 DDPG算法</h3><h4 id="算法引出">12.1.1 算法引出</h4><ol type="1"><li>TRPO和PPO是on-policy的actor-critic算法，这意味着它们只能在当前策略上进行更新，样本效率较低。</li><li>DQN是off-policy的Q-learning算法，直接估计Q值，样本效率较高，但是但是它只能处理动作空间有限的环境，这是因为它需要从所有动作中挑选一个<spanclass="math inline">\(Q\)</span>值最大的动作。虽然可以将将动作空间离散化，但这比较粗糙，无法精细控制。</li><li>TRPO和PPO学习的是随机策略，而DDPG学习的确定性策略。随机策略可以表示为<spanclass="math inline">\(a\sim\pi_\theta(a|s)\)</span>，确定性策略可以表示为<spanclass="math inline">\(a=\mu_\theta(s)\)</span>。</li></ol><h4 id="算法公式">12.1.2 算法公式</h4><p>确定性策略梯度定理：</p><p><span class="math display">\[\nabla_\theta J(\mu_\theta)=\mathbb{E}_{s\sim \rho_\mu,a\sim\mu_\theta}[\nabla_\theta \mu_\theta(s)\nabla_aQ_\phi(s,a)|_{a=\mu_\theta(s)}]\]</span></p><p><strong>这个公式可以理解为</strong>：假定现在已有函数<spanclass="math inline">\(Q\)</span>，给定一个状态<spanclass="math inline">\(s\)</span>，但由于现在动作空间是无限的，所以无法遍历所有动作来得到最大的<spanclass="math inline">\(Q\)</span>值，因此我们用策略<spanclass="math inline">\(\mu_\theta(s)\)</span>来找到一个动作<spanclass="math inline">\(a\)</span>，使得<spanclass="math inline">\(Q(s,a)\)</span>最大。此时，<spanclass="math inline">\(Q\)</span>就是critic,<spanclass="math inline">\(\mu_\theta(s)\)</span>就是actor。</p><h4 id="算法描述">12.1.3 算法描述</h4><p>DDPG要用到4个网络：<spanclass="math inline">\(\mu,\mu&#39;,Q,Q&#39;\)</span>。其中<spanclass="math inline">\(\mu\)</span>是actor，<spanclass="math inline">\(Q\)</span>是critic，<spanclass="math inline">\(\mu&#39;\)</span>是actor的target网络，<spanclass="math inline">\(Q&#39;\)</span>是critic的target网络。</p><p>目标网络的的更新方式是<strong>软更新</strong>： <spanclass="math display">\[\omega^-=\tau\omega+(1-\tau)\omega^-\]</span> 其中<spanclass="math inline">\(\tau\)</span>是更新系数，通常很小的一个数。当<spanclass="math inline">\(\tau = 1\)</span>时，就和DQN更新方式一样了。</p><p>另外，由于函数<span class="math inline">\(Q\)</span>存在<spanclass="math inline">\(Q\)</span>值估值过高的问题，DDPG 采用了 Double DQN中的技术来更新<span class="math inline">\(Q\)</span>网络。 但是，由于DDPG采用的是确定性策略，它本身的探索仍然十分有限。作为一种离线策略的算法，DDPG在行为策略上<strong>引入一个随机噪声<spanclass="math inline">\(\mathcal{N}\)</span>来进行探索</strong>。</p><h4 id="算法流程">12.1.4 算法流程</h4><ol type="1"><li>随机初始化actor和critic的网络参数<spanclass="math inline">\(\theta\)</span>和<spanclass="math inline">\(\omega\)</span>，初始化噪声过程<spanclass="math inline">\(\mathcal{N}\)</span>。</li><li>复制<span class="math inline">\(\theta\)</span>和<spanclass="math inline">\(\omega\)</span>到<spanclass="math inline">\(\theta^-\)</span>和<spanclass="math inline">\(\omega^-\)</span>，初始化目标网络。</li><li>初始化经验回放池<spanclass="math inline">\(\mathcal{D}\)</span>。</li><li>for 轮次<span class="math inline">\(episode=1,2,...,E\)</span> do<ul><li>初始化随机过程<spanclass="math inline">\(\mathcal{N}\)</span>用来探索。</li><li>获取环境初始状态<span class="math inline">\(s_1\)</span>。</li><li>for 时间步 t=1,2,…,T do<ul><li>根据当前策略和噪声选择动作<span class="math inline">\(a_t =\mu_\theta(s_t)+\mathcal{N}\)</span>。</li><li>执行动作<span class="math inline">\(a_t\)</span>，得到新状态<spanclass="math inline">\(s_{t+1}\)</span>，奖励<spanclass="math inline">\(r_t\)</span>。</li><li>将<spanclass="math inline">\((s_t,a_t,r_t,s_{t+1})\)</span>存入<spanclass="math inline">\(\mathcal{D}\)</span>。</li><li>从<span class="math inline">\(\mathcal{D}\)</span>中随机采样<spanclass="math inline">\(N\)</span>个样本<spanclass="math inline">\({(s_i,a_i,r_i,s_{i+1})}_{i=1,2,...,N}\)</span>。</li><li>对每个样本<spanclass="math inline">\(i\)</span>，用目标网络计算<spanclass="math inline">\(y_i=r_i+\gammaQ_\phi(s_{i+1},\mu_\theta(s_{i+1}))\)</span>。</li><li>最小化目标损失函数<spanclass="math inline">\(L(\omega)=\frac{1}{N}\sum_i(y_i-Q_\omega(s_i,a_i))^2\)</span>，更新当前critic网络参数<spanclass="math inline">\(\omega\)</span>。</li><li>计算采样的<spanclass="math inline">\(N\)</span>个动作的策略梯度，更新当前actor网络参数<spanclass="math inline">\(\theta\)</span>。<spanclass="math inline">\(\nabla_\theta J \approx\frac{1}{N}\sum_i\nabla_\theta \mu_\theta(s_i)\nabla_aQ_\phi(s_i,a_i)|_{a=\mu_\theta(s_i)}\)</span></li><li>更新目标网络。<spanclass="math inline">\(\omega^-=\tau\omega+(1-\tau)\omega^-,\theta^-=\tau\theta+(1-\tau)\theta^-\)</span>。</li></ul></li><li>end for</li></ul></li><li>end for</li></ol>]]></content>
    
    
      
      
    <summary type="html">&lt;h2 id=&quot;ddpg算法&quot;&gt;12 DDPG算法&lt;/h2&gt;
&lt;p&gt;深度确定性策略梯度是在动作空间无限的环境使用off-policy的actor-critic算法。它它的actor是一个确定性策略，通过梯度上升法来最大化Q值；它的critic是一个Q网络，通过梯度下降法来最小化Q</summary>
      
    
    
    
    <category term="强化学习" scheme="https://blog.keaikeqing.cn/categories/%E5%BC%BA%E5%8C%96%E5%AD%A6%E4%B9%A0/"/>
    
    
  </entry>
  
</feed>
